diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index b92e425..0000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7bc8663 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# https://editorconfig.org +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 4 + +[*.{md,yml,yaml,json,toml}] +indent_size = 2 + +[*.ipynb] +trim_trailing_whitespace = false +insert_final_newline = false + +[Makefile] +indent_style = tab diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a35a853 --- /dev/null +++ b/.env.example @@ -0,0 +1,28 @@ +# Copy to .env and fill in. Never commit .env (it is in .gitignore). +# On Google Colab, set these via the Secrets panel (key icon) instead. + +# ── ArangoDB (GraphRAG only) ────────────────────────────────────────────────── +# Local (default): use the bundled docker-compose — `docker compose up -d`, +# then ARANGO_HOST=http://localhost:8529 and ARANGO_PASS=devpassword. +# Cloud (ArangoDB Oasis): point ARANGO_HOST at your deployment endpoint, e.g. +# https://.arangodb.cloud:8529 +ARANGO_HOST=http://localhost:8529 +ARANGO_USER=root +ARANGO_PASS= +ARANGO_DB=pubmed_graph + +# ── Ollama (LLM) ────────────────────────────────────────────────────────────── +OLLAMA_API=http://localhost:11434/api/chat +LLM_MODEL=deepseek-r1:8b + +# ── Hosted agent providers (graphrag/kgqa.service) ──────────────────────────── +# Free-tier cloud LLMs for the hosted agent. Both are optional -- unset keys +# just fall through to the local Ollama call above. +GROQ_API_KEY= +GROQ_MODEL=llama-3.1-8b-instant +GEMINI_API_KEY= +GEMINI_MODEL=gemini-1.5-flash +# Provider chains per task, comma-separated, first that succeeds wins. +LLM_CHAIN_DECOMPOSE=groq,ollama +LLM_CHAIN_EXTRACT=groq,ollama +LLM_CHAIN_SYNTHESIZE=gemini,ollama diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..04e2728 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +# Normalize line endings: LF in the repository and on checkout, everywhere. +* text=auto eol=lf + +# Must be LF to run on Unix (Makefile is also tab-sensitive). +Makefile text eol=lf +*.sh text eol=lf + +# Binary assets — no EOL conversion, no diff noise. +*.png binary +*.jpg binary +*.pdf binary +*.pptx binary +*.pkl binary +*.bin binary + +# Thin Colab wrappers are documentation, not core source — keep them out of the +# language breakdown so the repo reads as the Python project it is. +*.ipynb linguist-documentation diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..daa70c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,34 @@ +--- +name: Bug report +about: Report something that isn't working as expected +title: "[Bug] " +labels: bug +assignees: "" +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To reproduce** +Steps or the exact command, e.g.: +```bash +python scripts/run_benchmark.py --arm graph --n 200 +``` + +**Expected behavior** +What you expected to happen. + +**Logs / traceback** +``` +paste the error here +``` + +**Environment** +- OS: +- Python version: +- Running where: [local / Colab] +- GPU (if any): +- Arango reachable / Ollama running: [yes/no] + +**Additional context** +Anything else that might help. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..a78cf63 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Question / discussion + url: https://github.com/vardhjain/Knowledge_Graph_Question_Answering/discussions + about: Ask a question or discuss the methodology, results, or design. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..7392076 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea or improvement +title: "[Feature] " +labels: enhancement +assignees: "" +--- + +**What problem does this solve?** +A clear description of the motivation or gap. + +**Proposed solution** +What you'd like to happen. + +**Fairness check (for retrieval/eval changes)** +This project is a *fair* ablation. If your idea touches retrieval or evaluation, +note how it keeps the arms comparable (shared corpus/embedder/reranker/prompt/ +LLM/top-k) and avoids leaking the answer into context. + +**Alternatives considered** +Any other approaches you weighed. + +**Additional context** +Links, papers, or examples. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..286fc76 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,29 @@ +## Summary + + + +## Type of change + +- [ ] Bug fix +- [ ] New feature +- [ ] Refactor / cleanup +- [ ] Docs +- [ ] Benchmark / results + +## Checklist + +- [ ] `make test` passes +- [ ] `make lint` passes +- [ ] `CHANGELOG.md` updated under "Unreleased" +- [ ] Docs/README updated if behavior changed + +## Fairness (retrieval/evaluation changes only) + +- [ ] Confounders (embedder, reranker, prompt, LLM, top-k, seed, n) stay in + `config.py` and identical across arms +- [ ] No benchmark question/answer can leak into a retrieved context + (the leakage regression test still passes) + +## Notes + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..aa385ee --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,50 @@ +name: CI + +on: + push: + branches: [main, revamp] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.10", "3.11"] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + cache: pip + + - name: Install test dependencies + # The heavy ML libraries (torch, sentence-transformers, faiss, arango, + # datasets) are imported lazily, so unit tests need only this light set. + # fastapi/httpx are needed to test backend/main.py's TestClient. + run: | + python -m pip install --upgrade pip + python -m pip install numpy scikit-learn scipy requests pytest pytest-cov ruff fastapi httpx + + - name: Lint (ruff) + run: ruff check src scripts tests app backend + + - name: Eval regression gate + # Blocks the build if results/summary.json (what RESULTS.md, the + # README, and the /benchmark dashboard all pull from) is ever edited + # down or regresses -- see tests/test_results_regression.py for why + # this doesn't re-run the LLM benchmark itself (needs GPU + ArangoDB). + run: pytest tests/test_results_regression.py -v + + - name: Test (pytest) + run: pytest --cov=kgqa --cov=graphrag --cov-report=xml --cov-report=term-missing + + - name: Upload coverage to Codecov + if: matrix.python-version == '3.11' + uses: codecov/codecov-action@v5 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage.xml + fail_ci_if_error: false diff --git a/.github/workflows/keep-warm.yml b/.github/workflows/keep-warm.yml new file mode 100644 index 0000000..8838285 --- /dev/null +++ b/.github/workflows/keep-warm.yml @@ -0,0 +1,22 @@ +name: Keep backend warm + +# Render's free tier sleeps after ~15 min idle and cold-starts in ~30-50s. +# Ping /health every 10 minutes during daytime hours only (13:00-23:00 UTC, +# roughly 6am-4pm Pacific) so we don't blow through the schedule for no +# audience and don't run this 24/7 for free. +on: + schedule: + - cron: "*/10 13-23 * * *" + workflow_dispatch: {} + +jobs: + ping: + runs-on: ubuntu-latest + steps: + - name: Ping /health + run: | + if [ -z "${{ secrets.BACKEND_URL }}" ]; then + echo "BACKEND_URL secret not set yet -- skipping (set it once the Render service is deployed)." + exit 0 + fi + curl -fsS "${{ secrets.BACKEND_URL }}/health" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..883f881 --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +# ── OS ──────────────────────────────────────────────────────────────────────── +.DS_Store +Thumbs.db + +# ── Python ──────────────────────────────────────────────────────────────────── +__pycache__/ +*.py[cod] +*.egg-info/ +.eggs/ +build/ +dist/ +.venv/ +venv/ +env/ +.ipynb_checkpoints/ + +# ── Secrets ─────────────────────────────────────────────────────────────────── +.env + +# ── Caches & artifacts (regenerated; never committed) ───────────────────────── +pubmed_vectors_cache.pkl +Plain_RAG/pubmed_rag_index.bin +Plain_RAG/pubmed_rag_data.pkl +*.bin +*.pkl + +# ── Results (figures are committed; keep raw JSON if you want — see README) ──── +# results/ is committed intentionally so the README can reference real numbers. + +# ── Tooling ─────────────────────────────────────────────────────────────────── +.pytest_cache/ +.ruff_cache/ +.coverage +htmlcov/ +.claude/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e10fe55 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,21 @@ +# Run automatically on `git commit` after `pre-commit install`. +# See https://pre-commit.com +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.6.9 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format + + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-yaml + - id: check-toml + - id: check-added-large-files + args: [--maxkb=1024] + - id: check-merge-conflict + - id: detect-private-key diff --git a/.streamlit/config.toml b/.streamlit/config.toml new file mode 100644 index 0000000..3fabf7a --- /dev/null +++ b/.streamlit/config.toml @@ -0,0 +1,11 @@ +# Theme for the Streamlit dashboard (app/dashboard.py). Read by `streamlit run` +# locally and by Streamlit Community Cloud. Only long-stable keys are used so it +# renders correctly on any recent Streamlit version. Palette matches the +# matplotlib figure in results/ablation.png (blue primary). +[theme] +base = "light" +primaryColor = "#2196F3" +backgroundColor = "#FFFFFF" +secondaryBackgroundColor = "#F5F7FA" +textColor = "#1A2027" +font = "sans serif" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..009261f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,57 @@ +# Changelog + +All notable changes to this project are documented here. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added +- **Interactive UIs** in `app/`: a Gradio chat demo (`chat_app.py`) over the + winning `graph` arm that cites source PubMed IDs, and a Streamlit results + dashboard (`dashboard.py`) that visualizes the ablation, McNemar tests, and + per-class breakdown. `requirements-app.txt`, `make chat` / `make dashboard`. +- `BaseRetriever.chat()` — conversational answer plus the retrieved source pubids. +- `scripts/compare.py` now also writes `results/summary.json` (structured metrics + + contrasts) for the dashboard. +- One-click **Streamlit Community Cloud** deploy for the dashboard: a light + `app/requirements.txt` (picked up before the heavy root file), a themed + `.streamlit/config.toml`, a richer page config, and a README live-demo badge. + +## [1.0.0] — 2026-06-12 + +The "fair comparison" revamp: turned a confounded notebook demo into a +controlled, reproducible 4-arm ablation with an industry-standard repo layout. + +### Added +- Importable `src/kgqa/` package: `config`, `prompts`, `llm`, `data`, + `evaluation`, `models`, and a `retrieval/` sub-package (`base`, `plain`, `graph`). +- Four retrieval arms isolating each component: + `plain → plain_rr → graph → graph_concepts`. +- A shared `ChunkStore` so every arm searches an identical corpus. +- MeSH concept-hop expansion (`graph_concepts`) — the previously unused + `Concepts`/`MENTIONS` graph is now exercised. +- Seeded random sampling and a paired **McNemar** significance test. +- `scripts/`: `ingest.py` (leakage-free graph build), `run_benchmark.py` + (`--arm`, retry + Ollama auto-restart + checkpointing), `compare.py`. +- Test suite (CPU-only via fakes), GitHub Actions CI, `ruff` + `pre-commit`. +- Docs and meta: README with results, `CONTRIBUTING`, `CODE_OF_CONDUCT`, + `SECURITY`, `CITATION.cff`, issue/PR templates, `Makefile`, architecture diagram. +- Benchmark results (n=200) and ablation figure under `results/`. + +### Fixed +- **Label leakage:** ingestion no longer stores a question-derived `title` or + `final_decision`; graph contexts use generic `=== STUDY n ===` labels, so the + benchmark question/answer can never appear in a retrieved context. +- **Confounded comparison:** the cross-encoder reranker is now its own arm + instead of a hidden advantage for GraphRAG. +- **Inconsistent corpus/chunking** across arms — now identical. +- `NameError` in the graph-expansion fallback path. + +### Changed +- Generation is bounded (`num_predict`) and the model kept resident + (`keep_alive`); `LLM_NUM_CTX` / `LLM_NUM_PREDICT` are environment-tunable. +- Removed the dead `faiss` dependency (PlainRAG uses the shared numpy-cosine store). + +[Unreleased]: https://github.com/vardhjain/Knowledge_Graph_Question_Answering/compare/v1.0.0...HEAD +[1.0.0]: https://github.com/vardhjain/Knowledge_Graph_Question_Answering/releases/tag/v1.0.0 diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000..b5c28ea --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,25 @@ +cff-version: 1.2.0 +title: "Knowledge Graph Question Answering: a fair GraphRAG vs PlainRAG comparison" +message: "If you use this software or its findings, please cite it as below." +type: software +authors: + - given-names: Vardh + family-names: Jain + email: vardhjain20@gmail.com +repository-code: "https://github.com/vardhjain/Knowledge_Graph_Question_Answering" +abstract: >- + A controlled 4-arm ablation (plain, plain_rr, graph, graph_concepts) on + PubMedQA that isolates what a knowledge graph contributes to retrieval-augmented + question answering, holding corpus, chunking, embedder, reranker, prompt, LLM, + and top-k constant. Includes a paired McNemar significance test and a + leakage-free ArangoDB graph schema. +keywords: + - graphrag + - retrieval-augmented-generation + - knowledge-graph + - pubmedqa + - arangodb + - ablation-study +license: MIT +version: 1.0.0 +date-released: "2026-06-12" diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4535721 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,57 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity and +orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes +- Focusing on what is best for the overall community + +Examples of unacceptable behavior: + +- The use of sexualized language or imagery, and sexual attention or advances +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information without explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards and +will take appropriate and fair corrective action in response to any behavior +they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement via GitHub. All +complaints will be reviewed and investigated promptly and fairly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +https://www.contributor-covenant.org/version/2/1/code_of_conduct.html. + +[homepage]: https://www.contributor-covenant.org diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..edf5a5c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,64 @@ +# Contributing + +Thanks for your interest in this project! It's a research codebase for a *fair* +GraphRAG vs PlainRAG comparison on PubMedQA, so contributions that improve +rigor, reproducibility, or clarity are especially welcome. + +## Development setup + +```bash +git clone https://github.com/vardhjain/Knowledge_Graph_Question_Answering.git +cd Knowledge_Graph_Question_Answering +python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\activate +make install-dev # or: pip install -r requirements-dev.txt +pre-commit install # optional: run ruff automatically on commit +``` + +The unit tests inject fakes for the encoder, reranker, and ArangoDB, so you can +run the whole suite on CPU with **no GPU, Ollama, or database** required: + +```bash +make test # pytest +make lint # ruff +``` + +See the [Makefile](Makefile) (`make help`) for all shortcuts. + +## Where things live + +| Path | What | +| --- | --- | +| `src/kgqa/` | the importable package (single source of truth) | +| `src/kgqa/config.py` | **all** shared constants + env overrides | +| `src/kgqa/retrieval/` | the four retrieval arms (`base`, `plain`, `graph`) | +| `scripts/` | `ingest.py`, `run_benchmark.py`, `compare.py` | +| `notebooks/` | thin Colab wrappers (kept output-free) | +| `tests/` | pytest suite (CPU-only via fakes) | + +> **Why no `configs/` directory?** Configuration is centralized in +> `src/kgqa/config.py` as a typed dataclass with environment-variable overrides +> (and an `.env.example` template). For this project that's safer and less +> error-prone than scattering YAML/JSON config files; please keep new knobs there. + +## Ground rules for changes + +This repo's whole point is a **fair** comparison. Before changing retrieval or +evaluation, please make sure: + +- Anything that could confound the arms (embedder, reranker, prompt, LLM, top-k, + seed, sample size) stays in `config.py` and identical across arms. +- No benchmark answer or question text can leak into a retrieved context + (there's a regression test for this — keep it green). +- New behavior has a test; `make test` and `make lint` both pass. + +## Pull requests + +1. Branch from `main`, make focused commits. +2. Run `make test && make lint`. +3. Open a PR using the template; describe what changed and why, and update + `CHANGELOG.md` under "Unreleased". + +## Commit messages + +Short imperative subject line, a blank line, then a body explaining the *why* +when it isn't obvious. diff --git a/Data_Ingestion_KG.ipynb b/Data_Ingestion_KG.ipynb deleted file mode 100644 index 5e0c0be..0000000 --- a/Data_Ingestion_KG.ipynb +++ /dev/null @@ -1,468 +0,0 @@ -{ - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "code", - "source": [ - "!pip install python-arango sentence-transformers datasets tqdm" - ], - "metadata": { - "id": "rUGLgHO2I-_Q" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "import time\n", - "from arango import ArangoClient\n", - "from sentence_transformers import SentenceTransformer\n", - "from datasets import load_dataset\n", - "from tqdm import tqdm" - ], - "metadata": { - "id": "Nyu1zWSUJKbO" - }, - "execution_count": 2, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "ARANGO_CONFIG = {\n", - " \"hosts\": \"https://bfc25a0e3c74.arangodb.cloud:8529\",\n", - " \"username\": \"root\",\n", - " \"password\": \"VnicTWKeXaDasFNfmCfU\",\n", - " \"db_name\": \"pubmed_graph\",\n", - " \"chunk_col\": \"Chunks\",\n", - " \"context_edge\": \"HAS_CONTEXT\",\n", - " \"mention_edge\": \"MENTIONS\"\n", - "}" - ], - "metadata": { - "id": "KAdtBe5TG5E5" - }, - "execution_count": 3, - "outputs": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "dW9J5OorEnsz" - }, - "outputs": [], - "source": [ - "# @title 🚀 GraphRAG Builder (Fixed & Complete)\n", - "# This script installs dependencies, connects to ArangoDB, sets up the schema,\n", - "# and ingests the PubMedQA dataset into the graph.\n", - "\n", - "# --- MANUAL CONFIGURATION ---\n", - "# Paste your details directly here to avoid input errors:\n", - "\n", - "# 1. The URL must start with https:// and usually ends with :8529\n", - "ARANGO_URL = \"https://bfc25a0e3c74.arangodb.cloud:8529\"\n", - "\n", - "# 2. The Username is almost always 'root'\n", - "ARANGO_USER = \"root\"\n", - "\n", - "# 3. Paste the password you copied from the 'Users' tab\n", - "ARANGO_PASS = \"VnicTWKeXaDasFNfmCfU\"\n", - "\n", - "# Database Name\n", - "DB_NAME = \"pubmed_graph\"\n", - "\n", - "# --- CONNECT ---\n", - "print(f\"Connecting to {ARANGO_URL}...\")\n", - "client = ArangoClient(hosts=ARANGO_URL)\n", - "sys_db = client.db(\"_system\", username=ARANGO_USER, password=ARANGO_PASS)\n", - "\n", - "# Create/Connect to specific database\n", - "if not sys_db.has_database(DB_NAME):\n", - " sys_db.create_database(DB_NAME)\n", - " print(f\"Created database: {DB_NAME}\")\n", - "else:\n", - " print(f\"Using existing database: {DB_NAME}\")\n", - "\n", - "db = client.db(DB_NAME, username=ARANGO_USER, password=ARANGO_PASS)\n", - "print(\"✅ Connected Successfully!\")\n", - "\n", - "# --- SCHEMA SETUP ---\n", - "print(\"\\nCreating Graph Schema...\")\n", - "\n", - "# 1. Define Node Collections\n", - "node_collections = [\"Papers\", \"Chunks\", \"Concepts\"]\n", - "for col in node_collections:\n", - " if not db.has_collection(col):\n", - " db.create_collection(col)\n", - " print(f\" - Created Node Collection: {col}\")\n", - "\n", - "# 2. Define Edge Collections\n", - "edge_collections = [\"HAS_CONTEXT\", \"MENTIONS\"]\n", - "for col in edge_collections:\n", - " if not db.has_collection(col):\n", - " db.create_collection(col, edge=True)\n", - " print(f\" - Created Edge Collection: {col}\")\n", - "\n", - "# 3. Create ArangoSearch View (Fallback for Vector Search)\n", - "# FIXED: The 'vector' index type is experimental in your version.\n", - "# We use an ArangoSearch View instead, which is robust and works on all versions.\n", - "view_name = \"pubmed_view\"\n", - "\n", - "# FIXED: Use db.views() list comprehension to check existence instead of .has_view()\n", - "existing_views = [v[\"name\"] for v in db.views()]\n", - "\n", - "if view_name not in existing_views:\n", - " # FIXED: Use dedicated method 'create_arangosearch_view' to avoid TypeError on 'type' arg\n", - " db.create_arangosearch_view(\n", - " name=view_name,\n", - " properties={\n", - " \"links\": {\n", - " \"Chunks\": {\n", - " \"fields\": {\n", - " \"embedding\": {\n", - " \"analyzers\": [\"identity\"] # Needed for vector operations\n", - " },\n", - " \"text\": {\n", - " \"analyzers\": [\"text_en\"] # Useful for keyword search\n", - " }\n", - " }\n", - " }\n", - " }\n", - " }\n", - " )\n", - " print(f\" - Created ArangoSearch View: {view_name}\")\n", - "else:\n", - " print(f\" - ArangoSearch View '{view_name}' already exists.\")\n", - "\n", - "print(\"\\n✅ Database Configured Successfully!\")\n", - "\n", - "# --- LOAD DATA & MODEL ---\n", - "print(\"\\nLoading Embedding Model & Dataset...\")\n", - "\n", - "# Load Model (Runs on GPU if available in Colab)\n", - "# We use all-MiniLM-L6-v2 for speed and good performance\n", - "model = SentenceTransformer('all-MiniLM-L6-v2')\n", - "\n", - "# Load Dataset (Standard download to avoid 429 Rate Limits)\n", - "# REMOVED: streaming=True to prevent \"Too Many Requests\" error\n", - "ds = load_dataset(\"qiaojin/PubMedQA\", \"pqa_unlabeled\", split=\"train\")\n", - "\n", - "print(\"✅ Model and Data ready.\")\n", - "\n", - "# --- PROCESSING LOOP ---\n", - "# This loop processes papers, chunks them, embeds them, and inserts into ArangoDB.\n", - "\n", - "BATCH_SIZE = 50 # Number of papers to process before sending to DB (smaller batch for safety)\n", - "LIMIT_PAPERS = None # Limit for this run to ensure it finishes quickly (Set to None for full dataset)\n", - "\n", - "papers_batch = []\n", - "chunks_batch = []\n", - "concepts_batch = []\n", - "edges_batch = []\n", - "\n", - "print(f\"\\n🚀 Starting Ingestion (Limit: {LIMIT_PAPERS} papers)...\")\n", - "start_time = time.time()\n", - "\n", - "count = 0\n", - "\n", - "for row in tqdm(ds, total=LIMIT_PAPERS):\n", - " if LIMIT_PAPERS and count >= LIMIT_PAPERS:\n", - " break\n", - "\n", - " pubid = row['pubid']\n", - " question = row['question']\n", - " long_answer = row['long_answer']\n", - "\n", - " # 1. Prepare Paper Node\n", - " paper_key = str(pubid)\n", - " papers_batch.append({\n", - " \"_key\": paper_key,\n", - " \"title\": question,\n", - " \"answer\": long_answer\n", - " })\n", - "\n", - " # 2. Process Concepts (MeSH Terms)\n", - " mesh_terms = row.get('context', {}).get('meshes', [])\n", - " for mesh in mesh_terms:\n", - " # Sanitize key (Arango keys cannot contain spaces/special chars easily, so we hash or simplify)\n", - " # Here we just remove non-alphanumeric for simplicity\n", - " mesh_key = \"\".join(x for x in mesh if x.isalnum())\n", - " if not mesh_key: continue\n", - "\n", - " # Add Concept Node\n", - " concepts_batch.append({\n", - " \"_key\": mesh_key,\n", - " \"name\": mesh\n", - " })\n", - "\n", - " # Link Paper -> Concept\n", - " edges_batch.append({\n", - " \"_collection\": \"MENTIONS\",\n", - " \"_from\": f\"Papers/{paper_key}\",\n", - " \"_to\": f\"Concepts/{mesh_key}\"\n", - " })\n", - "\n", - " # 3. Process Contexts (Chunks)\n", - " contexts = row.get('context', {}).get('contexts', [])\n", - " labels = row.get('context', {}).get('labels', [])\n", - "\n", - " if contexts:\n", - " # Embed all chunks for this paper at once\n", - " embeddings = model.encode(contexts)\n", - "\n", - " for idx, (text, emb) in enumerate(zip(contexts, embeddings)):\n", - " chunk_key = f\"{paper_key}_{idx}\"\n", - "\n", - " # Add Chunk Node\n", - " chunks_batch.append({\n", - " \"_key\": chunk_key,\n", - " \"text\": text,\n", - " \"label\": labels[idx] if idx < len(labels) else \"context\",\n", - " \"embedding\": emb.tolist() # Convert numpy array to list for JSON\n", - " })\n", - "\n", - " # Link Paper -> Chunk\n", - " edges_batch.append({\n", - " \"_collection\": \"HAS_CONTEXT\",\n", - " \"_from\": f\"Papers/{paper_key}\",\n", - " \"_to\": f\"Chunks/{chunk_key}\"\n", - " })\n", - "\n", - " count += 1\n", - "\n", - " # --- BATCH INSERTION ---\n", - " if count % BATCH_SIZE == 0:\n", - " # Insert Papers\n", - " if papers_batch:\n", - " db.collection(\"Papers\").import_bulk(papers_batch, on_duplicate=\"ignore\")\n", - " # Insert Concepts\n", - " if concepts_batch:\n", - " db.collection(\"Concepts\").import_bulk(concepts_batch, on_duplicate=\"ignore\")\n", - " # Insert Chunks\n", - " if chunks_batch:\n", - " db.collection(\"Chunks\").import_bulk(chunks_batch, on_duplicate=\"ignore\")\n", - "\n", - " # Insert Edges (Must split by collection type for import_bulk)\n", - " mentions = [e for e in edges_batch if e[\"_collection\"] == \"MENTIONS\"]\n", - " contexts = [e for e in edges_batch if e[\"_collection\"] == \"HAS_CONTEXT\"]\n", - "\n", - " if mentions:\n", - " db.collection(\"MENTIONS\").import_bulk(mentions, on_duplicate=\"ignore\")\n", - " if contexts:\n", - " db.collection(\"HAS_CONTEXT\").import_bulk(contexts, on_duplicate=\"ignore\")\n", - "\n", - " # Reset batches\n", - " papers_batch = []\n", - " chunks_batch = []\n", - " concepts_batch = []\n", - " edges_batch = []\n", - "\n", - "# Final flush for remaining data\n", - "if papers_batch: db.collection(\"Papers\").import_bulk(papers_batch, on_duplicate=\"ignore\")\n", - "if concepts_batch: db.collection(\"Concepts\").import_bulk(concepts_batch, on_duplicate=\"ignore\")\n", - "if chunks_batch: db.collection(\"Chunks\").import_bulk(chunks_batch, on_duplicate=\"ignore\")\n", - "\n", - "mentions = [e for e in edges_batch if e[\"_collection\"] == \"MENTIONS\"]\n", - "contexts = [e for e in edges_batch if e[\"_collection\"] == \"HAS_CONTEXT\"]\n", - "if mentions: db.collection(\"MENTIONS\").import_bulk(mentions, on_duplicate=\"ignore\")\n", - "if contexts: db.collection(\"HAS_CONTEXT\").import_bulk(contexts, on_duplicate=\"ignore\")\n", - "\n", - "end_time = time.time()\n", - "print(f\"\\n🎉 Finished! Processed {count} papers in {end_time - start_time:.2f} seconds.\")\n", - "print(f\"Go to your ArangoDB Dashboard to see the 'pubmed_graph' database.\")\n", - "print(f\"IMPORTANT: Use 'FOR doc IN pubmed_view' in your AQL queries!\")" - ] - }, - { - "cell_type": "code", - "source": [ - "# @title ➕ Add PubMedQA \"Labeled\" Subset\n", - "# This script adds the 1,000 labeled papers to your existing graph.\n", - "\n", - "\n", - "# --- MANUAL CONFIGURATION ---\n", - "# 1. The URL must start with https:// and usually ends with :8529\n", - "ARANGO_URL = \"https://bfc25a0e3c74.arangodb.cloud:8529\"\n", - "# 2. The Username\n", - "ARANGO_USER = \"root\"\n", - "# 3. Paste the password you copied from the 'Users' tab\n", - "ARANGO_PASS = \"VnicTWKeXaDasFNfmCfU\"\n", - "# Database Name\n", - "DB_NAME = \"pubmed_graph\"\n", - "\n", - "# --- CONNECT ---\n", - "print(f\"Connecting to {ARANGO_URL}...\")\n", - "client = ArangoClient(hosts=ARANGO_URL)\n", - "db = client.db(DB_NAME, username=ARANGO_USER, password=ARANGO_PASS)\n", - "print(\"✅ Connected to 'pubmed_graph'!\")\n", - "\n", - "# --- LOAD DATA & MODEL ---\n", - "print(\"\\nLoading 'pqa_labeled' dataset...\")\n", - "\n", - "# Load the LABELED subset this time\n", - "ds_labeled = load_dataset(\"qiaojin/PubMedQA\", \"pqa_labeled\", split=\"train\")\n", - "model = SentenceTransformer('all-MiniLM-L6-v2')\n", - "\n", - "print(f\"✅ Loaded {len(ds_labeled)} labeled papers.\")\n", - "\n", - "# --- PROCESSING LOOP ---\n", - "BATCH_SIZE = 50\n", - "papers_batch = []\n", - "chunks_batch = []\n", - "concepts_batch = []\n", - "edges_batch = []\n", - "\n", - "print(\"\\n🚀 Starting Ingestion of Labeled Data...\")\n", - "start_time = time.time()\n", - "count = 0\n", - "\n", - "for row in tqdm(ds_labeled):\n", - " pubid = row['pubid']\n", - " question = row['question']\n", - " long_answer = row['long_answer']\n", - " final_decision = row.get('final_decision', None) # Unique to labeled set\n", - "\n", - " # 1. Prepare Paper Node (With extra 'final_decision' field)\n", - " paper_key = str(pubid)\n", - " papers_batch.append({\n", - " \"_key\": paper_key,\n", - " \"title\": question,\n", - " \"answer\": long_answer,\n", - " \"decision\": final_decision, # Store 'yes', 'no', or 'maybe'\n", - " \"dataset\": \"labeled\" # Tag it so we know source\n", - " })\n", - "\n", - " # 2. Process Concepts (MeSH Terms)\n", - " mesh_terms = row.get('context', {}).get('meshes', [])\n", - " for mesh in mesh_terms:\n", - " mesh_key = \"\".join(x for x in mesh if x.isalnum())\n", - " if not mesh_key: continue\n", - "\n", - " concepts_batch.append({\n", - " \"_key\": mesh_key,\n", - " \"name\": mesh\n", - " })\n", - " edges_batch.append({\n", - " \"_collection\": \"MENTIONS\",\n", - " \"_from\": f\"Papers/{paper_key}\",\n", - " \"_to\": f\"Concepts/{mesh_key}\"\n", - " })\n", - "\n", - " # 3. Process Contexts (Chunks)\n", - " contexts = row.get('context', {}).get('contexts', [])\n", - " labels = row.get('context', {}).get('labels', [])\n", - "\n", - " if contexts:\n", - " embeddings = model.encode(contexts)\n", - " for idx, (text, emb) in enumerate(zip(contexts, embeddings)):\n", - " chunk_key = f\"{paper_key}_{idx}\"\n", - " chunks_batch.append({\n", - " \"_key\": chunk_key,\n", - " \"text\": text,\n", - " \"label\": labels[idx] if idx < len(labels) else \"context\",\n", - " \"embedding\": emb.tolist()\n", - " })\n", - " edges_batch.append({\n", - " \"_collection\": \"HAS_CONTEXT\",\n", - " \"_from\": f\"Papers/{paper_key}\",\n", - " \"_to\": f\"Chunks/{chunk_key}\"\n", - " })\n", - "\n", - " count += 1\n", - "\n", - " # --- BATCH INSERTION ---\n", - " if count % BATCH_SIZE == 0:\n", - " if papers_batch: db.collection(\"Papers\").import_bulk(papers_batch, on_duplicate=\"update\") # Update if exists\n", - " if concepts_batch: db.collection(\"Concepts\").import_bulk(concepts_batch, on_duplicate=\"ignore\")\n", - " if chunks_batch: db.collection(\"Chunks\").import_bulk(chunks_batch, on_duplicate=\"ignore\")\n", - "\n", - " mentions = [e for e in edges_batch if e[\"_collection\"] == \"MENTIONS\"]\n", - " contexts = [e for e in edges_batch if e[\"_collection\"] == \"HAS_CONTEXT\"]\n", - " if mentions: db.collection(\"MENTIONS\").import_bulk(mentions, on_duplicate=\"ignore\")\n", - " if contexts: db.collection(\"HAS_CONTEXT\").import_bulk(contexts, on_duplicate=\"ignore\")\n", - "\n", - " papers_batch = []\n", - " chunks_batch = []\n", - " concepts_batch = []\n", - " edges_batch = []\n", - "\n", - "# Final Flush\n", - "if papers_batch: db.collection(\"Papers\").import_bulk(papers_batch, on_duplicate=\"update\")\n", - "if concepts_batch: db.collection(\"Concepts\").import_bulk(concepts_batch, on_duplicate=\"ignore\")\n", - "if chunks_batch: db.collection(\"Chunks\").import_bulk(chunks_batch, on_duplicate=\"ignore\")\n", - "mentions = [e for e in edges_batch if e[\"_collection\"] == \"MENTIONS\"]\n", - "contexts = [e for e in edges_batch if e[\"_collection\"] == \"HAS_CONTEXT\"]\n", - "if mentions: db.collection(\"MENTIONS\").import_bulk(mentions, on_duplicate=\"ignore\")\n", - "if contexts: db.collection(\"HAS_CONTEXT\").import_bulk(contexts, on_duplicate=\"ignore\")\n", - "\n", - "end_time = time.time()\n", - "print(f\"\\n🎉 Added {count} labeled papers in {end_time - start_time:.2f} seconds.\")" - ], - "metadata": { - "id": "q_dQy8L2Ew8r" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "# @title 🧹 Remove \"decision\" and \"dataset\" columns\n", - "# This script iterates through Papers and deletes the specific attributes.\n", - "\n", - "# 1. Define the AQL Query\n", - "# We filter for papers that actually have these fields to save processing time.\n", - "# Setting them to 'null' with 'keepNull: false' deletes the attribute entirely.\n", - "aql_clean_columns = \"\"\"\n", - "FOR p IN Papers\n", - " FILTER HAS(p, \"decision\") OR HAS(p, \"dataset\")\n", - "\n", - " UPDATE p WITH {\n", - " decision: null,\n", - " dataset: null\n", - " } IN Papers\n", - " OPTIONS { keepNull: false }\n", - "\"\"\"\n", - "\n", - "# 2. Execute\n", - "print(\"Removing 'decision' and 'dataset' columns...\")\n", - "cursor = db.aql.execute(aql_clean_columns)\n", - "\n", - "# 3. Verify\n", - "# Let's count if any remain\n", - "verification_query = \"\"\"\n", - "FOR p IN Papers\n", - " FILTER HAS(p, \"decision\")\n", - " COLLECT WITH COUNT INTO count\n", - " RETURN count\n", - "\"\"\"\n", - "count = list(db.aql.execute(verification_query))[0]\n", - "\n", - "if count == 0:\n", - " print(\"✅ Success! Columns removed. All papers now have a uniform schema.\")\n", - "else:\n", - " print(f\"⚠️ Something went wrong. {count} papers still have the decision column.\")" - ], - "metadata": { - "id": "olhs2y-2Eyww" - }, - "execution_count": null, - "outputs": [] - } - ] -} \ No newline at end of file diff --git a/GraphRAG.ipynb b/GraphRAG.ipynb deleted file mode 100644 index a520090..0000000 --- a/GraphRAG.ipynb +++ /dev/null @@ -1,691 +0,0 @@ -{ - "nbformat": 4, - "nbformat_minor": 0, - "metadata": { - "colab": { - "provenance": [] - }, - "kernelspec": { - "name": "python3", - "display_name": "Python 3" - }, - "language_info": { - "name": "python" - } - }, - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "8v2LQf5_MZW7" - }, - "outputs": [], - "source": [ - "# @title 🚀 1. Install Dependencies & Setup\n", - "# This cell installs the necessary libraries to talk to ArangoDB and process the data.\n", - "# Run this cell first!\n", - "\n", - "!pip install python-arango datasets ollama gradio sentence-transformers -q\n", - "\n", - "import time\n", - "from getpass import getpass\n", - "from datasets import load_dataset\n", - "from sentence_transformers import SentenceTransformer\n", - "import subprocess\n", - "import requests\n", - "import sys\n", - "import re\n", - "import numpy as np\n", - "import warnings\n", - "from typing import List, Dict\n", - "from arango.exceptions import ServerConnectionError, ArangoServerError\n", - "from sklearn.metrics.pairwise import cosine_similarity\n", - "from tqdm import tqdm\n", - "import os\n", - "import pickle\n", - "from sentence_transformers import CrossEncoder\n", - "import pandas as pd\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "from sklearn.metrics import accuracy_score, classification_report, confusion_matrix\n", - "import gradio as gr\n", - "\n", - "!curl -fsSL https://ollama.com/install.sh | sh\n", - "\n", - "\n", - "print(\"✅ Libraries installed.\")" - ] - }, - { - "cell_type": "code", - "source": [ - "def check_and_pull_model(model_name=\"deepseek-r1:8b\"):\n", - " \"\"\"\n", - " Checks if the model exists in Ollama. If not, pulls it automatically.\n", - " \"\"\"\n", - " print(f\"🕵️ [Ollama] Checking for model: {model_name}...\")\n", - "\n", - " # 1. Check list of models\n", - " try:\n", - " result = subprocess.run([\"ollama\", \"list\"], capture_output=True, text=True)\n", - " if model_name in result.stdout:\n", - " print(f\"✅ [Ollama] Model '{model_name}' is ready.\")\n", - " return\n", - " except Exception as e:\n", - " print(f\"⚠️ [Ollama] Could not check model list: {e}\")\n", - "\n", - " # 2. If missing, pull it\n", - " print(f\"⬇️ [Ollama] Model not found. Pulling {model_name} (This takes 2-5 mins)...\")\n", - " try:\n", - " # We use Popen to stream the output so you don't think it hung\n", - " process = subprocess.Popen(\n", - " [\"ollama\", \"pull\", model_name],\n", - " stdout=subprocess.PIPE,\n", - " stderr=subprocess.PIPE\n", - " )\n", - " while True:\n", - " output = process.stderr.readline()\n", - " if output == b'' and process.poll() is not None:\n", - " break\n", - " if output:\n", - " # Print progress to console\n", - " print(output.decode().strip())\n", - "\n", - " print(f\"✅ [Ollama] Successfully pulled {model_name}!\")\n", - "\n", - " except Exception as e:\n", - " print(f\"❌ [Ollama] Failed to pull model: {e}\")\n", - " sys.exit(1) # Stop script if model fails\n", - "\n", - "MODEL_NAME = \"deepseek-r1:8b\"\n", - "OLLAMA_API = \"http://localhost:11434/api/chat\"\n", - "\n", - "\n", - "check_and_pull_model()" - ], - "metadata": { - "id": "4zktFyVCj_vW" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "# @title\n", - "# --- 3. ROBUST UTILITIES ---\n", - "\n", - "class FuzzyEvaluator:\n", - " \"\"\"Evaluates answers with logic to handle verbosity and synonyms.\"\"\"\n", - "\n", - " def extract_answer(self, text: str) -> str:\n", - " # Strip DeepSeek \"Thinking\" blocks\n", - " clean_text = re.sub(r'.*?', '', text, flags=re.DOTALL).lower()\n", - " # Look for the last explicit declaration\n", - " match = re.search(r'(?:final answer|answer):?\\s*(yes|no|maybe)', clean_text)\n", - " if match: return match.group(1)\n", - " # Fallback: look for isolated words at end of text\n", - " matches = re.findall(r'\\b(yes|no|maybe)\\b', clean_text)\n", - " if matches: return matches[-1]\n", - " return \"maybe\" # Default safety\n", - "\n", - " def is_correct(self, gt: str, pred: str) -> bool:\n", - " gt, pred = gt.lower().strip(), pred.lower().strip()\n", - "\n", - " # 1. Exact Match\n", - " if gt == pred: return True\n", - "\n", - " # 2. Starts With (e.g. \"yes, because...\")\n", - " if pred.startswith(gt + \" \") or pred.startswith(gt + \",\"): return True\n", - "\n", - " # 3. Synonyms\n", - " positive = [\"definitely yes\", \"likely\", \"probable\", \"certainly\"]\n", - " negative = [\"unlikely\", \"doubtful\", \"never\"]\n", - "\n", - " if gt == \"yes\" and any(x in pred for x in positive): return True\n", - " if gt == \"no\" and any(x in pred for x in negative): return True\n", - "\n", - " return False\n", - "\n", - "class ArangoConnectionManager:\n", - " \"\"\"Handles the 503 Service Unavailable errors by retrying.\"\"\"\n", - "\n", - " def __init__(self, config):\n", - " self.config = config\n", - " self.client = ArangoClient(hosts=config[\"hosts\"])\n", - " self.db = self._connect_with_retry()\n", - "\n", - " def _connect_with_retry(self, max_retries=5):\n", - " for attempt in range(max_retries):\n", - " try:\n", - " # verify connection\n", - " sys_db = self.client.db(\"_system\", username=self.config[\"username\"], password=self.config[\"password\"])\n", - " sys_db.version() # Ping\n", - "\n", - " # Connect to actual DB\n", - " db = self.client.db(self.config[\"db_name\"], username=self.config[\"username\"], password=self.config[\"password\"])\n", - " print(f\"✅ [ArangoDB] Connected successfully.\")\n", - " return db\n", - " except (ServerConnectionError, ArangoServerError) as e:\n", - " wait = (attempt + 1) * 5\n", - " print(f\"⚠️ [ArangoDB] Connection failed ({e}). Retrying in {wait}s...\")\n", - " time.sleep(wait)\n", - "\n", - " raise ConnectionError(\"Could not connect to ArangoDB after retries.\")" - ], - "metadata": { - "id": "_iTxmLlfNGNB" - }, - "execution_count": 4, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "# ==========================================\n", - "# 1. THE CACHING FUNCTION (Defined locally)\n", - "# ==========================================\n", - "def load_vectors_smartly(db, collection_name, cache_file=\"pubmed_vectors_cache.pkl\"):\n", - " \"\"\"\n", - " Handles the logic: Check Disk -> If Missing, Download -> Save to Disk.\n", - " \"\"\"\n", - " # A. Check Disk\n", - " if os.path.exists(cache_file):\n", - " print(f\"💾 [Cache] Found local file: {cache_file}\")\n", - " try:\n", - " with open(cache_file, 'rb') as f:\n", - " data = pickle.load(f)\n", - " ids = data.get('ids', [])\n", - " texts = data.get('texts', [])\n", - " embeddings = data.get('embeddings', [])\n", - "\n", - " if len(embeddings) > 0:\n", - " print(f\"✅ [Cache] Loaded {len(embeddings)} vectors from disk instantly.\")\n", - " return ids, texts, embeddings\n", - " except Exception as e:\n", - " print(f\"⚠️ [Cache] File corrupted ({e}). Re-downloading...\")\n", - "\n", - " # B. Download from Cloud (Only if A failed)\n", - " print(f\"☁️ [Index] Cache missing. Downloading from ArangoDB (This happens only once)...\")\n", - "\n", - " ids, texts, embeddings = [], [], []\n", - "\n", - " # Get Count\n", - " try:\n", - " count = db.aql.execute(f\"RETURN LENGTH({collection_name})\").next()\n", - " except:\n", - " count = 200000\n", - "\n", - " # Paged Download\n", - " BATCH_SIZE = 5000\n", - " offset = 0\n", - "\n", - " with tqdm(total=count, desc=\"Downloading Index\", unit=\"vec\") as pbar:\n", - " while True:\n", - " aql = f\"\"\"\n", - " FOR c IN {collection_name}\n", - " FILTER c.embedding != null\n", - " LIMIT {offset}, {BATCH_SIZE}\n", - " RETURN {{ \"id\": c._id, \"text\": c.text, \"emb\": c.embedding }}\n", - " \"\"\"\n", - " try:\n", - " cursor = db.aql.execute(aql, ttl=3600)\n", - " batch_count = 0\n", - " for doc in cursor:\n", - " ids.append(doc[\"id\"])\n", - " texts.append(doc[\"text\"])\n", - " embeddings.append(doc[\"emb\"])\n", - " batch_count += 1\n", - "\n", - " pbar.update(batch_count)\n", - " offset += batch_count\n", - " if batch_count < BATCH_SIZE: break\n", - " time.sleep(0.1) # Be gentle on the server\n", - " except Exception as e:\n", - " print(f\"⚠️ Error on batch: {e}\")\n", - " if \"503\" in str(e): time.sleep(5)\n", - " else: break\n", - "\n", - " # C. Save to Disk\n", - " embeddings_np = np.array(embeddings)\n", - " if len(ids) > 0:\n", - " print(f\"💾 [Cache] Saving {len(ids)} vectors to {cache_file}...\")\n", - " with open(cache_file, 'wb') as f:\n", - " pickle.dump({'ids': ids, 'texts': texts, 'embeddings': embeddings_np}, f)\n", - " print(\"✅ [Cache] Saved.\")\n", - "\n", - " return ids, texts, embeddings_np\n", - "\n", - "class RobustGraphRAG:\n", - " def __init__(self, config):\n", - " self.config = config\n", - " self.client = ArangoClient(hosts=config[\"hosts\"])\n", - " self.db = self.client.db(config[\"db_name\"], username=config[\"username\"], password=config[\"password\"])\n", - "\n", - " print(\"⏳ [Model] Loading Encoders...\")\n", - " self.encoder = SentenceTransformer(\"all-MiniLM-L6-v2\")\n", - " self.reranker = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')\n", - "\n", - " self.chunk_ids, self.chunk_texts, self.chunk_embeddings = load_vectors_smartly(\n", - " self.db,\n", - " self.config['chunk_col']\n", - " )\n", - "\n", - " def retrieve(self, query: str, top_k=3):\n", - " if len(self.chunk_embeddings) == 0: return \"No context.\"\n", - "\n", - " # 1. Wider Vector Search (75 candidates)\n", - " # We widen this to ensure we catch \"Conclusion\" chunks that might use different wording\n", - " query_emb = self.encoder.encode([query])\n", - " sims = cosine_similarity(query_emb, self.chunk_embeddings)[0]\n", - " top_n_indices = np.argsort(sims)[-75:][::-1]\n", - "\n", - " candidate_pairs = []\n", - " for idx in top_n_indices:\n", - " candidate_pairs.append((self.chunk_texts[idx], self.chunk_ids[idx]))\n", - "\n", - " # 2. Re-Ranking\n", - " cross_inputs = [[query, text] for text, _ in candidate_pairs]\n", - " scores = self.reranker.predict(cross_inputs)\n", - " ranked_indices = np.argsort(scores)[::-1]\n", - "\n", - " best_chunk_ids = []\n", - " for i in range(top_k):\n", - " idx = ranked_indices[i]\n", - " _, cid = candidate_pairs[idx]\n", - " best_chunk_ids.append(cid)\n", - "\n", - " # 3. Graph Expansion (Parent Abstract Reconstruction)\n", - " aql = \"\"\"\n", - " WITH Papers, Chunks\n", - " FOR start_chunk_id IN @ids\n", - " LET start_doc = DOCUMENT(start_chunk_id)\n", - "\n", - " // Find Parent Paper\n", - " FOR paper IN 1..1 INBOUND start_doc HAS_CONTEXT\n", - "\n", - " // Get ALL chunks (Introduction + Results + Conclusion)\n", - " LET full_text_chunks = (\n", - " FOR c IN 1..1 OUTBOUND paper HAS_CONTEXT\n", - " RETURN c.text\n", - " )\n", - "\n", - " // Concatenate into a clean abstract\n", - " LET full_abstract = CONCAT_SEPARATOR(\" \", full_text_chunks)\n", - "\n", - " RETURN {\n", - " \"title\": paper.title,\n", - " \"abstract\": full_abstract\n", - " }\n", - " \"\"\"\n", - "\n", - " try:\n", - " cursor = self.db.aql.execute(aql, bind_vars={\"ids\": best_chunk_ids})\n", - " context_parts = []\n", - " seen_titles = set()\n", - "\n", - " for res in cursor:\n", - " title = res.get('title', 'Unknown')\n", - " if title in seen_titles: continue\n", - " seen_titles.add(title)\n", - "\n", - " # Add \"Study X\" header to help LLM distinguish separate papers\n", - " entry = (\n", - " f\"=== STUDY: {title} ===\\n\"\n", - " f\"ABSTRACT: {res.get('abstract')}\\n\"\n", - " )\n", - " context_parts.append(entry)\n", - "\n", - " return \"\\n\".join(context_parts)\n", - "\n", - " except Exception as e:\n", - " print(f\"⚠️ Graph Error ({e}).\")\n", - " fallback_texts = []\n", - " for i in range(top_k):\n", - " idx = ranked_indices[i]\n", - " t, _ = candidate_pairs[idx]\n", - " fallback_texts.append(f\"Excerpt: {t}\")\n", - " return \"\\n\".join(fallback_texts)\n", - "\n", - " def _heuristic_override(self, response_text):\n", - " \"\"\"\n", - " Python Safety Net: Catches 'Maybe' and flips it if strong keywords exist.\n", - " \"\"\"\n", - " clean_text = response_text.lower()\n", - "\n", - " # 1. Extract the explicit answer\n", - " match = re.search(r'(?:final answer|answer):?\\s*(yes|no|maybe)', clean_text)\n", - " pred = match.group(1) if match else \"maybe\"\n", - "\n", - " # 2. If prediction is YES or NO, trust the model.\n", - " if pred in [\"yes\", \"no\"]:\n", - " return pred\n", - "\n", - " # 3. If prediction is MAYBE, check the REASONING for \"Soft Signals\"\n", - " # Positive Signals\n", - " soft_yes = [\"suggests\", \"indicates\", \"significant\", \"associated with\", \"effective\", \"improved\"]\n", - " for word in soft_yes:\n", - " if word in clean_text:\n", - " return \"yes\"\n", - "\n", - " # Negative Signals\n", - " soft_no = [\"no significant\", \"did not\", \"unrelated\", \"ineffective\", \"no difference\"]\n", - " for word in soft_no:\n", - " if word in clean_text:\n", - " return \"no\"\n", - "\n", - " return \"maybe\"\n", - "\n", - " def query_ollama(self, prompt: str):\n", - " # The \"Calibration\" Prompt\n", - " # We align the model with PubMedQA's specific annotation style.\n", - "\n", - " system_msg = \"\"\"\n", - " You are a PubMedQA annotator.\n", - " Your task is to classify the answer as 'yes', 'no', or 'maybe' based on the Study Abstract.\n", - "\n", - " ANNOTATION GUIDELINES (CRITICAL):\n", - " 1. If the study suggests a positive outcome, even if \"further study is needed\", the answer is YES.\n", - " 2. If the study finds a correlation or association, the answer is YES.\n", - " 3. If the study finds \"no significant difference\", the answer is NO.\n", - " 4. ONLY use MAYBE if the abstract explicitly states \"results were inconclusive\" or provides zero data.\n", - "\n", - " Format:\n", - " Final Answer: [yes/no/maybe]\n", - " \"\"\"\n", - "\n", - " full_prompt = f\"{system_msg}\\n\\nContext:\\n{prompt}\"\n", - "\n", - " url = \"http://localhost:11434/api/chat\"\n", - " payload = {\n", - " \"model\": \"deepseek-r1:8b\",\n", - " \"messages\": [{\"role\": \"user\", \"content\": full_prompt}],\n", - " \"stream\": False,\n", - " \"options\": {\n", - " \"temperature\": 0.0,\n", - " \"num_ctx\": 4096\n", - " }\n", - " }\n", - " try:\n", - " res = requests.post(url, json=payload, timeout=300)\n", - " if res.status_code == 200:\n", - " raw_response = res.json()['message']['content']\n", - "\n", - " # --- APPLY THE PYTHON SAFETY NET ---\n", - " final_decision = self._heuristic_override(raw_response)\n", - "\n", - " # Return a format that your evaluator can parse\n", - " return f\"{raw_response}\\n\\n[Heuristic Override Result]: Final Answer: {final_decision}\"\n", - "\n", - " return f\"Error {res.status_code}\"\n", - " except Exception as e:\n", - " return f\"Exception: {e}\"\n", - "\n", - "\n", - "\n", - " def generate_chat_response(self, message, context):\n", - " \"\"\"\n", - " A specific prompt for the Chat UI (Conversational, not Yes/No).\n", - " \"\"\"\n", - " system_msg = \"\"\"\n", - " You are a Helpful Medical AI Assistant.\n", - " Use the provided Research Abstracts to answer the user's question accurately.\n", - "\n", - " Guidelines:\n", - " 1. Base your answer ONLY on the context provided.\n", - " 2. Cite the specific study titles when making claims (e.g., \"According to the study on X...\").\n", - " 3. If the studies are conflicting, explain the conflict.\n", - " 4. If the answer is not in the context, admit you don't have evidence but give your opinion.\n", - " \"\"\"\n", - "\n", - " full_prompt = f\"{system_msg}\\n\\nContext:\\n{context}\\n\\nUser Question: {message}\"\n", - "\n", - " url = \"http://localhost:11434/api/chat\"\n", - " payload = {\n", - " \"model\": \"deepseek-r1:8b\",\n", - " \"messages\": [{\"role\": \"user\", \"content\": full_prompt}],\n", - " \"stream\": False,\n", - " \"options\": {\"temperature\": 0.3, \"num_ctx\": 4096} # Slight creativity allowed\n", - " }\n", - " try:\n", - " res = requests.post(url, json=payload, timeout=300)\n", - " if res.status_code == 200:\n", - " return res.json()['message']['content']\n", - " return \"Error: Could not communicate with model.\"\n", - " except Exception as e:\n", - " return f\"Error: {e}\"\n", - "\n", - " # --- THE UI LAUNCHER ---\n", - " def launch_gradio_ui(self):\n", - " print(\"\\n🚀 Launching Gradio UI...\")\n", - "\n", - " def chat_logic(message, history):\n", - " # 1. Retrieve Context\n", - " print(f\"🔎 Retrieving for: {message}...\")\n", - " retrieved_context = self.retrieve(message)\n", - "\n", - " # 2. Generate Answer\n", - " print(f\"🤖 Generating Answer...\")\n", - " response = self.generate_chat_response(message, retrieved_context)\n", - "\n", - " # 3. Optional: Append Sources to the bottom of the answer\n", - " final_output = f\"{response}\\n\\n___\\n**Sources Retrieved:**\\n\"\n", - "\n", - " # Simple regex to extract titles for display\n", - " titles = re.findall(r\"=== STUDY: (.*?) ===\", retrieved_context)\n", - " for t in titles:\n", - " final_output += f\"- *{t}*\\n\"\n", - "\n", - " return final_output\n", - "\n", - " # Create the Interface\n", - " demo = gr.ChatInterface(\n", - " fn=chat_logic,\n", - " title=\"🧬 PubMed GraphRAG Assistant\",\n", - " description=\"Ask detailed medical questions. I will retrieve full abstracts from the Knowledge Graph to answer you.\",\n", - " examples=[\n", - " \"Do preoperative statins reduce atrial fibrillation?\",\n", - " \"Is obesity a risk factor for cirrhosis-related death or hospitalization?\",\n", - " \"Does high-dose aspirin prevent cardiovascular events?\"\n", - " ],\n", - " theme=\"soft\"\n", - " )\n", - "\n", - " demo.launch(share=True, debug=True)" - ], - "metadata": { - "id": "djEmPjhjNLej" - }, - "execution_count": 5, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "class AdvancedEvaluator:\n", - " def __init__(self):\n", - " self.y_true = []\n", - " self.y_pred = []\n", - " self.start_time = None\n", - " self.end_time = None\n", - "\n", - " def start(self):\n", - " \"\"\"Starts the stopwatch.\"\"\"\n", - " self.start_time = time.time()\n", - " print(\"⏱️ Evaluation Timer Started...\")\n", - "\n", - " def stop(self):\n", - " \"\"\"Stops the stopwatch.\"\"\"\n", - " self.end_time = time.time()\n", - "\n", - " def record(self, gt, pred):\n", - " \"\"\"Records a single prediction pair.\"\"\"\n", - " # Normalize to ensure clean metrics\n", - " clean_gt = gt.lower().strip()\n", - " clean_pred = pred.lower().strip()\n", - "\n", - " # Safety: If model output garbage, classify as 'maybe'\n", - " if clean_pred not in ['yes', 'no', 'maybe']:\n", - " clean_pred = 'maybe'\n", - "\n", - " self.y_true.append(clean_gt)\n", - " self.y_pred.append(clean_pred)\n", - "\n", - " def generate_report(self):\n", - " \"\"\"Calculates and visualizes all requested metrics.\"\"\"\n", - " if not self.y_true:\n", - " print(\"⚠️ No data to report.\")\n", - " return\n", - "\n", - " # 1. Total Time\n", - " total_seconds = self.end_time - self.start_time\n", - " avg_per_sample = total_seconds / len(self.y_true)\n", - "\n", - " # 2. Accuracy\n", - " acc = accuracy_score(self.y_true, self.y_pred) * 100\n", - "\n", - " print(\"\\n\" + \"=\"*40)\n", - " print(f\"📊 FINAL EVALUATION REPORT\")\n", - " print(\"=\"*40)\n", - " print(f\"⏱️ Total Time: {total_seconds:.2f} seconds\")\n", - " print(f\"⚡ Avg Latency: {avg_per_sample:.2f} seconds/query\")\n", - " print(f\"🎯 Final Accuracy: {acc:.2f}%\")\n", - " print(\"-\" * 40)\n", - "\n", - " # 3. Prediction Summary (Counts)\n", - " df = pd.DataFrame({'Ground Truth': self.y_true, 'Prediction': self.y_pred})\n", - " print(\"\\n📋 Prediction Distribution:\")\n", - " print(df['Prediction'].value_counts())\n", - "\n", - " # 4. Classification Report\n", - " print(\"\\n📈 Detailed Classification Report:\")\n", - " # We specify labels to ensure all classes show up even if count is 0\n", - " labels = ['yes', 'no', 'maybe']\n", - " print(classification_report(self.y_true, self.y_pred, labels=labels, zero_division=0))\n", - "\n", - " # 5. Confusion Matrix Visualization\n", - " cm = confusion_matrix(self.y_true, self.y_pred, labels=labels)\n", - "\n", - " plt.figure(figsize=(8, 6))\n", - " sns.set(font_scale=1.2)\n", - " sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n", - " xticklabels=labels, yticklabels=labels)\n", - " plt.xlabel('Predicted Label')\n", - " plt.ylabel('True Label')\n", - " plt.title('Confusion Matrix: PubMedQA Evaluation')\n", - " plt.show()" - ], - "metadata": { - "id": "fIPqChKGR_uN" - }, - "execution_count": 6, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "# @title\n", - "# --- 5. MAIN EXECUTION (MERGED) ---\n", - "if __name__ == \"__main__\":\n", - "\n", - " # 1. Start Server (Background)\n", - " print(\"🚀 [Ollama] Ensuring server is running...\")\n", - " subprocess.Popen([\"ollama\", \"serve\"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)\n", - " time.sleep(3) # Give it a moment to spin up\n", - "\n", - " # 2. Auto-Pull Model\n", - " check_and_pull_model(\"deepseek-r1:8b\")\n", - " rag = RobustGraphRAG(ARANGO_CONFIG)\n", - " metrics = AdvancedEvaluator()\n", - "\n", - " # 3. Load Data\n", - " print(\"📚 [Data] Loading PubMedQA...\")\n", - " dataset = load_dataset(\"qiaojin/PubMedQA\", \"pqa_labeled\", split=\"train\")\n", - "\n", - " # 4. Evaluation Loop\n", - " LIMIT = 20\n", - " print(f\"\\n=== STARTING EVALUATION (Limit: {LIMIT}) ===\")\n", - " print(\"------------------------------------------------\")\n", - "\n", - " metrics.start() # <--- Start Timer\n", - "\n", - " for i, item in enumerate(dataset):\n", - " if i >= LIMIT: break\n", - "\n", - " question = item['question']\n", - " gt = item['final_decision']\n", - "\n", - " # A. Pipeline Retrieval\n", - " context = rag.retrieve(question)\n", - "\n", - " # B. Prompt\n", - " # We pass the raw context/question. The RobustGraphRAG class adds the \"Decisive\" System Prompt.\n", - " prompt = f\"\"\"\n", - " Context Information: {context}\n", - "\n", - " Question: {question}\n", - "\n", - " Instructions:\n", - " 1. You are a helpful medical expert at a hypothetical research institution. Answer the question based on the provided context.\n", - " 2. Answer in just one word. Do not provide any explanation.\n", - " 3. This is being used only for research/educational purposes.\n", - " 4. Conclude your answer with exactly: \"Final Answer: [yes/no/maybe]\n", - " \"\"\"\n", - " raw_response = rag.query_ollama(prompt)\n", - "\n", - " # C. Logic Extraction (Handling the 'Fixed Override')\n", - " if \"[Fixed Override]\" in raw_response:\n", - " # 1. Extract the overridden answer\n", - " match = re.search(r\"Final Answer: (yes|no|maybe)\", raw_response, re.IGNORECASE)\n", - " pred = match.group(1).lower() if match else \"maybe\"\n", - "\n", - " # Print log with special \"Wrench\" icon to show the heuristic worked\n", - " icon = \"✅\" if pred == gt else \"❌\"\n", - " print(f\"[{i+1}] GT: {gt:<5} | Pred: {pred:<5} | {icon} (🛠️ Fixed)\")\n", - "\n", - " else:\n", - " # 2. Extract standard answer\n", - " match = re.search(r\"(?:final answer|answer):?\\s*(yes|no|maybe)\", raw_response.lower())\n", - " pred = match.group(1).lower() if match else \"maybe\"\n", - "\n", - " icon = \"✅\" if pred == gt else \"❌\"\n", - " print(f\"[{i+1}] GT: {gt:<5} | Pred: {pred:<5} | {icon}\")\n", - "\n", - " # D. Record Data point for the Graphs\n", - " metrics.record(gt, pred)\n", - "\n", - " # 5. Finalize & Visualize\n", - " metrics.stop() # <--- Stop Timer\n", - " metrics.generate_report() # <--- Plots Confusion Matrix" - ], - "metadata": { - "id": "VBfaqOFQDxtR", - "collapsed": true - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [ - "# Launch UI\n", - "rag.launch_gradio_ui()" - ], - "metadata": { - "id": "-LJUiQ1CR9Nm" - }, - "execution_count": null, - "outputs": [] - }, - { - "cell_type": "code", - "source": [], - "metadata": { - "id": "IYaeOxtIBp_p" - }, - "execution_count": null, - "outputs": [] - } - ] -} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a4d22a6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 The Knowledge Graph Question Answering Project Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..face578 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.DEFAULT_GOAL := help +.PHONY: help install install-dev install-app test lint format ingest benchmark compare chat dashboard clean + +help: ## Show this help + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ + awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-12s\033[0m %s\n", $$1, $$2}' + +install: ## Install runtime dependencies + pip install -r requirements.txt + +install-dev: ## Install dev dependencies (tests + lint) + pip install -r requirements-dev.txt + +install-app: ## Install UI dependencies (gradio + streamlit) + pip install -r requirements-app.txt + +test: ## Run the test suite + pytest + +lint: ## Lint with ruff + ruff check src scripts tests app + +format: ## Auto-fix lint issues with ruff + ruff check --fix src scripts tests app + +ingest: ## Build the ArangoDB knowledge graph (needs ARANGO_PASS) + python scripts/ingest.py + +benchmark: ## Run all four arms (needs ARANGO_PASS + Ollama) + @for arm in plain plain_rr graph graph_concepts; do \ + echo "===== $$arm ====="; \ + python scripts/run_benchmark.py --arm $$arm --n 200; \ + done + +compare: ## Aggregate results into table, McNemar tests, and figure + python scripts/compare.py + +chat: ## Launch the Gradio chat demo (needs ArangoDB + Ollama) + python app/chat_app.py + +dashboard: ## Launch the Streamlit results dashboard + streamlit run app/dashboard.py + +clean: ## Remove caches and generated vector cache + rm -rf .pytest_cache .ruff_cache *.egg-info src/*.egg-info \ + pubmed_vectors_cache.pkl + find . -type d -name __pycache__ -exec rm -rf {} + diff --git a/Plain_RAG/Plain_RAG.ipynb b/Plain_RAG/Plain_RAG.ipynb deleted file mode 100644 index d35c42c..0000000 --- a/Plain_RAG/Plain_RAG.ipynb +++ /dev/null @@ -1,8831 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "SwCv3__NIaTo", - "outputId": "ad7bf0ed-b938-45a8-a266-97cd05508255" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m59.4/59.4 MB\u001b[0m \u001b[31m33.6 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25h" - ] - } - ], - "source": [ - "!pip install -q sentence-transformers datasets transformers accelerate bitsandbytes" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "e24TcF9NZ2gO", - "outputId": "071b2bd1-ed2d-403b-b0d6-a110f624fd0e" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Requirement already satisfied: matplotlib in /usr/local/lib/python3.12/dist-packages (3.10.0)\n", - "Requirement already satisfied: seaborn in /usr/local/lib/python3.12/dist-packages (0.13.2)\n", - "Requirement already satisfied: contourpy>=1.0.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.3.3)\n", - "Requirement already satisfied: cycler>=0.10 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (0.12.1)\n", - "Requirement already satisfied: fonttools>=4.22.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (4.60.1)\n", - "Requirement already satisfied: kiwisolver>=1.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (1.4.9)\n", - "Requirement already satisfied: numpy>=1.23 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (2.0.2)\n", - "Requirement already satisfied: packaging>=20.0 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (25.0)\n", - "Requirement already satisfied: pillow>=8 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (11.3.0)\n", - "Requirement already satisfied: pyparsing>=2.3.1 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (3.2.5)\n", - "Requirement already satisfied: python-dateutil>=2.7 in /usr/local/lib/python3.12/dist-packages (from matplotlib) (2.9.0.post0)\n", - "Requirement already satisfied: pandas>=1.2 in /usr/local/lib/python3.12/dist-packages (from seaborn) (2.2.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.12/dist-packages (from pandas>=1.2->seaborn) (2025.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.12/dist-packages (from pandas>=1.2->seaborn) (2025.2)\n", - "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.12/dist-packages (from python-dateutil>=2.7->matplotlib) (1.17.0)\n" - ] - } - ], - "source": [ - "!pip install matplotlib seaborn" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "RORhfl85J00q", - "outputId": "1b0c3ab1-d493-4d0d-a58a-9d165875c2d7" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Collecting faiss-gpu-cu12\n", - " Downloading faiss_gpu_cu12-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (12 kB)\n", - "Requirement already satisfied: numpy<3,>=2 in /usr/local/lib/python3.12/dist-packages (from faiss-gpu-cu12) (2.0.2)\n", - "Requirement already satisfied: packaging in /usr/local/lib/python3.12/dist-packages (from faiss-gpu-cu12) (25.0)\n", - "Requirement already satisfied: nvidia-cuda-runtime-cu12>=12.1.105 in /usr/local/lib/python3.12/dist-packages (from faiss-gpu-cu12) (12.6.77)\n", - "Requirement already satisfied: nvidia-cublas-cu12>=12.1.3.1 in /usr/local/lib/python3.12/dist-packages (from faiss-gpu-cu12) (12.6.4.1)\n", - "Downloading faiss_gpu_cu12-1.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (48.3 MB)\n", - "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m48.3/48.3 MB\u001b[0m \u001b[31m30.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n", - "\u001b[?25hInstalling collected packages: faiss-gpu-cu12\n", - "Successfully installed faiss-gpu-cu12-1.13.0\n" - ] - } - ], - "source": [ - "!pip install faiss-gpu-cu12" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "pVq9qIBETknV" - }, - "outputs": [], - "source": [ - "import os\n", - "import torch\n", - "import faiss\n", - "import numpy as np\n", - "import time\n", - "import pickle\n", - "import gradio as gr\n", - "from tqdm.notebook import tqdm\n", - "from datasets import load_dataset\n", - "from sentence_transformers import SentenceTransformer\n", - "from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig\n", - "from sklearn.metrics import accuracy_score, classification_report\n", - "import matplotlib.pyplot as plt\n", - "import seaborn as sns\n", - "from sklearn.metrics import confusion_matrix\n", - "import pandas as pd" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "X_tuVvKdTohM" - }, - "outputs": [], - "source": [ - "EMBEDDING_MODEL_NAME = \"sentence-transformers/all-mpnet-base-v2\"\n", - "LLM_MODEL_NAME = \"deepseek-ai/DeepSeek-R1-Distill-Llama-8B\"\n", - "INDEX_TYPE = \"IndexFlatIP\" # Inner Product (Cosine Similarity)\n", - "BATCH_SIZE = 128\n", - "TOP_K_RETRIEVAL = 3\n", - "INDEX_FILE = \"pubmed_rag_index.bin\"\n", - "DATA_FILE = \"pubmed_rag_data.pkl\"" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "nOAJa-drTsrp" - }, - "outputs": [], - "source": [ - "class PubMedRAG:\n", - " def __init__(self):\n", - " self.device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", - " print(f\"Initializing RAG Pipeline on {self.device}\")\n", - "\n", - " # 1. Load Embedding Model\n", - " print(f\"Loading Embedding Model: {EMBEDDING_MODEL_NAME}\")\n", - " self.embedder = SentenceTransformer(EMBEDDING_MODEL_NAME, device=self.device)\n", - " self.embedding_dim = self.embedder.get_sentence_embedding_dimension()\n", - "\n", - " # 2. Load LLM (4-bit quantized)\n", - " print(f\"Loading LLM: {LLM_MODEL_NAME}\")\n", - " bnb_config = BitsAndBytesConfig(\n", - " load_in_4bit=True,\n", - " bnb_4bit_compute_dtype=torch.float16,\n", - " bnb_4bit_quant_type=\"nf4\",\n", - " )\n", - " self.tokenizer = AutoTokenizer.from_pretrained(LLM_MODEL_NAME)\n", - " self.llm = AutoModelForCausalLM.from_pretrained(\n", - " LLM_MODEL_NAME,\n", - " quantization_config=bnb_config,\n", - " device_map=\"auto\"\n", - " )\n", - "\n", - " # 3. Initialize placeholders\n", - " self.index = None\n", - " self.documents = []\n", - " self.labeled_data = []\n", - "\n", - " def load_and_index_data(self):\n", - " \"\"\"Loads data. Tries to load from disk first; otherwise builds from scratch.\"\"\"\n", - "\n", - " # --- OPTION A: LOAD FROM DISK ---\n", - " if os.path.exists(INDEX_FILE) and os.path.exists(DATA_FILE):\n", - " print(f\"\\nFound saved index and data on disk!\")\n", - " print(f\" - Loading Index from {INDEX_FILE}\")\n", - " self.index = faiss.read_index(INDEX_FILE)\n", - "\n", - " # Move to GPU if possible\n", - " if self.device == \"cuda\" and hasattr(faiss, \"StandardGpuResources\"):\n", - " try:\n", - " res = faiss.StandardGpuResources()\n", - " self.index = faiss.index_cpu_to_gpu(res, 0, self.index)\n", - " print(\" Index moved to GPU.\")\n", - " except Exception as e:\n", - " print(f\" GPU move failed ({e}), keeping on CPU.\")\n", - "\n", - " print(f\" - Loading Data from {DATA_FILE}...\")\n", - " with open(DATA_FILE, \"rb\") as f:\n", - " saved_data = pickle.load(f)\n", - " self.documents = saved_data[\"documents\"]\n", - " self.labeled_data = saved_data[\"labeled_data\"]\n", - " print(\"State restored.\")\n", - " return\n", - "\n", - " # --- OPTION B: BUILD FROM SCRATCH ---\n", - " print(\"\\nLoading Datasets from Hugging Face\")\n", - " ds_labeled = load_dataset(\"qiaojin/PubmedQA\", \"pqa_labeled\", split=\"train\")\n", - " ds_unlabeled = load_dataset(\"qiaojin/PubmedQA\", \"pqa_unlabeled\", split=\"train\")\n", - " ds_artificial = load_dataset(\"qiaojin/PubmedQA\", \"pqa_artificial\", split=\"train\")\n", - "\n", - " print(f\" - Labeled: {len(ds_labeled)}\")\n", - " print(f\" - Unlabeled: {len(ds_unlabeled)}\")\n", - " print(f\" - Artificial: {len(ds_artificial)}\")\n", - "\n", - " def process_split(dataset, split_name):\n", - " docs = []\n", - " for item in tqdm(dataset, desc=f\"Processing {split_name}\"):\n", - " full_text = \" \".join(item['context']['contexts'])\n", - " question_text = item.get('question', \"\")\n", - " if not question_text and split_name == \"labeled\":\n", - " question_text = item.get('question', \"No Question Found\")\n", - "\n", - " docs.append({\n", - " \"text\": full_text,\n", - " \"pubid\": item['pubid'],\n", - " \"question\": question_text,\n", - " \"final_decision\": item.get('final_decision', None)\n", - " })\n", - " return docs\n", - "\n", - " self.labeled_data = process_split(ds_labeled, \"labeled\")\n", - " all_docs = []\n", - " all_docs.extend(self.labeled_data)\n", - " all_docs.extend(process_split(ds_unlabeled, \"unlabeled\"))\n", - " all_docs.extend(process_split(ds_artificial, \"artificial\"))\n", - "\n", - " self.documents = all_docs\n", - " print(f\"Total Documents: {len(self.documents)}\")\n", - "\n", - " print(\"\\nGenerating Embeddings\")\n", - " texts = [d['text'] for d in self.documents]\n", - " embeddings = self.embedder.encode(\n", - " texts,\n", - " batch_size=BATCH_SIZE,\n", - " show_progress_bar=True,\n", - " convert_to_numpy=True,\n", - " normalize_embeddings=True\n", - " )\n", - "\n", - " print(f\"\\nBuilding FAISS {INDEX_TYPE} Index\")\n", - " index_flat = faiss.IndexFlatIP(self.embedding_dim)\n", - " index_flat.add(embeddings)\n", - "\n", - " # Save to disk\n", - " print(\"Saving to disk for future runs\")\n", - " faiss.write_index(index_flat, INDEX_FILE)\n", - " with open(DATA_FILE, \"wb\") as f:\n", - " pickle.dump({\"documents\": self.documents, \"labeled_data\": self.labeled_data}, f)\n", - "\n", - " # Enable GPU\n", - " if self.device == \"cuda\" and hasattr(faiss, \"StandardGpuResources\"):\n", - " try:\n", - " res = faiss.StandardGpuResources()\n", - " self.index = faiss.index_cpu_to_gpu(res, 0, index_flat)\n", - " except:\n", - " self.index = index_flat\n", - " else:\n", - " self.index = index_flat\n", - "\n", - " def retrieve(self, query, k=TOP_K_RETRIEVAL):\n", - " query_vec = self.embedder.encode([query], convert_to_numpy=True, normalize_embeddings=True)\n", - " distances, indices = self.index.search(query_vec, k)\n", - " results = []\n", - " for i, idx in enumerate(indices[0]):\n", - " if idx != -1:\n", - " results.append(self.documents[idx])\n", - " return results\n", - "\n", - " def generate_response(self, query, retrieved_docs, mode=\"detailed\"):\n", - " context_text = \"\\n\\n\".join([f\"Abstract {i+1}: {doc['text']}\" for i, doc in enumerate(retrieved_docs)])\n", - "\n", - " # 1. Define System & User Prompts\n", - " if mode == \"benchmark\":\n", - " sys_msg = (\n", - " \"Answer in just one word based on the given context.Do not provide any explanation. You final answer should be one of 3 words: yes, no, maybe\"\n", - " )\n", - " temp = 0.6\n", - " max_tokens = 2048\n", - " rep_penalty = 1.1\n", - "\n", - " else:\n", - " sys_msg = (\n", - " \"You are a helpful medical assistant. Answer the user's question based on the provided medical abstracts. \"\n", - " \"Cite the abstracts by number if necessary. Be concise.\"\n", - " )\n", - " temp = 0.6\n", - " max_tokens = 1024\n", - " rep_penalty = 1.1\n", - "\n", - " # 2. Create Chat Structure (Standard for Llama/DeepSeek)\n", - " messages = [\n", - " {\"role\": \"system\", \"content\": sys_msg},\n", - " {\"role\": \"user\", \"content\": f\"Contexts:\\n{context_text}\\n\\nQuestion: {query}\"}\n", - " ]\n", - "\n", - " # 3. Apply Chat Template (Handles special tokens like <|begin_of_text|>)\n", - " inputs = self.tokenizer.apply_chat_template(\n", - " messages,\n", - " tokenize=True,\n", - " add_generation_prompt=True,\n", - " return_tensors=\"pt\"\n", - " ).to(self.device)\n", - "\n", - " # 4. Generate\n", - " outputs = self.llm.generate(\n", - " inputs,\n", - " max_new_tokens=max_tokens,\n", - " temperature=temp,\n", - " top_p=1.0,\n", - " do_sample=False if mode==\"benchmark\" else True,\n", - " repetition_penalty=rep_penalty,\n", - ")\n", - "\n", - " # 5. Decode ONLY the new tokens (Slice off the prompt)\n", - " # This removes the need to manually split \"Question:...\" from the output\n", - " generated_tokens = outputs[0][len(inputs[0]):]\n", - " response = self.tokenizer.decode(generated_tokens, skip_special_tokens=False)\n", - "\n", - " # 6. Clean up DeepSeek tags\n", - " if \"\" in response:\n", - " response = response.split(\"\")[-1].strip()\n", - " #else:\n", - " # Fallback: specific regex if tags are still missing but reasoning is evident\n", - " #response = re.sub(r'.*?', '', response, flags=re.DOTALL).strip()\n", - "\n", - " # 7. Clean up \"Answer:\" prefix if present\n", - " if response.startswith(\"Answer:\"):\n", - " response = response[7:].strip()\n", - "\n", - " # 8. Final clean of EOS tokens\n", - " response = response.replace(\"\", \"\").replace(\"<|end_of_text|>\", \"\").replace(\"<|end_of_sentence|>\", \"\").replace(\"<|end▁of▁sentence|>\", \"\").strip()\n", - "\n", - " return response\n", - "\n", - "\n", - " def run_benchmark(self, sample_size=50):\n", - "\n", - " print(f\"\\nSTARTING BENCHMARK (Sample Size: {sample_size})...\")\n", - "\n", - " # Slice the test set\n", - " test_set = self.labeled_data[:sample_size]\n", - " y_true = []\n", - " y_pred = []\n", - "\n", - " start_time = time.time()\n", - "\n", - " for item in tqdm(test_set, desc=\"Benchmarking\"):\n", - " # Safety check for data integrity\n", - " question = item.get('question')\n", - " ground_truth = item.get('final_decision')\n", - "\n", - " if not question or not ground_truth:\n", - " continue\n", - "\n", - " # Retrieve and Generate\n", - " retrieved = self.retrieve(question, k=TOP_K_RETRIEVAL)\n", - " response = self.generate_response(question, retrieved, mode=\"benchmark\")\n", - "\n", - " # Normalize prediction\n", - " pred_lower = response.lower()\n", - " prediction = \"maybe\"\n", - " if \"yes\" in pred_lower:\n", - " prediction = \"yes\"\n", - " elif \"no\" in pred_lower:\n", - " prediction = \"no\"\n", - "\n", - " y_true.append(ground_truth)\n", - " y_pred.append(prediction)\n", - "\n", - " duration = time.time() - start_time\n", - "\n", - " print(\"\\n\" + \"=\"*50)\n", - " print(\"BENCHMARK RESULTS\")\n", - " print(\"=\"*50)\n", - " print(f\"Time taken: {duration:.2f}s\")\n", - " print(f\"Accuracy: {accuracy_score(y_true, y_pred):.2%}\")\n", - "\n", - " # 1. Classification Report (Existing)\n", - " labels = [\"yes\", \"no\", \"maybe\"]\n", - " print(\"\\n--- Classification Report ---\")\n", - " print(classification_report(y_true, y_pred, labels=labels, zero_division=0))\n", - "\n", - " # 2. Prediction Counts (New)\n", - " print(\"\\n--- Prediction Summary ---\")\n", - " pred_counts = pd.Series(y_pred).value_counts().reindex(labels, fill_value=0)\n", - " print(pred_counts)\n", - "\n", - " # 3. Confusion Matrix (New)\n", - " cm = confusion_matrix(y_true, y_pred, labels=labels)\n", - "\n", - " # Plotting the Matrix\n", - " plt.figure(figsize=(8, 6))\n", - " sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n", - " xticklabels=labels, yticklabels=labels)\n", - " plt.xlabel('Predicted')\n", - " plt.ylabel('Actual')\n", - " plt.title('Confusion Matrix')\n", - " plt.show()\n", - "\n", - " def launch_gradio_ui(self):\n", - " \"\"\"Launches the Gradio Chat Interface.\"\"\"\n", - " print(\"\\nLaunching Gradio UI\")\n", - "\n", - " def chat_logic(message, history):\n", - " # We ignore history for single-turn RAG to keep context clean and fast\n", - " retrieved = self.retrieve(message)\n", - " response = self.generate_response(message, retrieved, mode=\"interactive\")\n", - " return response\n", - "\n", - " demo = gr.ChatInterface(\n", - " fn=chat_logic,\n", - " title=\"PubMed Medical AI Assistant\",\n", - " description=\"Ask detailed medical questions. The AI retrieves relevant abstracts from the PubMedQA dataset to generate answers.\",\n", - " examples=[\n", - " \"Do preoperative statins reduce atrial fibrillation?\",\n", - " \"Is Hirschsprung disease a mendelian or a multifactorial disorder?\",\n", - " \"Does high-dose aspirin prevent cardiovascular events?\"\n", - " ],\n", - " theme=\"soft\"\n", - " )\n", - "\n", - " # share=True creates a public link\n", - " demo.launch(share=True, debug=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "rITTxoHN7cfw", - "outputId": "14a279a4-8c76-4bfd-ef9c-432d89212b79" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "GPU Memory Cleared. Current allocated: 0.00 MB\n" - ] - } - ], - "source": [ - "import gc\n", - "if 'rag_system' in globals():\n", - " del rag_system\n", - " print(\"Deleted rag_system object.\")\n", - "\n", - "if 'app' in globals():\n", - " del app\n", - " print(\"Deleted app object.\")\n", - "\n", - "# 2. Run Garbage Collector\n", - "gc.collect()\n", - "if torch.cuda.is_available():\n", - " torch.cuda.empty_cache()\n", - " torch.cuda.ipc_collect() # Clear IPC memory if using multiprocessing\n", - " print(f\"GPU Memory Cleared. Current allocated: {torch.cuda.memory_allocated() / 1024**2:.2f} MB\")\n", - "else:\n", - " print(\"No GPU detected.\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 833, - "referenced_widgets": [ - "4a0ea4434e584a36bdafbd77fdf915d4", - "9ffe20e0f2124c14b7c12ce8daf7c4b5", - "ea06f0dd061c4e3a96240433969ed793", - "c527b5e31da3403fb57d9fd4b2b9b191", - "76f4bb7b9b104810985282bd16842650", - "afa27f83547045aca2c4e8e92fe49878", - "871272ea241147bb87feeaaf793e1d9e", - "482e5a82919f4a1ab8168b0006b2d979", - "8ac89f580fb8427697cac9fad7ee1693", - "5dd09f0c273647bd89678aa2fe32f6df", - "e4ea1f9ea6a343ef995c2eface0efd13", - "c2c50ced649b428097bffe04f97a137f", - "38b4097af230467a81ee65454f907eb5", - "0b6e7094f94f4cb481e772bd5443a025", - "1d4b2d25edcd4207bce2972e824e815d", - "60574c59d6da4bfa92b4b93884d2b5d8", - "ab64037914e04ee7adb2877784271790", - "feeb9cc484ee467b851fb576af521adf", - "a4ba3264f68348eb9650d38fd1668a26", - "421cda2009074fcd9c5d4a81109b67ce", - "e2a80bf2c52d4a0c98731e3de6a24f22", - "b06f97d725d14843a55ff2442af9a97e", - "c2536ef6eb4341e3b54cf473ae498ca9", - "92a456546b7b493d8321c24a62a6b551", - "ea161cf843184232bb844545176cffb0", - "9a80ee6fd91a401dac1652acd15d44f5", - "3bd2d7fb9da04416b2bcca81d4d15bd5", - "c9d5bcb10e674784a570b625a59f12cf", - "e88678951fc2460ea8241edba800a2c0", - "bdf14d4a82c2425d8ec0c9a2acc63055", - "603d475c14724766b576206c2ec53a45", - "7f5af4598bb24c0b992424624f6dfb6d", - "74af2c01c93847bdb011d0368acf44f7", - "b316a24c01d341ca878ebc93bb6db8ad", - "77232511fed041feaeefc974aceee07f", - "1d67cff04e32401da2213ad82228b20d", - "774add6aa42e4884809cdf435ce39068", - "0f56e7ca761d45c5ab1345df35e81f9e", - "d0c8f23c7434465ab284741201956266", - "13a434a2a4624e6192ca51ae7e394667", - "fac8b597f40c43a29f69ce1e6f3a77f9", - "aad85d3f773545799449c1a6080e7302", - "a0d0c70f3f11437f9c99619436e7f077", - "f6821378164c4c2eb9458d1458ca58e1", - "e34578ea4acd4623ba900470212faa90", - "9eddcbf6d4cc4f7ca5ba92e7d2dfd6db", - "2b77339b62684a4785226247f3e5e85d", - "29fe6407ea524db591c8f3579a4f8410", - "e07c41df4bf447acae4a58ae3b0c0d73", - "61ff82cf14374a28a23b941d965027eb", - "fa92d691af6946568131ab68afffbf00", - "efde71ea0b7440acbb9f45fdb5678264", - "196df619acff47b4a6ec9f9ce1710dd1", - "41f98a688b284ab4b6ffa93c1703abf4", - "94471425ae534a29a6d3d86a79312039", - "1d7ed0749dae4c81bad40d13c43d7629", - "439dc3fac7864363a295684ae40dbb35", - "5fad1f69c31243bebf49515b52540989", - "d031a5cdcf8d4a3aa030c22f9bbb423b", - "4867ed9d8d294c239f589708056ed572", - "17ea216363d94349b98f267554d7aeae", - "035286955a1b4a83957eb326f6bb0bee", - "3dd72d49bb3548fca32d19eded946c2d", - "7d3ad593a3314885a4f213e4c89da0c4", - "7254e9e3c0a24d0eaa7908b8018e3f83", - "28a9db2641bf474b84eb193e3a5143be", - "6ac6e1708632488b9af0bbefb6542a02", - "e847e23b3dc64c3684d713e36a2d43e5", - "bb112f11bd27452fadf6e17f6f5f5022", - "81d8ade37ad944c2bf20d5d2bec8e94c", - "b4d7798f0f9e4d68bb10104c227c8236", - "45041ac463164980bb6437335bcf7edf", - "4696a263556c417489660ea5c6240551", - "f41affd88fb944ed8ac6cc632d2d7edc", - "a423c1bf1c4f47dcb3ca275f27f4b23b", - "dfdac9cdd0c546429604490fdecb22a7", - "db2458bb9a0a49d8b087a2b1ebd0864d", - "38dcc7d3f5c142c68e7d5c5f6b86087d", - "f5be3e18dfc24499ae25a2ddb496ae02", - "cd046088208841c0be5b5c78203b00b8", - "7d874d8f7e744ce191f0cdae0798e707", - "eb6ee46fe93a46489385ae7ee409665d", - "a10211fa0818443f89043174a09bee1a", - "c0765fdc3eb143d6880ff4e00141ce14", - "d5f205776c3f42ccaa9542f715b85748", - "b19cf7b7c453462ea0afb26e12f404f2", - "89f29ab2c0f84bdf812d34265d0319d2", - "889fb0ea3dc3479ba47569301abf1095", - "1ef175665a774f44896a1f938d318c3d", - "7d74b694cba0407f992bce853ce926a3", - "a15d8fd61eba4842b6be4c015cca167d", - "0e048e0c3d8045309f54e9da505dc91b", - "bffaec4d3d254433b55a82dec359bf2c", - "d5bc725f098447a6920aee7ff3e00a97", - "6583e5aadfca452c93f8f87ec2f97b32", - "94a70ea4f9f94d7dbfbda2f820bf7016", - "66d531e4e851439e9b66b7a9b2d285b1", - "49b1777da63c42e69be269a810493345", - "aa365a31e1964c5bb4298a227b23d66c", - "6c52dc5d7772478eac56b01ea54c8e19", - "6a88eb28c414476c86ca250e5df03db6", - "54b693f1fd55413e825bcf420fa43e6a", - "1feef1a0e35c475e964d9e598cf1b4c5", - "e5a8f5ebb26843cf92150b2f817b2dad", - "2fba292dcd994df7bfeb0e2bdf672b5d", - "b54500bcbde249ffa53ccd68c2105b7a", - "0206f0d4ca17415f8691d3125d380aff", - "0b57c423eedc41a287fefe85e1f63d7c", - "927fff0357d7428480d2bd936b21ae6f", - "8e48b73fd5c648eb9609a8aa9830d12f", - "1405d35083dc4c11a9ad396085c97a86", - "f73b129ccce84d9f8b09c85ea7e13a5d", - "0b2f7c9999d84d49afa93b78e67ef2fa", - "9951e4c31cf445049d9f14280c1b063e", - "937d0c8d494d40c98927ea97d39933a9", - "599727a146204539892e35143c16557b", - "18b23622d00442ca9e977b587b0c7399", - "3633835bfdc24858bf09a23ddb01a75c", - "a917cc0882cf4354bd0d0ca37b0f5e62", - "a6be0858867147f6b1c64b626d583a2c", - "890f5d59c2944577921d3f31529aeaf9", - "bb412ac42ad64c30934b9594bfad392b", - "cac61cb1e1d7406e9dc97aa868525dfe", - "b38d78293de747829603e05788a7482c", - "538567e28b9942808b2d7197c156fdb1", - "9a02b190859e49efaeb6d51605eee3b2", - "428e6cbcb8bc40bcb1b414667bb76b2a", - "3ea8fd43ac404f9bb6981c2a90ff1e89", - "a366ef49fb9e4e068c7206fa5de9a4d3", - "6824c16467c644af923b0f11ac97ddb7", - "638a3ecec14d45f399461d6b38ee81fd", - "c8a3b8286e3b4027b67758fdf25c2e35", - "9aee096b2c81404bb99d96fe337cee8f", - "248e6c55a0284f4c857aad39c860ec75", - "efba2394d02944e5927907b4bfc3ccee", - "0462deedee3648ae8aaa3ca3f2f1ef3a", - "8c4ba041dafb4b429a4f23a77efb4746", - "c0a319544cd34d56bfd4006624a97e4d", - "caea2c391cab4442a9ea8361cc7f8155", - "3a0263be005f445fbfcd5ad0c9519d91", - "0d437a74f6644ef3a5fe13a4e9d4b9e8", - "c4b1153f8e0e4f3aa43d38bd0fce633a", - "833dd5db9c8641fb8297fa3761d8aedf", - "6dfc2bf4e9424660a65a5eed380280f3", - "3fe726c210d24f67a6d07d4446a852e6", - "9efa8add1b4e4b79a542b40d4d24e613", - "dd9daff9f9f94764aeb9234e72ea6249", - "2c01b612ca0f4e259dcf092f49186446", - "db060cbdb85a4aa480a98c3784b95029", - "b62fe168ee7f4be4958f377409753e2a", - "2239b2fa9dc64255ac7dec5ce5228856", - "c564890b26554bd89876286934cc622e", - "4a8d56c5081342a1a5ea977f0e5e4903", - "1368e371c0e445e69601acb25f90e5a9", - "966b2b6f86dc49e7a579b500bef5b031", - "6925e5ccc9be44f6bc8477a974e75478", - "f8d10d68c78048e5ab8fc3caf3df6643", - "34a668472a424843bf5793ddcb449ec1", - "e2a296c76c4845fbba8070a639deddbd", - "405dd53da4354822a2a2c504a4def91d", - "cdd057a3ca3b4a18a9eabd1e267f4924", - "ccec33fd75964b30a96ce2e2a1e9815f", - "07cd5bdb960f4508b89a8367222ac494", - "a7150866ea154d5b856961cb6057b36a", - "257493a319dc4612b0f9fed2793913a9", - "146f8df9d3854b5b887412a8f61d66e0", - "986e6f7bc5264864ac8835058e949ad0", - "e30cfabacedb4a17a1f3953535d1712f", - "01c0eaf975ad4a8aa970f967e255981f", - "5e66b50859b04182b3cf604f09444c68", - "40e78669fb1c4c7dbd18601a3f2a3543", - "b8540d0b29c34a9ab6045b7276ed0ba4", - "3efb64c7f88a4db080cdb2255c8469fc", - "9e68a7867b4941109d73f86659f5335a", - "1a8107dc0d53406ba59789b98aff7f9a", - "9c79d2d8bd534bd982681128ff66155a", - "4e7174b5381546b98c0483631316f9e5", - "2f1d706566f146febfb49aa02a4fc7de", - "f5ebdd8a776542d3b7f9b476c0aa5512", - "2ae85a21b46a469cac1f9f7d482de268", - "3f59d1d5e6174d6894661efecdd33ab8", - "5e5995ebed594d9cad5621faa35f77a2", - "89cc23e9cf2847788ec3edf1ba2db8e3", - "665ef92b2901437b87fec93d13b2d4ac", - "b0035682525c443884fc1212f968b320", - "5992d99cf8bc4146983af1d8cd7b265d", - "62519dce485943e1b82fa59e251c1356", - "f6a54fc4dc2847659f547739c35a21b0", - "73e01096faf44ad9b74735371430b492", - "5acbc6e8cb7c4bb486e3db5b55aff134", - "0cc723ccf64a4b03bc743c1a564894b0", - "6fde41413a1a4839973593da2f78ced3", - "6831116bb8884a9480d7f3bfe9973d9a", - "a7e3a11dd3f94e4fb54037ae40aba206", - "3ff040d751764df1bd75e25f24d9942e", - "fb0f1fb808504ea294a729f05b1abcc1", - "0b8590fba60f40f6af3835446752cd37", - "9d32b2d2e5c64bc49f72f2226483cfa9", - "f8a4062f155d4750a30bfbdc5292ddea", - "f40afdca622848cfb153e6dc1c3f8d64", - "6d4efff530d54625a97583d6bed2520c", - "22ef9732885147e3983c606a466e0615", - "8a46241fb1a34bd1ac48677447aa2942", - "c6b666ea73f74788af455ec3eb739918", - "25c0858689be446d8403e09f4a61dbbc", - "5dc0cbd7081645b6bee0ac67da226f7f", - "182592a6f90940c488cfb93b88d11de8", - "7116032437674606b9f3966eea6457bd", - "ab3fe113b61548c0bc356114fc0c054e", - "411d3970835f4a40a916329318af5be4", - "79fd48757aac4ad0a24fefd6ea1d305b", - "ae1cb2179d3b4fcb92b168699d9bdabf", - "fad638c32e4a407b97e6b464c8b2070b", - "5503d335b4d649bea4fcb23e41279b0a", - "1c27ab84b04f4473b264c20a8429eba1", - "236f08564a1c434e9d265b9cbd601f0c", - "5c2a29cd2e5d4b1e9ace1b517f80247c", - "3be95f3a95854ba6b8f68ff4b63b6649", - "5dbf531a776c48728899c0c00ab9b8f1", - "c401a44341474936876919f32aa32ca4" - ] - }, - "id": "3teRQj_RZr2l", - "outputId": "19d120d2-89f6-49a6-8c72-52e3ca326485" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Initializing RAG Pipeline on cuda\n", - "Loading Embedding Model: sentence-transformers/all-mpnet-base-v2\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.12/dist-packages/huggingface_hub/utils/_auth.py:94: UserWarning: \n", - "The secret `HF_TOKEN` does not exist in your Colab secrets.\n", - "To authenticate with the Hugging Face Hub, create a token in your settings tab (https://huggingface.co/settings/tokens), set it as secret in your Google Colab and restart your session.\n", - "You will be able to reuse this secret in all of your notebooks.\n", - "Please note that authentication is recommended but still optional to access public models or datasets.\n", - " warnings.warn(\n" - ] - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "4a0ea4434e584a36bdafbd77fdf915d4", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "modules.json: 0%| | 0.00/349 [00:00" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# 3. Run Benchmark\n", - "rag_system.run_benchmark(sample_size=200)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 854 - }, - "id": "WyXrV0W_Zym1", - "outputId": "fe936e62-a0bb-4b42-abf5-e0cc24ecab29" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\n", - "Launching Gradio UI\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/usr/local/lib/python3.12/dist-packages/gradio/chat_interface.py:347: UserWarning: The 'tuples' format for chatbot messages is deprecated and will be removed in a future version of Gradio. Please set type='messages' instead, which uses openai-style 'role' and 'content' keys.\n", - " self.chatbot = Chatbot(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Colab notebook detected. This cell will run indefinitely so that you can see errors and logs. To turn off, set debug=False in launch().\n", - "* Running on public URL: https://c9f3b4c1ac2f5412a9.gradio.live\n", - "\n", - "This share link expires in 1 week. For free permanent hosting and GPU upgrades, run `gradio deploy` from the terminal in the working directory to deploy to Hugging Face Spaces (https://huggingface.co/spaces)\n" - ] - }, - { - "data": { - "text/html": [ - "
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.\n", - "Setting `pad_token_id` to `eos_token_id`:128001 for open-end generation.\n", - "The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.\n", - "Setting `pad_token_id` to `eos_token_id`:128001 for open-end generation.\n", - "The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.\n", - "Setting `pad_token_id` to `eos_token_id`:128001 for open-end generation.\n", - "The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results.\n", - "Setting `pad_token_id` to `eos_token_id`:128001 for open-end generation.\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Keyboard interruption in main thread... closing server.\n", - "Killing tunnel 127.0.0.1:7860 <> https://c9f3b4c1ac2f5412a9.gradio.live\n" - ] - } - ], - "source": [ - "# 4. Launch UI\n", - "rag_system.launch_gradio_ui()" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "A100", - "machine_shape": "hm", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3", - "name": "python3" - }, - "language_info": { - "name": "python" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "01c0eaf975ad4a8aa970f967e255981f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_1a8107dc0d53406ba59789b98aff7f9a", - "placeholder": "​", - "style": "IPY_MODEL_9c79d2d8bd534bd982681128ff66155a", - "value": " 2/2 [00:36<00:00, 36.46s/it]" - } - }, - "0206f0d4ca17415f8691d3125d380aff": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "035286955a1b4a83957eb326f6bb0bee": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "0407b8dd4aeb47b5b764bbff04602228": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "0462deedee3648ae8aaa3ca3f2f1ef3a": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_c4b1153f8e0e4f3aa43d38bd0fce633a", - "placeholder": "​", - "style": "IPY_MODEL_833dd5db9c8641fb8297fa3761d8aedf", - "value": " 9.08M/? [00:00<00:00, 151MB/s]" - } - }, - "07cd5bdb960f4508b89a8367222ac494": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "0b2f7c9999d84d49afa93b78e67ef2fa": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_3633835bfdc24858bf09a23ddb01a75c", - "max": 190, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_a917cc0882cf4354bd0d0ca37b0f5e62", - "value": 190 - } - }, - "0b57c423eedc41a287fefe85e1f63d7c": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "0b6e7094f94f4cb481e772bd5443a025": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_a4ba3264f68348eb9650d38fd1668a26", - "max": 116, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_421cda2009074fcd9c5d4a81109b67ce", - "value": 116 - } - }, - "0b8590fba60f40f6af3835446752cd37": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "0cc723ccf64a4b03bc743c1a564894b0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_0b8590fba60f40f6af3835446752cd37", - "placeholder": "​", - "style": "IPY_MODEL_9d32b2d2e5c64bc49f72f2226483cfa9", - "value": " 8.67G/8.67G [00:36<00:00, 518MB/s]" - } - }, - "0d437a74f6644ef3a5fe13a4e9d4b9e8": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "0e048e0c3d8045309f54e9da505dc91b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_49b1777da63c42e69be269a810493345", - "placeholder": "​", - "style": "IPY_MODEL_aa365a31e1964c5bb4298a227b23d66c", - "value": " 466k/? [00:00<00:00, 40.0MB/s]" - } - }, - "0f56e7ca761d45c5ab1345df35e81f9e": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "1081d141162244228d6d499183cf8739": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_58b03b1be4494a2ab597eee6bcf7ae54", - "IPY_MODEL_ce3396be9e2a410699c283ce39bf43d0", - "IPY_MODEL_35fdd3442b744bb18d190720cd3ee675" - ], - "layout": "IPY_MODEL_ed3439b4f7614a2e895aa6b8c68f0dd7" - } - }, - "1368e371c0e445e69601acb25f90e5a9": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "13a434a2a4624e6192ca51ae7e394667": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "1405d35083dc4c11a9ad396085c97a86": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_f73b129ccce84d9f8b09c85ea7e13a5d", - "IPY_MODEL_0b2f7c9999d84d49afa93b78e67ef2fa", - "IPY_MODEL_9951e4c31cf445049d9f14280c1b063e" - ], - "layout": "IPY_MODEL_937d0c8d494d40c98927ea97d39933a9" - } - }, - "146f8df9d3854b5b887412a8f61d66e0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_986e6f7bc5264864ac8835058e949ad0", - "IPY_MODEL_e30cfabacedb4a17a1f3953535d1712f", - "IPY_MODEL_01c0eaf975ad4a8aa970f967e255981f" - ], - "layout": "IPY_MODEL_5e66b50859b04182b3cf604f09444c68" - } - }, - "17ea216363d94349b98f267554d7aeae": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "182592a6f90940c488cfb93b88d11de8": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "18b23622d00442ca9e977b587b0c7399": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "196df619acff47b4a6ec9f9ce1710dd1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "1a8107dc0d53406ba59789b98aff7f9a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "1c27ab84b04f4473b264c20a8429eba1": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "1d4b2d25edcd4207bce2972e824e815d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_e2a80bf2c52d4a0c98731e3de6a24f22", - "placeholder": "​", - "style": "IPY_MODEL_b06f97d725d14843a55ff2442af9a97e", - "value": " 116/116 [00:00<00:00, 15.0kB/s]" - } - }, - "1d67cff04e32401da2213ad82228b20d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_fac8b597f40c43a29f69ce1e6f3a77f9", - "max": 53, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_aad85d3f773545799449c1a6080e7302", - "value": 53 - } - }, - "1d7ed0749dae4c81bad40d13c43d7629": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_439dc3fac7864363a295684ae40dbb35", - "IPY_MODEL_5fad1f69c31243bebf49515b52540989", - "IPY_MODEL_d031a5cdcf8d4a3aa030c22f9bbb423b" - ], - "layout": "IPY_MODEL_4867ed9d8d294c239f589708056ed572" - } - }, - "1ef175665a774f44896a1f938d318c3d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_7d74b694cba0407f992bce853ce926a3", - "IPY_MODEL_a15d8fd61eba4842b6be4c015cca167d", - "IPY_MODEL_0e048e0c3d8045309f54e9da505dc91b" - ], - "layout": "IPY_MODEL_bffaec4d3d254433b55a82dec359bf2c" - } - }, - "1feef1a0e35c475e964d9e598cf1b4c5": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_927fff0357d7428480d2bd936b21ae6f", - "placeholder": "​", - "style": "IPY_MODEL_8e48b73fd5c648eb9609a8aa9830d12f", - "value": " 239/239 [00:00<00:00, 26.4kB/s]" - } - }, - "2239b2fa9dc64255ac7dec5ce5228856": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "22ef9732885147e3983c606a466e0615": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_7116032437674606b9f3966eea6457bd", - "placeholder": "​", - "style": "IPY_MODEL_ab3fe113b61548c0bc356114fc0c054e", - "value": " 2/2 [00:17<00:00,  8.55s/it]" - } - }, - "236f08564a1c434e9d265b9cbd601f0c": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "248e6c55a0284f4c857aad39c860ec75": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_c0a319544cd34d56bfd4006624a97e4d", - "placeholder": "​", - "style": "IPY_MODEL_caea2c391cab4442a9ea8361cc7f8155", - "value": "tokenizer.json: " - } - }, - "257493a319dc4612b0f9fed2793913a9": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "25c0858689be446d8403e09f4a61dbbc": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "28a9db2641bf474b84eb193e3a5143be": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "29fe6407ea524db591c8f3579a4f8410": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_41f98a688b284ab4b6ffa93c1703abf4", - "placeholder": "​", - "style": "IPY_MODEL_94471425ae534a29a6d3d86a79312039", - "value": " 571/571 [00:00<00:00, 54.4kB/s]" - } - }, - "2ae85a21b46a469cac1f9f7d482de268": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_5992d99cf8bc4146983af1d8cd7b265d", - "placeholder": "​", - "style": "IPY_MODEL_62519dce485943e1b82fa59e251c1356", - "value": " 7.39G/7.39G [00:32<00:00, 57.2MB/s]" - } - }, - "2b77339b62684a4785226247f3e5e85d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_efde71ea0b7440acbb9f45fdb5678264", - "max": 571, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_196df619acff47b4a6ec9f9ce1710dd1", - "value": 571 - } - }, - "2c01b612ca0f4e259dcf092f49186446": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "2f1d706566f146febfb49aa02a4fc7de": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_5e5995ebed594d9cad5621faa35f77a2", - "placeholder": "​", - "style": "IPY_MODEL_89cc23e9cf2847788ec3edf1ba2db8e3", - "value": "model-00002-of-000002.safetensors: 100%" - } - }, - "2fba292dcd994df7bfeb0e2bdf672b5d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "31a0d00d72494794826148e1f442b848": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "34a668472a424843bf5793ddcb449ec1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_a7150866ea154d5b856961cb6057b36a", - "placeholder": "​", - "style": "IPY_MODEL_257493a319dc4612b0f9fed2793913a9", - "value": " 24.2k/? [00:00<00:00, 2.65MB/s]" - } - }, - "35fdd3442b744bb18d190720cd3ee675": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_8442e31b4f95484c84e74d7f85bf09ef", - "placeholder": "​", - "style": "IPY_MODEL_31a0d00d72494794826148e1f442b848", - "value": " 200/200 [1:15:56<00:00, 18.67s/it]" - } - }, - "3633835bfdc24858bf09a23ddb01a75c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "38b4097af230467a81ee65454f907eb5": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_ab64037914e04ee7adb2877784271790", - "placeholder": "​", - "style": "IPY_MODEL_feeb9cc484ee467b851fb576af521adf", - "value": "config_sentence_transformers.json: 100%" - } - }, - "38dcc7d3f5c142c68e7d5c5f6b86087d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_f5be3e18dfc24499ae25a2ddb496ae02", - "IPY_MODEL_cd046088208841c0be5b5c78203b00b8", - "IPY_MODEL_7d874d8f7e744ce191f0cdae0798e707" - ], - "layout": "IPY_MODEL_eb6ee46fe93a46489385ae7ee409665d" - } - }, - "3a0263be005f445fbfcd5ad0c9519d91": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "20px" - } - }, - "3bd2d7fb9da04416b2bcca81d4d15bd5": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "3be95f3a95854ba6b8f68ff4b63b6649": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "3dd72d49bb3548fca32d19eded946c2d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "3ea8fd43ac404f9bb6981c2a90ff1e89": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "3efb64c7f88a4db080cdb2255c8469fc": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "3f59d1d5e6174d6894661efecdd33ab8": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "3fe726c210d24f67a6d07d4446a852e6": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_db060cbdb85a4aa480a98c3784b95029", - "placeholder": "​", - "style": "IPY_MODEL_b62fe168ee7f4be4958f377409753e2a", - "value": "config.json: 100%" - } - }, - "3ff040d751764df1bd75e25f24d9942e": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "405dd53da4354822a2a2c504a4def91d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "40e78669fb1c4c7dbd18601a3f2a3543": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "411d3970835f4a40a916329318af5be4": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_79fd48757aac4ad0a24fefd6ea1d305b", - "IPY_MODEL_ae1cb2179d3b4fcb92b168699d9bdabf", - "IPY_MODEL_fad638c32e4a407b97e6b464c8b2070b" - ], - "layout": "IPY_MODEL_5503d335b4d649bea4fcb23e41279b0a" - } - }, - "41f98a688b284ab4b6ffa93c1703abf4": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "421cda2009074fcd9c5d4a81109b67ce": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "428e6cbcb8bc40bcb1b414667bb76b2a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "439dc3fac7864363a295684ae40dbb35": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_17ea216363d94349b98f267554d7aeae", - "placeholder": "​", - "style": "IPY_MODEL_035286955a1b4a83957eb326f6bb0bee", - "value": "model.safetensors: 100%" - } - }, - "45041ac463164980bb6437335bcf7edf": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4696a263556c417489660ea5c6240551": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "482e5a82919f4a1ab8168b0006b2d979": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4867ed9d8d294c239f589708056ed572": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "49b1777da63c42e69be269a810493345": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4a0ea4434e584a36bdafbd77fdf915d4": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_9ffe20e0f2124c14b7c12ce8daf7c4b5", - "IPY_MODEL_ea06f0dd061c4e3a96240433969ed793", - "IPY_MODEL_c527b5e31da3403fb57d9fd4b2b9b191" - ], - "layout": "IPY_MODEL_76f4bb7b9b104810985282bd16842650" - } - }, - "4a8d56c5081342a1a5ea977f0e5e4903": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4e7174b5381546b98c0483631316f9e5": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_2f1d706566f146febfb49aa02a4fc7de", - "IPY_MODEL_f5ebdd8a776542d3b7f9b476c0aa5512", - "IPY_MODEL_2ae85a21b46a469cac1f9f7d482de268" - ], - "layout": "IPY_MODEL_3f59d1d5e6174d6894661efecdd33ab8" - } - }, - "538567e28b9942808b2d7197c156fdb1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_638a3ecec14d45f399461d6b38ee81fd", - "placeholder": "​", - "style": "IPY_MODEL_c8a3b8286e3b4027b67758fdf25c2e35", - "value": " 3.07k/? [00:00<00:00, 408kB/s]" - } - }, - "54b693f1fd55413e825bcf420fa43e6a": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_0206f0d4ca17415f8691d3125d380aff", - "max": 239, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_0b57c423eedc41a287fefe85e1f63d7c", - "value": 239 - } - }, - "5503d335b4d649bea4fcb23e41279b0a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "58b03b1be4494a2ab597eee6bcf7ae54": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_0407b8dd4aeb47b5b764bbff04602228", - "placeholder": "​", - "style": "IPY_MODEL_f1387e7b89c543beb5a20cd2cc13b4cc", - "value": "Benchmarking: 100%" - } - }, - "5992d99cf8bc4146983af1d8cd7b265d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "599727a146204539892e35143c16557b": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5acbc6e8cb7c4bb486e3db5b55aff134": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_3ff040d751764df1bd75e25f24d9942e", - "max": 8667826246, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_fb0f1fb808504ea294a729f05b1abcc1", - "value": 8667826246 - } - }, - "5c2a29cd2e5d4b1e9ace1b517f80247c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5dbf531a776c48728899c0c00ab9b8f1": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5dc0cbd7081645b6bee0ac67da226f7f": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5dd09f0c273647bd89678aa2fe32f6df": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5e5995ebed594d9cad5621faa35f77a2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5e66b50859b04182b3cf604f09444c68": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5fad1f69c31243bebf49515b52540989": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_3dd72d49bb3548fca32d19eded946c2d", - "max": 437971872, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_7d3ad593a3314885a4f213e4c89da0c4", - "value": 437971872 - } - }, - "603d475c14724766b576206c2ec53a45": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "60574c59d6da4bfa92b4b93884d2b5d8": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "61ff82cf14374a28a23b941d965027eb": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "62519dce485943e1b82fa59e251c1356": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "638a3ecec14d45f399461d6b38ee81fd": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6583e5aadfca452c93f8f87ec2f97b32": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "665ef92b2901437b87fec93d13b2d4ac": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "66d531e4e851439e9b66b7a9b2d285b1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "6824c16467c644af923b0f11ac97ddb7": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "6831116bb8884a9480d7f3bfe9973d9a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6925e5ccc9be44f6bc8477a974e75478": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_405dd53da4354822a2a2c504a4def91d", - "placeholder": "​", - "style": "IPY_MODEL_cdd057a3ca3b4a18a9eabd1e267f4924", - "value": "model.safetensors.index.json: " - } - }, - "6a88eb28c414476c86ca250e5df03db6": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_2fba292dcd994df7bfeb0e2bdf672b5d", - "placeholder": "​", - "style": "IPY_MODEL_b54500bcbde249ffa53ccd68c2105b7a", - "value": "special_tokens_map.json: 100%" - } - }, - "6ac6e1708632488b9af0bbefb6542a02": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_e847e23b3dc64c3684d713e36a2d43e5", - "IPY_MODEL_bb112f11bd27452fadf6e17f6f5f5022", - "IPY_MODEL_81d8ade37ad944c2bf20d5d2bec8e94c" - ], - "layout": "IPY_MODEL_b4d7798f0f9e4d68bb10104c227c8236" - } - }, - "6c52dc5d7772478eac56b01ea54c8e19": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_6a88eb28c414476c86ca250e5df03db6", - "IPY_MODEL_54b693f1fd55413e825bcf420fa43e6a", - "IPY_MODEL_1feef1a0e35c475e964d9e598cf1b4c5" - ], - "layout": "IPY_MODEL_e5a8f5ebb26843cf92150b2f817b2dad" - } - }, - "6d4efff530d54625a97583d6bed2520c": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_5dc0cbd7081645b6bee0ac67da226f7f", - "max": 2, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_182592a6f90940c488cfb93b88d11de8", - "value": 2 - } - }, - "6dfc2bf4e9424660a65a5eed380280f3": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_3fe726c210d24f67a6d07d4446a852e6", - "IPY_MODEL_9efa8add1b4e4b79a542b40d4d24e613", - "IPY_MODEL_dd9daff9f9f94764aeb9234e72ea6249" - ], - "layout": "IPY_MODEL_2c01b612ca0f4e259dcf092f49186446" - } - }, - "6fde41413a1a4839973593da2f78ced3": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "7116032437674606b9f3966eea6457bd": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "7254e9e3c0a24d0eaa7908b8018e3f83": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "73e01096faf44ad9b74735371430b492": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_6831116bb8884a9480d7f3bfe9973d9a", - "placeholder": "​", - "style": "IPY_MODEL_a7e3a11dd3f94e4fb54037ae40aba206", - "value": "model-00001-of-000002.safetensors: 100%" - } - }, - "74af2c01c93847bdb011d0368acf44f7": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "76f4bb7b9b104810985282bd16842650": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "77232511fed041feaeefc974aceee07f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_d0c8f23c7434465ab284741201956266", - "placeholder": "​", - "style": "IPY_MODEL_13a434a2a4624e6192ca51ae7e394667", - "value": "sentence_bert_config.json: 100%" - } - }, - "774add6aa42e4884809cdf435ce39068": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_a0d0c70f3f11437f9c99619436e7f077", - "placeholder": "​", - "style": "IPY_MODEL_f6821378164c4c2eb9458d1458ca58e1", - "value": " 53.0/53.0 [00:00<00:00, 7.44kB/s]" - } - }, - "79fd48757aac4ad0a24fefd6ea1d305b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_1c27ab84b04f4473b264c20a8429eba1", - "placeholder": "​", - "style": "IPY_MODEL_236f08564a1c434e9d265b9cbd601f0c", - "value": "generation_config.json: 100%" - } - }, - "7d3ad593a3314885a4f213e4c89da0c4": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "7d74b694cba0407f992bce853ce926a3": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_d5bc725f098447a6920aee7ff3e00a97", - "placeholder": "​", - "style": "IPY_MODEL_6583e5aadfca452c93f8f87ec2f97b32", - "value": "tokenizer.json: " - } - }, - "7d874d8f7e744ce191f0cdae0798e707": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_89f29ab2c0f84bdf812d34265d0319d2", - "placeholder": "​", - "style": "IPY_MODEL_889fb0ea3dc3479ba47569301abf1095", - "value": " 232k/? [00:00<00:00, 16.1MB/s]" - } - }, - "7f5af4598bb24c0b992424624f6dfb6d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "81d8ade37ad944c2bf20d5d2bec8e94c": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_dfdac9cdd0c546429604490fdecb22a7", - "placeholder": "​", - "style": "IPY_MODEL_db2458bb9a0a49d8b087a2b1ebd0864d", - "value": " 363/363 [00:00<00:00, 45.8kB/s]" - } - }, - "833dd5db9c8641fb8297fa3761d8aedf": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "8442e31b4f95484c84e74d7f85bf09ef": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "871272ea241147bb87feeaaf793e1d9e": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "889fb0ea3dc3479ba47569301abf1095": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "890f5d59c2944577921d3f31529aeaf9": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "89cc23e9cf2847788ec3edf1ba2db8e3": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "89f29ab2c0f84bdf812d34265d0319d2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8a46241fb1a34bd1ac48677447aa2942": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8ac89f580fb8427697cac9fad7ee1693": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "8c4ba041dafb4b429a4f23a77efb4746": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8e48b73fd5c648eb9609a8aa9830d12f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "927fff0357d7428480d2bd936b21ae6f": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "92a456546b7b493d8321c24a62a6b551": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_c9d5bcb10e674784a570b625a59f12cf", - "placeholder": "​", - "style": "IPY_MODEL_e88678951fc2460ea8241edba800a2c0", - "value": "README.md: " - } - }, - "937d0c8d494d40c98927ea97d39933a9": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "94471425ae534a29a6d3d86a79312039": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "94a70ea4f9f94d7dbfbda2f820bf7016": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "20px" - } - }, - "966b2b6f86dc49e7a579b500bef5b031": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_6925e5ccc9be44f6bc8477a974e75478", - "IPY_MODEL_f8d10d68c78048e5ab8fc3caf3df6643", - "IPY_MODEL_34a668472a424843bf5793ddcb449ec1" - ], - "layout": "IPY_MODEL_e2a296c76c4845fbba8070a639deddbd" - } - }, - "986e6f7bc5264864ac8835058e949ad0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_40e78669fb1c4c7dbd18601a3f2a3543", - "placeholder": "​", - "style": "IPY_MODEL_b8540d0b29c34a9ab6045b7276ed0ba4", - "value": "Fetching 2 files: 100%" - } - }, - "9951e4c31cf445049d9f14280c1b063e": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_a6be0858867147f6b1c64b626d583a2c", - "placeholder": "​", - "style": "IPY_MODEL_890f5d59c2944577921d3f31529aeaf9", - "value": " 190/190 [00:00<00:00, 24.8kB/s]" - } - }, - "9a02b190859e49efaeb6d51605eee3b2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "9a80ee6fd91a401dac1652acd15d44f5": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_7f5af4598bb24c0b992424624f6dfb6d", - "placeholder": "​", - "style": "IPY_MODEL_74af2c01c93847bdb011d0368acf44f7", - "value": " 11.6k/? [00:00<00:00, 1.38MB/s]" - } - }, - "9aee096b2c81404bb99d96fe337cee8f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_248e6c55a0284f4c857aad39c860ec75", - "IPY_MODEL_efba2394d02944e5927907b4bfc3ccee", - "IPY_MODEL_0462deedee3648ae8aaa3ca3f2f1ef3a" - ], - "layout": "IPY_MODEL_8c4ba041dafb4b429a4f23a77efb4746" - } - }, - "9c79d2d8bd534bd982681128ff66155a": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "9d32b2d2e5c64bc49f72f2226483cfa9": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "9e68a7867b4941109d73f86659f5335a": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "9eddcbf6d4cc4f7ca5ba92e7d2dfd6db": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_61ff82cf14374a28a23b941d965027eb", - "placeholder": "​", - "style": "IPY_MODEL_fa92d691af6946568131ab68afffbf00", - "value": "config.json: 100%" - } - }, - "9efa8add1b4e4b79a542b40d4d24e613": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_2239b2fa9dc64255ac7dec5ce5228856", - "max": 826, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_c564890b26554bd89876286934cc622e", - "value": 826 - } - }, - "9ffe20e0f2124c14b7c12ce8daf7c4b5": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_afa27f83547045aca2c4e8e92fe49878", - "placeholder": "​", - "style": "IPY_MODEL_871272ea241147bb87feeaaf793e1d9e", - "value": "modules.json: 100%" - } - }, - "a0d0c70f3f11437f9c99619436e7f077": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "a10211fa0818443f89043174a09bee1a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "a15d8fd61eba4842b6be4c015cca167d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_94a70ea4f9f94d7dbfbda2f820bf7016", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_66d531e4e851439e9b66b7a9b2d285b1", - "value": 1 - } - }, - "a366ef49fb9e4e068c7206fa5de9a4d3": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "20px" - } - }, - "a423c1bf1c4f47dcb3ca275f27f4b23b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "a4ba3264f68348eb9650d38fd1668a26": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "a6be0858867147f6b1c64b626d583a2c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "a7150866ea154d5b856961cb6057b36a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "a7e3a11dd3f94e4fb54037ae40aba206": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "a917cc0882cf4354bd0d0ca37b0f5e62": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "a9a353632bc6456da4c6a6210cf2e7ae": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "aa365a31e1964c5bb4298a227b23d66c": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "aad85d3f773545799449c1a6080e7302": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "ab3fe113b61548c0bc356114fc0c054e": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "ab64037914e04ee7adb2877784271790": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "ae1cb2179d3b4fcb92b168699d9bdabf": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_5c2a29cd2e5d4b1e9ace1b517f80247c", - "max": 181, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_3be95f3a95854ba6b8f68ff4b63b6649", - "value": 181 - } - }, - "afa27f83547045aca2c4e8e92fe49878": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b0035682525c443884fc1212f968b320": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "b06f97d725d14843a55ff2442af9a97e": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "b19cf7b7c453462ea0afb26e12f404f2": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "b316a24c01d341ca878ebc93bb6db8ad": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_77232511fed041feaeefc974aceee07f", - "IPY_MODEL_1d67cff04e32401da2213ad82228b20d", - "IPY_MODEL_774add6aa42e4884809cdf435ce39068" - ], - "layout": "IPY_MODEL_0f56e7ca761d45c5ab1345df35e81f9e" - } - }, - "b38d78293de747829603e05788a7482c": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_a366ef49fb9e4e068c7206fa5de9a4d3", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_6824c16467c644af923b0f11ac97ddb7", - "value": 1 - } - }, - "b4d7798f0f9e4d68bb10104c227c8236": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b54500bcbde249ffa53ccd68c2105b7a": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "b62fe168ee7f4be4958f377409753e2a": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "b8540d0b29c34a9ab6045b7276ed0ba4": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "bb112f11bd27452fadf6e17f6f5f5022": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_f41affd88fb944ed8ac6cc632d2d7edc", - "max": 363, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_a423c1bf1c4f47dcb3ca275f27f4b23b", - "value": 363 - } - }, - "bb412ac42ad64c30934b9594bfad392b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_cac61cb1e1d7406e9dc97aa868525dfe", - "IPY_MODEL_b38d78293de747829603e05788a7482c", - "IPY_MODEL_538567e28b9942808b2d7197c156fdb1" - ], - "layout": "IPY_MODEL_9a02b190859e49efaeb6d51605eee3b2" - } - }, - "bdf14d4a82c2425d8ec0c9a2acc63055": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "20px" - } - }, - "bffaec4d3d254433b55a82dec359bf2c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c0765fdc3eb143d6880ff4e00141ce14": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "c0a319544cd34d56bfd4006624a97e4d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c2536ef6eb4341e3b54cf473ae498ca9": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_92a456546b7b493d8321c24a62a6b551", - "IPY_MODEL_ea161cf843184232bb844545176cffb0", - "IPY_MODEL_9a80ee6fd91a401dac1652acd15d44f5" - ], - "layout": "IPY_MODEL_3bd2d7fb9da04416b2bcca81d4d15bd5" - } - }, - "c2c50ced649b428097bffe04f97a137f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_38b4097af230467a81ee65454f907eb5", - "IPY_MODEL_0b6e7094f94f4cb481e772bd5443a025", - "IPY_MODEL_1d4b2d25edcd4207bce2972e824e815d" - ], - "layout": "IPY_MODEL_60574c59d6da4bfa92b4b93884d2b5d8" - } - }, - "c401a44341474936876919f32aa32ca4": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "c4b1153f8e0e4f3aa43d38bd0fce633a": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c527b5e31da3403fb57d9fd4b2b9b191": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_5dd09f0c273647bd89678aa2fe32f6df", - "placeholder": "​", - "style": "IPY_MODEL_e4ea1f9ea6a343ef995c2eface0efd13", - "value": " 349/349 [00:00<00:00, 31.2kB/s]" - } - }, - "c564890b26554bd89876286934cc622e": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "c6b666ea73f74788af455ec3eb739918": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c8a3b8286e3b4027b67758fdf25c2e35": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "c9d5bcb10e674784a570b625a59f12cf": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "cac61cb1e1d7406e9dc97aa868525dfe": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_428e6cbcb8bc40bcb1b414667bb76b2a", - "placeholder": "​", - "style": "IPY_MODEL_3ea8fd43ac404f9bb6981c2a90ff1e89", - "value": "tokenizer_config.json: " - } - }, - "caea2c391cab4442a9ea8361cc7f8155": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "ccec33fd75964b30a96ce2e2a1e9815f": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "20px" - } - }, - "cd046088208841c0be5b5c78203b00b8": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_d5f205776c3f42ccaa9542f715b85748", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_b19cf7b7c453462ea0afb26e12f404f2", - "value": 1 - } - }, - "cdd057a3ca3b4a18a9eabd1e267f4924": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "ce3396be9e2a410699c283ce39bf43d0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_fbe77566bd3448a7b83f76591c6de21f", - "max": 200, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_a9a353632bc6456da4c6a6210cf2e7ae", - "value": 200 - } - }, - "d031a5cdcf8d4a3aa030c22f9bbb423b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_7254e9e3c0a24d0eaa7908b8018e3f83", - "placeholder": "​", - "style": "IPY_MODEL_28a9db2641bf474b84eb193e3a5143be", - "value": " 438M/438M [00:02<00:00, 415MB/s]" - } - }, - "d0c8f23c7434465ab284741201956266": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "d5bc725f098447a6920aee7ff3e00a97": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "d5f205776c3f42ccaa9542f715b85748": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "20px" - } - }, - "db060cbdb85a4aa480a98c3784b95029": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "db2458bb9a0a49d8b087a2b1ebd0864d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "dd9daff9f9f94764aeb9234e72ea6249": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_4a8d56c5081342a1a5ea977f0e5e4903", - "placeholder": "​", - "style": "IPY_MODEL_1368e371c0e445e69601acb25f90e5a9", - "value": " 826/826 [00:00<00:00, 78.4kB/s]" - } - }, - "dfdac9cdd0c546429604490fdecb22a7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e07c41df4bf447acae4a58ae3b0c0d73": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e2a296c76c4845fbba8070a639deddbd": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e2a80bf2c52d4a0c98731e3de6a24f22": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e30cfabacedb4a17a1f3953535d1712f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_3efb64c7f88a4db080cdb2255c8469fc", - "max": 2, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_9e68a7867b4941109d73f86659f5335a", - "value": 2 - } - }, - "e34578ea4acd4623ba900470212faa90": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_9eddcbf6d4cc4f7ca5ba92e7d2dfd6db", - "IPY_MODEL_2b77339b62684a4785226247f3e5e85d", - "IPY_MODEL_29fe6407ea524db591c8f3579a4f8410" - ], - "layout": "IPY_MODEL_e07c41df4bf447acae4a58ae3b0c0d73" - } - }, - "e4ea1f9ea6a343ef995c2eface0efd13": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "e5a8f5ebb26843cf92150b2f817b2dad": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e847e23b3dc64c3684d713e36a2d43e5": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_45041ac463164980bb6437335bcf7edf", - "placeholder": "​", - "style": "IPY_MODEL_4696a263556c417489660ea5c6240551", - "value": "tokenizer_config.json: 100%" - } - }, - "e88678951fc2460ea8241edba800a2c0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "ea06f0dd061c4e3a96240433969ed793": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_482e5a82919f4a1ab8168b0006b2d979", - "max": 349, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_8ac89f580fb8427697cac9fad7ee1693", - "value": 349 - } - }, - "ea161cf843184232bb844545176cffb0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_bdf14d4a82c2425d8ec0c9a2acc63055", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_603d475c14724766b576206c2ec53a45", - "value": 1 - } - }, - "eb6ee46fe93a46489385ae7ee409665d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "ed3439b4f7614a2e895aa6b8c68f0dd7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "efba2394d02944e5927907b4bfc3ccee": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_3a0263be005f445fbfcd5ad0c9519d91", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_0d437a74f6644ef3a5fe13a4e9d4b9e8", - "value": 1 - } - }, - "efde71ea0b7440acbb9f45fdb5678264": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "f1387e7b89c543beb5a20cd2cc13b4cc": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "f40afdca622848cfb153e6dc1c3f8d64": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_c6b666ea73f74788af455ec3eb739918", - "placeholder": "​", - "style": "IPY_MODEL_25c0858689be446d8403e09f4a61dbbc", - "value": "Loading checkpoint shards: 100%" - } - }, - "f41affd88fb944ed8ac6cc632d2d7edc": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "f5be3e18dfc24499ae25a2ddb496ae02": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_a10211fa0818443f89043174a09bee1a", - "placeholder": "​", - "style": "IPY_MODEL_c0765fdc3eb143d6880ff4e00141ce14", - "value": "vocab.txt: " - } - }, - "f5ebdd8a776542d3b7f9b476c0aa5512": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_665ef92b2901437b87fec93d13b2d4ac", - "max": 7392730108, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_b0035682525c443884fc1212f968b320", - "value": 7392730108 - } - }, - "f6821378164c4c2eb9458d1458ca58e1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "f6a54fc4dc2847659f547739c35a21b0": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_73e01096faf44ad9b74735371430b492", - "IPY_MODEL_5acbc6e8cb7c4bb486e3db5b55aff134", - "IPY_MODEL_0cc723ccf64a4b03bc743c1a564894b0" - ], - "layout": "IPY_MODEL_6fde41413a1a4839973593da2f78ced3" - } - }, - "f73b129ccce84d9f8b09c85ea7e13a5d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_599727a146204539892e35143c16557b", - "placeholder": "​", - "style": "IPY_MODEL_18b23622d00442ca9e977b587b0c7399", - "value": "config.json: 100%" - } - }, - "f8a4062f155d4750a30bfbdc5292ddea": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_f40afdca622848cfb153e6dc1c3f8d64", - "IPY_MODEL_6d4efff530d54625a97583d6bed2520c", - "IPY_MODEL_22ef9732885147e3983c606a466e0615" - ], - "layout": "IPY_MODEL_8a46241fb1a34bd1ac48677447aa2942" - } - }, - "f8d10d68c78048e5ab8fc3caf3df6643": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_ccec33fd75964b30a96ce2e2a1e9815f", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_07cd5bdb960f4508b89a8367222ac494", - "value": 1 - } - }, - "fa92d691af6946568131ab68afffbf00": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - }, - "fac8b597f40c43a29f69ce1e6f3a77f9": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "fad638c32e4a407b97e6b464c8b2070b": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "HTMLView", - "description": "", - "description_tooltip": null, - "layout": "IPY_MODEL_5dbf531a776c48728899c0c00ab9b8f1", - "placeholder": "​", - "style": "IPY_MODEL_c401a44341474936876919f32aa32ca4", - "value": " 181/181 [00:00<00:00, 22.2kB/s]" - } - }, - "fb0f1fb808504ea294a729f05b1abcc1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "fbe77566bd3448a7b83f76591c6de21f": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "feeb9cc484ee467b851fb576af521adf": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "" - } - } - } - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/README.md b/README.md new file mode 100644 index 0000000..97b2a40 --- /dev/null +++ b/README.md @@ -0,0 +1,281 @@ +
+ +# 🧬 Knowledge Graph Question Answering + +### GraphRAG vs PlainRAG on PubMedQA — a fair, leakage-free, statistically-tested ablation + +[![CI](https://github.com/vardhjain/Knowledge_Graph_Question_Answering/actions/workflows/ci.yml/badge.svg)](https://github.com/vardhjain/Knowledge_Graph_Question_Answering/actions/workflows/ci.yml) +[![codecov](https://codecov.io/gh/vardhjain/Knowledge_Graph_Question_Answering/graph/badge.svg)](https://codecov.io/gh/vardhjain/Knowledge_Graph_Question_Answering) +[![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/) +[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE) +[![Lint: ruff](https://img.shields.io/badge/lint-ruff-261230.svg)](https://github.com/astral-sh/ruff) +[![Live Demo](https://img.shields.io/badge/Streamlit-Live%20Demo-FF4B4B?logo=streamlit&logoColor=white)](https://vardhjain-knowledge-graph-question-answerin-appdashboard-hkwi57.streamlit.app) +[![Docs](https://img.shields.io/badge/docs-online-1f6feb)](https://vardhjain.github.io/Knowledge_Graph_Question_Answering/) + +[**▶ Live demo**](https://vardhjain-knowledge-graph-question-answerin-appdashboard-hkwi57.streamlit.app)  ·  [**Results**](#results)  ·  [**Why it's fair**](#why-the-original-comparison-was-unfair-and-what-changed)  ·  [**Setup**](#setup) + +Co-built with [Akash Raghavendra](https://github.com/Akash-Raghavendra). + +
+ +A controlled study of **what a knowledge graph actually contributes** to +retrieval-augmented question answering on biomedical literature +([PubMedQA](https://pubmedqa.github.io/)). + +Most "GraphRAG beats RAG" demos are confounded: the graph pipeline quietly also +gets a reranker, a different corpus, or — worst of all — leaks the answer into +the prompt. This repo throws those out and runs a **4-arm ablation** where every +layer is held constant and the *only* thing that changes is how much graph +structure the retriever uses. + +``` +plain ─► plain_rr ─► graph ─► graph_concepts + (RAG) (+rerank) (+parent (+MeSH concept + expansion) hop) +``` + +Same corpus, same chunking, same embedder, same reranker, same prompt, same LLM, +same seeded sample, same top-k. The accuracy delta between adjacent arms is +attributable to exactly one component, and we report a **paired McNemar test** so +you can tell a real effect from noise. + +![Architecture and 4-arm ablation](assets/architecture.svg) + +--- + +## Hosted agent + +Beyond the research ablation above, [`backend/`](backend/) (FastAPI) and +[`frontend/`](frontend/) (Next.js) turn the winning `graph` arm into a live +chat agent with a reasoning-path visualization and a `/benchmark` dashboard. +See [`backend/README.md`](backend/README.md) and +[`frontend/README.md`](frontend/README.md) for setup and deployment. + +**Scope limit:** the hosted demo's graph (`graph_id="demo"`) runs on Neo4j +AuraDB Free, seeded with only the PubMedQA **labeled split (1,000 papers)** +via [`scripts/ingest_neo4j.py`](scripts/ingest_neo4j.py) -- not the full +~62k-paper corpus the benchmark above was run over. That's a deliberate, +documented tradeoff to keep the hosted demo on a genuinely free-forever tier; +it doesn't affect the numbers in [RESULTS.md](RESULTS.md), which come from +the untouched ArangoDB-based benchmark pipeline. + +--- + +## Why the original comparison was unfair (and what changed) + +This started from a working but confounded notebook comparison. The audit found +six issues; all are fixed in this revamp: + +| # | Flaw (before) | Fix (now) | +| --- | --- | --- | +| 1 | GraphRAG had a cross-encoder reranker; PlainRAG was raw FAISS top-3 | The reranker is its **own arm** (`plain_rr`). The graph arms build *on top of* `plain_rr`, so the rerank is controlled for, not a hidden advantage | +| 2 | The two pipelines indexed **different corpora** | All arms search one shared `ChunkStore` (labeled + unlabeled, identical chunks) | +| 3 | Different granularity (whole abstracts vs per-section chunks) | Identical per-section chunking for every arm | +| 4 | **Label leakage**: papers stored `title = question` and `final_decision`, injected into the prompt as `=== STUDY: {title} ===` | Ingestion stores **no** question-derived title and **no** `final_decision`; graph context uses generic `=== STUDY n ===` labels with abstracts only. A unit test asserts the question never appears in the context | +| 5 | `Concepts` (MeSH) and `MENTIONS` edges were built but **never used** | The `graph_concepts` arm hops across shared MeSH concepts to pull in related papers | +| 6 | `NameError` in the graph fallback; first-100 samples, no seed, no significance test | Fixed fallback; seeded random sample (default n=200); paired McNemar test | + +**What we expected vs. what we found.** Going in, we expected concept-hop +expansion to be where the graph shines and a plain parent-expansion gain to be +modest. The data said the opposite: the decisive, statistically significant win +came from **parent-document expansion**, while concept-hop did not help on this +single-abstract dataset. We report that honestly rather than bury it — see +[Results](#results). + +--- + +## 🗂️ Repository layout + +``` +src/kgqa/ importable package — single source of truth + config.py all shared constants (models, top-k, seed, n) + prompts.py benchmark/chat prompts (identical across arms) + llm.py Ollama client + data.py seeded sampling + canonical chunking + evaluation.py answer extraction, metrics, McNemar test + models.py encoder / reranker / ArangoDB loaders + retrieval/ + base.py ChunkStore + BaseRetriever (encode→rerank→select) + plain.py plain, plain_rr arms + graph.py graph, graph_concepts arms +scripts/ + ingest.py build the leakage-free graph in ArangoDB (run once) + run_benchmark.py run one arm: --arm {plain,plain_rr,graph,graph_concepts} + compare.py summary table + McNemar + ablation figure +notebooks/ + 01_ingest.ipynb thin Colab wrapper for ingestion + 02_benchmark.ipynb thin Colab wrapper for all arms + comparison +tests/ pytest suite (runs on CPU, no Ollama/ArangoDB needed) +docs/ project report (PDF) and slides (PPTX) +``` + +## 🧰 Stack + +- **Dataset:** PubMedQA (`pqa_labeled` for evaluation, `pqa_unlabeled` for corpus) +- **Embeddings:** `all-MiniLM-L6-v2` (384-dim) +- **Reranker:** `cross-encoder/ms-marco-MiniLM-L-6-v2` +- **Graph DB:** ArangoDB — any instance (local Docker or [ArangoDB Oasis](https://cloud.arangodb.com)); schema: Papers / Chunks / Concepts; HAS_CONTEXT / MENTIONS +- **LLM:** `deepseek-r1:8b` via [Ollama](https://ollama.com) + +--- + +## Setup + +```bash +pip install -r requirements.txt # add -r requirements-dev.txt for tests +cp .env.example .env # then set ARANGO_PASS (and ARANGO_HOST if remote) +``` + +All connection settings are read from the environment (or a local `.env`, or +Colab Secrets) — `ARANGO_HOST`, `ARANGO_USER`, `ARANGO_PASS`, `ARANGO_DB`. +**Nothing is hardcoded**; the default host is `http://localhost:8529`. + +You need two services: an **ArangoDB** instance and a running **Ollama**. + +```bash +# ArangoDB — option A: local, via the bundled compose file +docker compose up -d # ArangoDB at localhost:8529 (root / devpassword) +export ARANGO_PASS=devpassword # PowerShell: $env:ARANGO_PASS="devpassword" + +# ArangoDB — option B: a cloud deployment (e.g. ArangoDB Oasis free tier) +# export ARANGO_HOST=https://.arangodb.cloud:8529 +# export ARANGO_PASS= + +# Ollama (LLM) +ollama serve & ollama pull deepseek-r1:8b +``` + +## ⚙️ Running the benchmark + +```bash +python scripts/ingest.py # build the graph once +make benchmark # all four arms (n=200) +# or run arms individually: +# python scripts/run_benchmark.py --arm plain --n 200 (plain_rr / graph / graph_concepts) +python scripts/compare.py # table + McNemar + figure -> results/ +``` + +The benchmark is LLM-bound and benefits from a GPU. If you don't have one, +**Google Colab** works well: run [`notebooks/01_ingest.ipynb`](notebooks/01_ingest.ipynb) +once, then [`notebooks/02_benchmark.ipynb`](notebooks/02_benchmark.ipynb) (set +`ARANGO_HOST` / `ARANGO_PASS` in Colab Secrets). + +--- + +## ▶ Live demo + +**[▶ Open the results dashboard](https://vardhjain-knowledge-graph-question-answerin-appdashboard-hkwi57.streamlit.app)** — an +interactive Streamlit dashboard of the 4-arm ablation: headline accuracy, the +paired McNemar significance tests, latency, and (when raw results are present) +per-class confusion matrices. No setup, no login — it reads the committed +`results/` artifacts, so it needs no LLM, database, or GPU. + +[![Results dashboard](assets/dashboard.png)](https://vardhjain-knowledge-graph-question-answerin-appdashboard-hkwi57.streamlit.app) + +Run the dashboard locally: + +```bash +pip install -r app/requirements.txt +make dashboard # or: streamlit run app/dashboard.py +``` + +**Chat demo** — a Gradio assistant that answers from the graph and cites PubMed +IDs (the winning `graph` arm). It's a *live* pipeline that needs a reachable +ArangoDB + Ollama, so run it yourself (best on a GPU Colab): + +```bash +pip install -r requirements-app.txt +python app/chat_app.py --share # public Gradio link +``` + +![GraphRAG chat interface](assets/chat.png) + +A hosted always-on chat isn't provided on purpose — it would need a paid GPU and +a persistent ArangoDB. See [app/README.md](app/README.md) for details. + +--- + +## Results + +Seeded random sample of **n = 200** PubMedQA `pqa_labeled` questions (seed 42, +identical across arms), `deepseek-r1:8b` via Ollama on an A100. Regenerate with +`scripts/compare.py` (writes `results/summary.md` and `results/ablation.png`). +Canonical numbers (and the honest write-up) live in [RESULTS.md](RESULTS.md). + +| Arm | Accuracy | Macro F1 | Avg latency | Adds | +| --- | --- | --- | --- | --- | +| `plain` | 30.0% | 29.7% | 6.4 s | baseline chunk RAG | +| `plain_rr` | 37.0% | 35.2% | 6.6 s | + cross-encoder reranker | +| **`graph`** | **59.5%** | **50.5%** | 7.5 s | + parent-paper expansion | +| `graph_concepts` | 57.5% | 50.0% | 40.8 s | + MeSH concept hop | + +**Paired McNemar tests** — each contrast isolates one component on the same 200 questions: + +| Contrast | Δ accuracy | gains / losses | p | significant? | +| --- | --- | --- | --- | --- | +| `plain → plain_rr` (reranker) | +7.0 pp | 35 / 21 | 0.081 | no | +| `plain_rr → graph` (parent expansion) | **+22.5 pp** | 71 / 26 | **<0.0001** | **yes** | +| `graph → graph_concepts` (concept hop) | −2.0 pp | 26 / 30 | 0.69 | no | + +![4-arm ablation on PubMedQA](results/ablation.png) + +### What the ablation shows + +1. **The graph's decisive win is parent-document expansion** (+22.5 pp, + p < 0.0001). Retrieving at the fine-grained chunk level but feeding the LLM the + *full reconstructed abstract* (chunk → paper → all sections, via `HAS_CONTEXT`) + is what moves the needle — for only ~1 s over `plain_rr`. With the label + leakage fixed, this is a clean, legitimate graph advantage. +2. **Single-fragment retrieval is not enough for PubMedQA.** `plain` and + `plain_rr` land *below* the majority-class baseline (PubMedQA is ≈55% "yes"); a + lone ~250-character section rarely contains enough to judge the question. + Context sufficiency — which the graph supplies — is the dominant factor, and + `graph` is the only arm that clears the trivial baseline. +3. **The reranker helps modestly but not significantly** at this sample size + (+7 pp, p = 0.08). +4. **Concept-hop expansion does not help here** (−2 pp, p = 0.69) and costs ~5× + the latency. An honest — and expected — negative result: on single-abstract QA, + papers pulled in via shared MeSH terms act mostly as distractors. The graph + helps by *deepening* context (the full document), not by *broadening* it + (related documents). + +The macro-F1 / accuracy gap on the graph arms reflects weak recall on the rare +`maybe` class (~11% of the data) — a dataset property, not a retrieval one. + +--- + +## 🧪 Development + +```bash +make install-dev # deps for tests + lint +make test # pytest — 17 tests, all CPU, no external services +make lint # ruff +make help # all shortcuts (ingest, benchmark, compare, ...) +``` + +CI runs ruff + pytest on every push/PR (Python 3.10 and 3.11). Unit tests inject +fakes for the encoder, reranker, and ArangoDB, so the heavy ML dependencies are +never needed just to verify the logic. Optionally `pre-commit install` to run +ruff automatically on each commit. + +## 📖 Documentation + +- **[Project site](https://vardhjain.github.io/Knowledge_Graph_Question_Answering/)** — the story and results at a glance (GitHub Pages) +- **[Project report (PDF)](docs/Project_Report.pdf)** and **[slides](docs/Graph_RAG_PPT.pptx)** +- **[Architecture diagram](assets/architecture.svg)** · **[CHANGELOG](CHANGELOG.md)** · **[CONTRIBUTING](CONTRIBUTING.md)** + +## 🤝 Contributing + +Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) for setup, the +project layout, and the fairness ground rules. Changes are tracked in +[CHANGELOG.md](CHANGELOG.md); please be kind and follow the +[Code of Conduct](CODE_OF_CONDUCT.md). + +## 📚 Citing + +If this project or its findings are useful in your work, please cite it — see +[CITATION.cff](CITATION.cff) (GitHub renders a "Cite this repository" button). + +## 📄 License + +[MIT](LICENSE). diff --git a/RESULTS.md b/RESULTS.md new file mode 100644 index 0000000..803f5a7 --- /dev/null +++ b/RESULTS.md @@ -0,0 +1,46 @@ +# Results + +Single source of truth for every number quoted in the README, the resume, and +the (future) `/benchmark` dashboard. Generated by `scripts/compare.py` from +`results/summary.json` — n=200, seed=42, model `deepseek-r1:8b`, dataset +PubMedQA (`pqa_labeled`). + +## Per-arm + +| Arm | Accuracy | Macro F1 | Avg latency (s) | n | Adds | +| --- | --- | --- | --- | --- | --- | +| plain | 30.00% | 29.69% | 6.4 | 200 | baseline chunk RAG | +| plain_rr | 37.00% | 35.21% | 6.6 | 200 | + cross-encoder reranker | +| **graph** | **59.50%** | **50.51%** | 7.5 | 200 | + parent-paper expansion | +| graph_concepts | 57.50% | 49.97% | 40.8 | 200 | + MeSH concept hop | + +## Significance (paired McNemar) + +| Contrast | Δacc (pp) | gains | losses | p | significant? | +| --- | --- | --- | --- | --- | --- | +| plain → plain_rr (reranker effect) | +7.00 | 35 | 21 | 0.0814 | no | +| **plain_rr → graph (parent-expansion effect)** | **+22.50** | 71 | 26 | **<0.0001** | **yes** | +| graph → graph_concepts (concept-hop effect) | -2.00 | 26 | 30 | 0.6889 | no | + +## Honest summary + +The graph arm's win is not "adding a knowledge graph" in the abstract — it's +one specific mechanism: **parent-document expansion**. Once a chunk is +retrieved, walking `HAS_CONTEXT` back to the parent paper and handing the LLM +the full abstract instead of an isolated chunk lifts accuracy by **+22.5 +percentage points** over the reranked baseline, and a paired McNemar test on +the same 200 questions confirms this is not noise (p < 0.0001). The reranker +alone (+7.0pp) does not clear significance at this sample size. The further +MeSH concept-hop (`graph_concepts`) does not help — it's not significantly +different from `graph` (p = 0.69) and costs ~5x the latency (40.8s vs 7.5s), +because that latency is dominated by the extra graph traversal and abstract +reconstruction per hop, not by the LLM call itself. So: the graph helps +because it recovers context that flat chunking throws away, not because more +graph traversal is inherently better. Ship `graph`, not `graph_concepts`. + +**Correct resume framing:** "+22.5pp accuracy lift from parent-document graph +expansion over a reranked RAG baseline, McNemar p<0.0001" — not "GraphRAG gets +65% accuracy" and not "graph retrieval is 2x faster" (it is ~14% *slower*, +7.5s vs 6.6s, and that's an honest, disclosed tradeoff for the accuracy gain). + +Full methodology and the 4-arm ablation diagram: [README.md](README.md#results). diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..714ea7d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,23 @@ +# Security Policy + +This is a research project, but a few things are worth handling carefully. + +## Reporting a vulnerability + +If you find a security issue (for example, an accidental credential commit or a +dependency vulnerability), please **do not open a public issue**. Instead, use +GitHub's [private vulnerability reporting](https://github.com/vardhjain/Knowledge_Graph_Question_Answering/security/advisories/new) +or email the maintainer. You can expect an acknowledgement within a few days. + +## Secrets + +- Never commit real credentials. ArangoDB and LLM settings are read from the + environment (or a local `.env`, which is git-ignored). Use `.env.example` as a + template, and Colab **Secrets** for notebook runs. +- If a secret is ever committed, rotate it immediately — removing it from the + latest commit is not enough, as it remains in git history. + +## Supported versions + +The latest release on `main` is supported. This project pins minimum dependency +versions in `requirements.txt`; run `pip list --outdated` periodically. diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..a70c7d7 --- /dev/null +++ b/app/README.md @@ -0,0 +1,52 @@ +# Apps + +Two optional front-ends. Install their deps with `pip install -r requirements-app.txt`. + +## `chat_app.py` — live GraphRAG chat (Gradio) + +An interactive assistant over the winning `graph` arm: it retrieves from the +knowledge graph, answers with `deepseek-r1:8b`, and cites the source PubMed IDs. + +```bash +python app/chat_app.py # http://localhost:7860 +python app/chat_app.py --share # public share link (handy on Colab) +python app/chat_app.py --concepts # use the graph_concepts arm +``` + +This is a **live** demo, so it needs the backend running: a reachable ArangoDB +(`ARANGO_HOST` / `ARANGO_PASS`) and Ollama with `deepseek-r1:8b` pulled. To host +it on **Hugging Face Spaces**, set the Space SDK to Gradio and `app_file: +app/chat_app.py`, and point `ARANGO_HOST`/`ARANGO_PASS` at a hosted database via +Space secrets. + +## `dashboard.py` — results dashboard (Streamlit) + +Visualizes the saved benchmark: per-arm accuracy/F1, the paired McNemar tests, +the ablation figure, and (if the per-sample `results/*_results.json` are present) +confusion matrices and per-class F1. No LLM or database required — it only reads +`results/`, so it's light and deploys anywhere. + +```bash +pip install -r app/requirements.txt # light: streamlit + pandas + scikit-learn +streamlit run app/dashboard.py +``` + +### Deploy to Streamlit Community Cloud (free, always-on) + +The dashboard is the project's hosted demo. `app/requirements.txt` sits next to +the entrypoint so Streamlit Cloud installs only the light deps (it searches the +entrypoint's directory before the heavy root `requirements.txt`). + +1. Push these to `main`: `app/dashboard.py`, `app/requirements.txt`, + `.streamlit/config.toml`, and the `results/` artifacts. +2. Go to , sign in with GitHub, authorize the repo. +3. **Create app → Deploy a public app from GitHub.** +4. Repository `vardhjain/Knowledge_Graph_Question_Answering`, Branch `main`, + **Main file path `app/dashboard.py`**. +5. (Optional) Advanced settings → Python 3.11. Set a custom subdomain (e.g. + `kgqa-ablation`) for a clean URL, or accept the auto-generated one. +6. **Deploy.** Copy the final `*.streamlit.app` URL and point the badge + + "Live demo" link in the root README at it. + +> Tip: commit the per-sample `results/{arm}_results.json` files too (if you still +> have them from the benchmark run) to light up the confusion-matrix section. diff --git a/app/chat_app.py b/app/chat_app.py new file mode 100644 index 0000000..5c4f235 --- /dev/null +++ b/app/chat_app.py @@ -0,0 +1,83 @@ +"""Gradio chat demo over the GraphRAG (`graph`) arm — the ablation's winner. + + python app/chat_app.py # local: http://localhost:7860 + python app/chat_app.py --share # public share link (Colab / remote) + python app/chat_app.py --concepts # use the graph_concepts arm instead + +Requirements: `pip install gradio` (see requirements-app.txt), a reachable +ArangoDB (set ARANGO_HOST / ARANGO_PASS), and a running Ollama with the model +pulled. This is a *live* demo — it retrieves from the graph and calls the LLM. +""" + +from __future__ import annotations + +import argparse +import os +import re +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) + +EXAMPLES = [ + "Do preoperative statins reduce postoperative atrial fibrillation?", + "Is vitamin D deficiency associated with increased mortality?", + "Does laparoscopic surgery reduce hospital stay versus open surgery?", +] + + +def _strip_think(text: str) -> str: + """Drop the reasoning model's ... block for a clean answer.""" + return re.sub(r".*?", "", text, flags=re.DOTALL).strip() + + +def build_retriever(use_concepts: bool): + from kgqa.config import ArangoConfig + from kgqa.models import connect_arango, load_encoder, load_reranker + from kgqa.retrieval import ChunkStore, GraphRetriever + + db = connect_arango(ArangoConfig()) + cache = os.path.join(ROOT, "pubmed_vectors_cache.pkl") + store = ChunkStore.from_arango(db, cache_file=cache) + print(f"[demo] {len(store):,} chunks loaded") + return GraphRetriever(store, load_encoder(), db, + reranker=load_reranker(), use_concepts=use_concepts) + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--share", action="store_true", help="create a public share link") + parser.add_argument("--concepts", action="store_true", help="use the graph_concepts arm") + parser.add_argument("--port", type=int, default=7860) + args = parser.parse_args() + + import gradio as gr + + rag = build_retriever(args.concepts) + + def respond(message, history): + result = rag.chat(message) + answer = _strip_think(result["answer"]) or "_No answer produced._" + sources = result.get("sources", []) + if sources: + links = "\n".join( + f"- [PMID {pid}](https://pubmed.ncbi.nlm.nih.gov/{pid}/)" for pid in sources + ) + answer += f"\n\n**Sources**\n{links}" + return answer + + gr.ChatInterface( + fn=respond, + title="PubMed GraphRAG assistant", + description=( + "Graph-augmented retrieval over PubMedQA: matched chunks are expanded " + "to full abstracts via the knowledge graph, then answered by " + "deepseek-r1:8b. Answers cite the source PubMed IDs." + ), + examples=EXAMPLES, + ).launch(share=args.share, server_port=args.port) + + +if __name__ == "__main__": + main() diff --git a/app/dashboard.py b/app/dashboard.py new file mode 100644 index 0000000..07b5f65 --- /dev/null +++ b/app/dashboard.py @@ -0,0 +1,147 @@ +"""Streamlit dashboard for the GraphRAG vs PlainRAG ablation results. + + pip install streamlit # see requirements-app.txt + streamlit run app/dashboard.py + +Reads results/summary.json (always) for the headline metrics and significance +tests, and results/{arm}_results.json (if present) for confusion matrices and +per-class F1. No LLM or database needed — it just visualizes the saved results, +so it deploys cleanly to Streamlit Cloud. +""" + +from __future__ import annotations + +import json +import os +import sys + +import pandas as pd +import streamlit as st + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) +RESULTS_DIR = os.path.join(ROOT, "results") +ARM_ORDER = ["plain", "plain_rr", "graph", "graph_concepts"] +LABELS = ["yes", "no", "maybe"] + + +@st.cache_data +def load_summary(): + with open(os.path.join(RESULTS_DIR, "summary.json")) as f: + return json.load(f) + + +@st.cache_data +def load_raw(): + raw = {} + for arm in ARM_ORDER: + path = os.path.join(RESULTS_DIR, f"{arm}_results.json") + if os.path.exists(path): + with open(path) as f: + raw[arm] = json.load(f) + return raw + + +def main(): + repo = "https://github.com/vardhjain/Knowledge_Graph_Question_Answering" + st.set_page_config( + page_title="GraphRAG vs PlainRAG — PubMedQA Ablation", + page_icon="🧬", + layout="wide", + initial_sidebar_state="collapsed", + menu_items={ + "Get Help": repo, + "Report a bug": f"{repo}/issues", + "About": ( + "### GraphRAG vs PlainRAG — a fair 4-arm ablation on PubMedQA\n" + "Every layer held constant; only the retrieval strategy changes.\n\n" + f"Source: [{repo}]({repo})" + ), + }, + ) + st.title("GraphRAG vs PlainRAG — a fair 4-arm ablation on PubMedQA") + + try: + summary = load_summary() + except FileNotFoundError: + st.error("results/summary.json not found. Run `python scripts/compare.py` first.") + st.stop() + + st.caption( + f"n = {summary['n']} questions · seed {summary['seed']} · " + f"{summary['model']} · {summary['dataset']}. " + "Every layer held constant; only the retrieval strategy changes." + ) + + arms = summary["arms"] + best = max(arms, key=lambda a: a["accuracy"]) + + # ── headline metrics ────────────────────────────────────────────────────── + cols = st.columns(len(arms)) + for col, arm in zip(cols, arms, strict=False): + delta = f"{arm['accuracy'] - arms[0]['accuracy']:+.1f} pp vs plain" \ + if arm["arm"] != "plain" else None + col.metric(arm["arm"], f"{arm['accuracy']:.1f}%", delta) + + st.success( + f"**Winner: `{best['arm']}` at {best['accuracy']:.1f}%.** The decisive, " + "statistically significant gain comes from parent-document expansion " + "(`plain_rr → graph`: +22.5 pp, McNemar p < 0.0001). The reranker helps " + "but isn't significant; the concept hop doesn't help and costs ~5× latency." + ) + + with st.expander("How this is measured (fairness)"): + st.markdown( + "All four arms share the same corpus, chunking, embedder, reranker, " + "prompt, LLM, seed, and top-k — **only the retrieval strategy changes**, " + "so each adjacent contrast isolates one component. Significance is a " + "paired **McNemar** test on the same questions. The graph context is " + "leakage-free: no question-derived titles or gold labels ever reach the " + "prompt." + ) + + left, right = st.columns([3, 2]) + + with left: + st.subheader("Accuracy & macro-F1 by arm") + df = pd.DataFrame(arms).set_index("arm") + st.bar_chart(df[["accuracy", "macro_f1"]], stack=False, color=["#2196F3", "#FF9800"]) + st.dataframe( + df[["adds", "accuracy", "macro_f1", "avg_latency", "samples"]], + use_container_width=True, + ) + + with right: + st.subheader("Significance (paired McNemar)") + cdf = pd.DataFrame(summary["contrasts"]) + cdf["contrast"] = cdf["from"] + " → " + cdf["to"] + " (" + cdf["effect"] + ")" + cdf["significant"] = cdf["significant"].map({True: "yes", False: "no"}) + st.dataframe( + cdf[["contrast", "delta_acc", "gains", "losses", "p_value", "significant"]], + use_container_width=True, hide_index=True, + ) + st.caption("Latency by arm (seconds / query)") + st.bar_chart(df["avg_latency"], color="#26A69A", horizontal=True) + + # ── optional: per-class detail from raw per-sample results ──────────────── + raw = load_raw() + if raw: + st.subheader("Per-class detail") + from sklearn.metrics import confusion_matrix, f1_score + tabs = st.tabs([a for a in ARM_ORDER if a in raw]) + for tab, arm in zip(tabs, [a for a in ARM_ORDER if a in raw], strict=False): + with tab: + r = raw[arm] + cm = confusion_matrix(r["y_true"], r["y_pred"], labels=LABELS) + st.write("Confusion matrix (rows = actual, cols = predicted)") + st.dataframe(pd.DataFrame(cm, index=LABELS, columns=LABELS)) + f1s = f1_score(r["y_true"], r["y_pred"], labels=LABELS, + average=None, zero_division=0) + st.write("Per-class F1") + st.bar_chart(pd.Series(f1s, index=LABELS)) + + st.caption(f"Source: {repo}") + + +if __name__ == "__main__": + main() diff --git a/app/requirements.txt b/app/requirements.txt new file mode 100644 index 0000000..40d73da --- /dev/null +++ b/app/requirements.txt @@ -0,0 +1,14 @@ +# Streamlit Community Cloud deploy dependencies for app/dashboard.py ONLY. +# +# This file lives next to the entrypoint on purpose: Community Cloud searches the +# entrypoint's directory FIRST, so this light file is used and the heavy root +# requirements.txt (torch, sentence-transformers, datasets, python-arango) is +# never installed for the hosted dashboard. Keep the deploy's "Main file path" +# set to app/dashboard.py. +# +# Also handy locally — `pip install -r app/requirements.txt` runs just the +# dashboard. It needs streamlit + pandas (+ scikit-learn, used lazily for the +# per-class confusion matrices when results/{arm}_results.json files are present). +streamlit>=1.39 +pandas>=2.0 +scikit-learn>=1.3 diff --git a/assets/architecture.svg b/assets/architecture.svg new file mode 100644 index 0000000..6424cd3 --- /dev/null +++ b/assets/architecture.svg @@ -0,0 +1,81 @@ + + + + + + + + + + Knowledge Graph QA — fair 4-arm ablation + Every layer is held constant; only the retrieval strategy changes. + + + + + PubMedQA question + + + Encode · all-MiniLM-L6-v2 + + + Vector search · ChunkStore + 206,613 chunks · ArangoDB + + + Cross-encoder rerank + rerank arms only + + + Context assembly + ← the only thing that differs + + + LLM · deepseek-r1:8b (Ollama) + + + Extract yes/no/maybe → McNemar + + + + + + + + + + + + + + + + + + The four arms (accuracy, n=200) + + + + + + plain + raw top-k chunks + 30.0% + + plain_rr + + cross-encoder reranker + 37.0% + + graph ★ + + parent abstracts (HAS_CONTEXT) + 59.5% + + graph_concepts + + concept hop (MENTIONS) + 57.5% + + + + Parent-document expansion: +22.5 pp over plain_rr + paired McNemar p < 0.0001 · concept hop did not help (−2 pp, n.s.) + diff --git a/assets/chat.png b/assets/chat.png new file mode 100644 index 0000000..daef3cc Binary files /dev/null and b/assets/chat.png differ diff --git a/assets/dashboard.png b/assets/dashboard.png new file mode 100644 index 0000000..2dea607 Binary files /dev/null and b/assets/dashboard.png differ diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..8bd825b --- /dev/null +++ b/backend/README.md @@ -0,0 +1,33 @@ +# Backend + +FastAPI service exposing the hosted GraphRAG agent (`graphrag.answer`, see +[`../src/graphrag`](../src/graphrag)). + +## Local dev + +```bash +pip install -r backend/requirements.txt +uvicorn backend.main:app --reload +# -> http://localhost:8000/docs +``` + +## Endpoints + +| Endpoint | Method | Purpose | +| --- | --- | --- | +| `/health` | GET | liveness probe; also pinged by the keep-warm cron | +| `/query` | POST | `{question, graph_id="demo", use_concepts=false}` -> `{answer, reasoning_path, sources}` | +| `/ingest` | POST | `{dataset_id}` -> `{graph_id}`. Only preloaded dataset ids (currently `demo`) resolve; arbitrary document upload is out of scope for v1 (see the execution plan's scope warning) and returns `501`. | + +## Deploy (Render, free tier) + +[`render.yaml`](../render.yaml) at the repo root is a Render Blueprint -- +connect the repo on Render and it's picked up automatically. Set the secret +env vars (`ARANGO_HOST`, `ARANGO_PASS`, `GROQ_API_KEY`, `GEMINI_API_KEY`, +`CORS_ORIGINS`) in the Render dashboard; nothing else to configure. + +Free tier sleeps after ~15 min idle (30-50s cold start on the next request). +[`.github/workflows/keep-warm.yml`](../.github/workflows/keep-warm.yml) pings +`/health` every 10 minutes during daytime hours to keep it warm without +burning the whole free-hours budget. Set the `BACKEND_URL` repo secret to the +deployed Render URL once it exists. diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/main.py b/backend/main.py new file mode 100644 index 0000000..9f4bcac --- /dev/null +++ b/backend/main.py @@ -0,0 +1,97 @@ +"""FastAPI backend for the hosted GraphRAG agent. + + uvicorn backend.main:app --reload # local dev, http://localhost:8000 + uvicorn backend.main:app --host 0.0.0.0 --port $PORT # Render start command + +Endpoints: + GET /health -- liveness/readiness probe, also used to keep the + Render free-tier instance warm (see + .github/workflows/keep-warm.yml). + POST /query -- ask a question against a graph. + POST /ingest -- resolve a preloaded dataset id to a graph_id. + Arbitrary PDF/document upload is intentionally out + of scope for v1 (see the execution plan's scope + warning: ingestion on messy real-world input is + where this balloons) and returns 501. +""" + +from __future__ import annotations + +import os +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) + +from fastapi import FastAPI, HTTPException # noqa: E402 +from fastapi.middleware.cors import CORSMiddleware # noqa: E402 +from pydantic import BaseModel, Field # noqa: E402 + +app = FastAPI( + title="GraphRAG hosted agent", + description="GraphRAG vs PlainRAG on PubMedQA -- see /docs and RESULTS.md", + version="1.0.0", +) + +# Frontend runs on a different origin (Vercel); allow it in explicitly via env +# so this isn't wide open by default in production. +_origins = os.environ.get("CORS_ORIGINS", "http://localhost:3000").split(",") +app.add_middleware( + CORSMiddleware, + allow_origins=[o.strip() for o in _origins if o.strip()], + allow_methods=["GET", "POST"], + allow_headers=["*"], +) + +# Dataset ids /ingest is allowed to resolve without a live upload pipeline. +_KNOWN_DATASETS = {"demo": "demo"} + + +class QueryRequest(BaseModel): + question: str = Field(min_length=1, max_length=2000) + graph_id: str = "demo" + use_concepts: bool = False + + +class QueryResponse(BaseModel): + answer: str + reasoning_path: list[dict] + sources: list[str] + + +class IngestRequest(BaseModel): + dataset_id: str + + +class IngestResponse(BaseModel): + graph_id: str + + +@app.get("/health") +def health() -> dict: + return {"status": "ok"} + + +@app.post("/query", response_model=QueryResponse) +def query(req: QueryRequest) -> dict: + from graphrag import answer + + try: + return answer(req.question, graph_id=req.graph_id, use_concepts=req.use_concepts) + except Exception as exc: + raise HTTPException(status_code=502, detail=f"Answering failed: {exc}") from exc + + +@app.post("/ingest", response_model=IngestResponse) +def ingest(req: IngestRequest) -> dict: + graph_id = _KNOWN_DATASETS.get(req.dataset_id) + if graph_id is None: + raise HTTPException( + status_code=501, + detail=( + f"Unknown dataset_id {req.dataset_id!r}. Only preloaded datasets " + f"are supported: {sorted(_KNOWN_DATASETS)}. Arbitrary document " + "upload is not implemented in v1." + ), + ) + return {"graph_id": graph_id} diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..b17c6f9 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,5 @@ +-r ../requirements.txt + +# API server +fastapi>=0.110 +uvicorn[standard]>=0.29 diff --git a/backend/test_main.py b/backend/test_main.py new file mode 100644 index 0000000..b172a6b --- /dev/null +++ b/backend/test_main.py @@ -0,0 +1,72 @@ +"""Tests for the FastAPI backend -- graphrag.answer is monkeypatched, no live +ArangoDB/LLM calls. Run with: pytest backend/test_main.py +""" + +from __future__ import annotations + +import os +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) + +from fastapi.testclient import TestClient + +from backend.main import app + +client = TestClient(app) + + +def test_health(): + resp = client.get("/health") + assert resp.status_code == 200 + assert resp.json() == {"status": "ok"} + + +def test_query_returns_answer_shape(monkeypatch): + import graphrag + + def fake_answer(question, graph_id="demo", use_concepts=False): + return { + "answer": f"answer to: {question}", + "reasoning_path": [{"kind": "seed_chunk", "node_id": "Chunks/1_0", "label": "x"}], + "sources": ["1"], + } + + monkeypatch.setattr(graphrag, "answer", fake_answer) + + resp = client.post("/query", json={"question": "does aspirin help?"}) + assert resp.status_code == 200 + body = resp.json() + assert body["answer"] == "answer to: does aspirin help?" + assert body["sources"] == ["1"] + assert body["reasoning_path"][0]["kind"] == "seed_chunk" + + +def test_query_rejects_empty_question(): + resp = client.post("/query", json={"question": ""}) + assert resp.status_code == 422 + + +def test_query_failure_returns_502(monkeypatch): + import graphrag + + def broken(*args, **kwargs): + raise RuntimeError("no providers available") + + monkeypatch.setattr(graphrag, "answer", broken) + + resp = client.post("/query", json={"question": "does aspirin help?"}) + assert resp.status_code == 502 + assert "no providers available" in resp.json()["detail"] + + +def test_ingest_known_dataset_returns_graph_id(): + resp = client.post("/ingest", json={"dataset_id": "demo"}) + assert resp.status_code == 200 + assert resp.json() == {"graph_id": "demo"} + + +def test_ingest_unknown_dataset_returns_501(): + resp = client.post("/ingest", json={"dataset_id": "my_arbitrary.pdf"}) + assert resp.status_code == 501 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..2cf966a --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +# Local ArangoDB for development and running the benchmark without a cloud account. +# +# docker compose up -d +# export ARANGO_PASS=devpassword # PowerShell: $env:ARANGO_PASS="devpassword" +# python scripts/ingest.py # then run_benchmark.py / compare.py +# +# Web UI: http://localhost:8529 (user: root, password: devpassword) +# Change the password below (and ARANGO_PASS) before exposing this anywhere. + +services: + arangodb: + image: arangodb:3.11 + container_name: kgqa-arangodb + environment: + ARANGO_ROOT_PASSWORD: devpassword + ports: + - "8529:8529" + volumes: + - arango_data:/var/lib/arangodb3 + +volumes: + arango_data: diff --git a/Graph_RAG_PPT.pptx b/docs/Graph_RAG_PPT.pptx similarity index 100% rename from Graph_RAG_PPT.pptx rename to docs/Graph_RAG_PPT.pptx diff --git a/Project Report.pdf b/docs/Project_Report.pdf similarity index 100% rename from Project Report.pdf rename to docs/Project_Report.pdf diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000..2faa444 --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1,10 @@ +# GitHub Pages site (Settings → Pages → Source: Deploy from a branch → main → /docs) +title: Knowledge Graph Question Answering +description: GraphRAG vs PlainRAG on PubMedQA — a fair, leakage-free, statistically-tested ablation +theme: jekyll-theme-cayman +show_downloads: false + +# Keep the repo's data/binaries out of the built site. +exclude: + - "*.pdf" + - "*.pptx" diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..d01bc0a --- /dev/null +++ b/docs/index.md @@ -0,0 +1,39 @@ +--- +--- + +[**▶ Live demo**](https://vardhjain-knowledge-graph-question-answerin-appdashboard-hkwi57.streamlit.app)  ·  [**GitHub repo**](https://github.com/vardhjain/Knowledge_Graph_Question_Answering)  ·  [**Project report (PDF)**](https://github.com/vardhjain/Knowledge_Graph_Question_Answering/blob/main/docs/Project_Report.pdf)  ·  [**Slides**](https://github.com/vardhjain/Knowledge_Graph_Question_Answering/blob/main/docs/Graph_RAG_PPT.pptx) + +## What this is + +Most "GraphRAG beats RAG" demos are confounded — the graph pipeline quietly also +gets a reranker, a different corpus, or even leaks the answer into the prompt. +This project runs a **4-arm ablation** on [PubMedQA](https://pubmedqa.github.io/) +where every layer (corpus, chunking, embedder, reranker, prompt, LLM, top-k, seed) +is held constant, so the accuracy change between adjacent arms is attributable to +exactly one component — verified with a paired **McNemar** test. + +![Architecture and 4-arm ablation](https://raw.githubusercontent.com/vardhjain/Knowledge_Graph_Question_Answering/main/assets/architecture.svg) + +## Results (n = 200, seed 42) + +| Arm | Accuracy | Macro F1 | Adds | +| --- | --- | --- | --- | +| `plain` | 30.0% | 29.7% | baseline chunk RAG | +| `plain_rr` | 37.0% | 35.2% | + cross-encoder reranker | +| **`graph`** | **59.5%** | **50.5%** | + parent-paper expansion | +| `graph_concepts` | 57.5% | 50.0% | + MeSH concept hop | + +![4-arm ablation](https://raw.githubusercontent.com/vardhjain/Knowledge_Graph_Question_Answering/main/results/ablation.png) + +**The honest finding:** the graph's decisive, statistically significant win comes +from **parent-document expansion** (`plain_rr → graph`: **+22.5 pp**, McNemar +**p < 0.0001**). The reranker helps but isn't significant (+7 pp, p = 0.08), and +MeSH concept-hop expansion does **not** help on this single-abstract dataset +(−2 pp, p = 0.69) while costing ~5× the latency. The graph helps by *deepening* +context, not by *broadening* it. + +## Explore + +- **[Live results dashboard](https://vardhjain-knowledge-graph-question-answerin-appdashboard-hkwi57.streamlit.app)** — interactive bars, significance tests, per-class breakdown +- **[Source code & README](https://github.com/vardhjain/Knowledge_Graph_Question_Answering)** — package, scripts, tests, CI +- **[Project report (PDF)](https://github.com/vardhjain/Knowledge_Graph_Question_Answering/blob/main/docs/Project_Report.pdf)** and **[slides](https://github.com/vardhjain/Knowledge_Graph_Question_Answering/blob/main/docs/Graph_RAG_PPT.pptx)** diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..5ef6a52 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,41 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/app/benchmark/page.tsx b/frontend/app/benchmark/page.tsx new file mode 100644 index 0000000..edddbd0 --- /dev/null +++ b/frontend/app/benchmark/page.tsx @@ -0,0 +1,162 @@ +import Link from "next/link"; +import { loadBenchmarkSummary } from "@/lib/results"; + +export const metadata = { + title: "Benchmark -- PubMed GraphRAG assistant", +}; + +function fmtPct(n: number) { + return `${n.toFixed(1)}%`; +} + +function fmtP(p: number) { + return p < 0.0001 ? "<0.0001" : p.toFixed(4); +} + +export default function BenchmarkPage() { + const summary = loadBenchmarkSummary(); + const winner = summary.arms.length + ? summary.arms.reduce((a, b) => (b.accuracy > a.accuracy ? b : a)) + : null; + + return ( +
+
+
+
+

Benchmark

+ + ← Back to chat + +
+

+ 4-arm ablation on {summary.dataset}, n={summary.n}, seed={summary.seed}, model{" "} + {summary.model}. Numbers + pulled directly from{" "} + + RESULTS.md + + . +

+
+
+ +
+
+

+ Per-arm results +

+
+ + + + + + + + + + + + {summary.arms.map((arm) => ( + + + + + + + + ))} + +
ArmAccuracyMacro F1Avg latencyAdds
+ {arm.arm} + {arm.arm === winner?.arm && ( + + winner + + )} + {fmtPct(arm.accuracy)}{fmtPct(arm.macro_f1)}{arm.avg_latency.toFixed(1)}s{arm.adds}
+
+
+ +
+

+ Significance (paired McNemar) +

+
+ + + + + + + + + + + + {summary.contrasts.map((c) => ( + + + + + + + + ))} + +
ContrastΔaccGains / lossesp-valueSignificant?
+ {c.from} →{" "} + {c.to} + ({c.effect}) + 0 ? "text-green-700" : "text-red-700"}`}> + {c.delta_acc > 0 ? "+" : ""} + {c.delta_acc.toFixed(1)}pp + + {c.gains} / {c.losses} + {fmtP(c.p_value)} + {c.significant ? ( + yes + ) : ( + no + )} +
+
+
+ +
+

+ Honest summary +

+

+ The graph arm's win is one specific mechanism, not "a knowledge graph" in + the abstract: parent-document expansion{" "} + lifts accuracy +22.5 percentage points over the reranked baseline, confirmed by a + paired McNemar test (p<0.0001) -- not noise. The further MeSH concept-hop arm does not clear + significance against the plain graph arm (p=0.69) and costs ~5x the latency, so it is + not the recommended arm to ship. See{" "} + + RESULTS.md + {" "} + for the full write-up. +

+
+
+
+ ); +} diff --git a/frontend/app/favicon.ico b/frontend/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/frontend/app/favicon.ico differ diff --git a/frontend/app/globals.css b/frontend/app/globals.css new file mode 100644 index 0000000..2a89a48 --- /dev/null +++ b/frontend/app/globals.css @@ -0,0 +1,19 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx new file mode 100644 index 0000000..c8029f5 --- /dev/null +++ b/frontend/app/layout.tsx @@ -0,0 +1,33 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "PubMed GraphRAG assistant", + description: "Graph-augmented retrieval over PubMedQA, with a reasoning-path visualization", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx new file mode 100644 index 0000000..c84a40e --- /dev/null +++ b/frontend/app/page.tsx @@ -0,0 +1,36 @@ +import Link from "next/link"; +import ChatPanel from "@/components/ChatPanel"; + +export default function Home() { + return ( +
+
+
+

+ PubMed GraphRAG assistant +

+ + View benchmark → + +
+

+ Graph-augmented retrieval over PubMedQA. Parent-document expansion lifts + accuracy +22.5pp over a reranked baseline (McNemar p<0.0001) -- + see{" "} + + RESULTS.md + + . +

+
+
+ +
+
+ ); +} diff --git a/frontend/components/ChatPanel.tsx b/frontend/components/ChatPanel.tsx new file mode 100644 index 0000000..63f5ffd --- /dev/null +++ b/frontend/components/ChatPanel.tsx @@ -0,0 +1,151 @@ +"use client"; + +import { useState } from "react"; +import { askQuestion, ApiError } from "@/lib/api"; +import type { ChatMessage } from "@/lib/types"; +import ReasoningGraph from "./ReasoningGraph"; + +const EXAMPLE_QUESTIONS = [ + "Do preoperative statins reduce postoperative atrial fibrillation?", + "Is vitamin D deficiency associated with increased mortality?", + "Does laparoscopic surgery reduce hospital stay versus open surgery?", +]; + +export default function ChatPanel() { + const [messages, setMessages] = useState([]); + const [input, setInput] = useState(""); + const [loading, setLoading] = useState(false); + const [openReasoningFor, setOpenReasoningFor] = useState(null); + + async function send(question: string) { + if (!question.trim() || loading) return; + setInput(""); + setMessages((prev) => [...prev, { role: "user", content: question }]); + setLoading(true); + + try { + const result = await askQuestion({ question }); + setMessages((prev) => [ + ...prev, + { + role: "assistant", + content: result.answer, + sources: result.sources, + reasoningPath: result.reasoning_path, + }, + ]); + } catch (err) { + const message = err instanceof ApiError ? err.message : "Something went wrong."; + setMessages((prev) => [...prev, { role: "assistant", content: message, error: true }]); + } finally { + setLoading(false); + } + } + + return ( +
+
+ {messages.length === 0 && ( +
+

+ Ask a biomedical question. Answers are grounded in PubMed abstracts + retrieved via a knowledge graph -- click a sample question to try it. +

+
+ {EXAMPLE_QUESTIONS.map((q) => ( + + ))} +
+
+ )} + + {messages.map((m, i) => ( +
+
+ {m.content} + + {m.role === "assistant" && !m.error && m.sources && m.sources.length > 0 && ( +
+ Sources: + {m.sources.map((pmid, si) => ( + + PMID {pmid} + {si < m.sources!.length - 1 ? ", " : ""} + + ))} +
+ )} + + {m.role === "assistant" && !m.error && m.reasoningPath && ( +
+ + {openReasoningFor === i && ( +
+ +
+ )} +
+ )} +
+
+ ))} + + {loading && ( +
+
+ Retrieving and reasoning over the graph... +
+
+ )} +
+ +
{ + e.preventDefault(); + send(input); + }} + className="flex gap-2 p-4 border-t border-gray-200" + > + setInput(e.target.value)} + placeholder="Ask a biomedical question..." + disabled={loading} + className="flex-1 rounded-full border border-gray-300 px-4 py-2 text-sm focus:outline-none focus:border-blue-400" + /> + +
+
+ ); +} diff --git a/frontend/components/ReasoningGraph.tsx b/frontend/components/ReasoningGraph.tsx new file mode 100644 index 0000000..9c83fa9 --- /dev/null +++ b/frontend/components/ReasoningGraph.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { useMemo } from "react"; +import ReactFlow, { + Background, + Controls, + type Edge, + type Node, + Position, +} from "reactflow"; +import "reactflow/dist/style.css"; +import type { ReasoningStep } from "@/lib/types"; + +const KIND_STYLE: Record = { + seed_chunk: { bg: "#eff6ff", border: "#3b82f6" }, // retrieved chunk + parent_paper: { bg: "#f0fdf4", border: "#22c55e" }, // expanded via HAS_CONTEXT + concept_neighbour: { bg: "#fdf4ff", border: "#a855f7" }, // MeSH concept hop +}; + +const KIND_LABEL: Record = { + seed_chunk: "Retrieved chunk", + parent_paper: "Parent paper (HAS_CONTEXT)", + concept_neighbour: "Concept neighbour (MENTIONS)", +}; + +function buildGraph(steps: ReasoningStep[]): { nodes: Node[]; edges: Edge[] } { + const seedChunks = steps.filter((s) => s.kind === "seed_chunk"); + const parentPapers = steps.filter((s) => s.kind === "parent_paper"); + const conceptNeighbours = steps.filter((s) => s.kind === "concept_neighbour"); + + const nodes: Node[] = []; + const edges: Edge[] = []; + const seen = new Set(); + + const addNode = (step: ReasoningStep, x: number, y: number) => { + if (seen.has(step.node_id)) return; + seen.add(step.node_id); + const style = KIND_STYLE[step.kind]; + nodes.push({ + id: step.node_id, + position: { x, y }, + data: { label: step.label }, + sourcePosition: Position.Bottom, + targetPosition: Position.Top, + style: { + background: style.bg, + border: `1.5px solid ${style.border}`, + borderRadius: 8, + padding: 8, + fontSize: 12, + width: 180, + }, + }); + }; + + seedChunks.forEach((s, i) => addNode(s, i * 220, 0)); + parentPapers.forEach((s, i) => addNode(s, i * 220, 160)); + conceptNeighbours.forEach((s, i) => addNode(s, i * 220 + 60, 320)); + + for (const step of parentPapers) { + if (step.from_node) { + edges.push({ + id: `${step.from_node}->${step.node_id}`, + source: step.from_node, + target: step.node_id, + label: step.edge ?? undefined, + animated: true, + style: { stroke: KIND_STYLE.parent_paper.border }, + }); + } + } + + // Concept-hop origin isn't tracked per-neighbour by the backend (the graph + // query ranks across all seed papers together) -- draw from every parent + // paper so the fan-out is visible without overclaiming a single source. + for (const neighbour of conceptNeighbours) { + for (const parent of parentPapers) { + edges.push({ + id: `${parent.node_id}->${neighbour.node_id}`, + source: parent.node_id, + target: neighbour.node_id, + label: neighbour.edge ?? undefined, + style: { stroke: KIND_STYLE.concept_neighbour.border, strokeDasharray: "4 3" }, + }); + } + } + + return { nodes, edges }; +} + +export default function ReasoningGraph({ steps }: { steps: ReasoningStep[] }) { + const { nodes, edges } = useMemo(() => buildGraph(steps), [steps]); + + if (steps.length === 0) { + return ( +

+ No reasoning path to show for this answer. +

+ ); + } + + return ( +
+
+ + + + +
+
+ {(Object.keys(KIND_STYLE) as ReasoningStep["kind"][]).map((kind) => ( + + + {KIND_LABEL[kind]} + + ))} +
+
+ ); +} diff --git a/frontend/eslint.config.mjs b/frontend/eslint.config.mjs new file mode 100644 index 0000000..05e726d --- /dev/null +++ b/frontend/eslint.config.mjs @@ -0,0 +1,18 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + // Override default ignores of eslint-config-next. + globalIgnores([ + // Default ignores of eslint-config-next: + ".next/**", + "out/**", + "build/**", + "next-env.d.ts", + ]), +]); + +export default eslintConfig; diff --git a/frontend/lib/api.ts b/frontend/lib/api.ts new file mode 100644 index 0000000..8d48307 --- /dev/null +++ b/frontend/lib/api.ts @@ -0,0 +1,46 @@ +import type { QueryRequest, QueryResponse } from "./types"; + +// Render free tier cold-starts in ~30-50s after idling; give it real headroom +// instead of failing a legitimate cold start. +const REQUEST_TIMEOUT_MS = 60_000; + +const API_URL = process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:8000"; + +export class ApiError extends Error { + constructor(message: string, readonly status?: number) { + super(message); + this.name = "ApiError"; + } +} + +export async function askQuestion(req: QueryRequest): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + + try { + const res = await fetch(`${API_URL}/query`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ graph_id: "demo", use_concepts: false, ...req }), + signal: controller.signal, + }); + + if (!res.ok) { + const body = await res.json().catch(() => ({})); + const detail = typeof body.detail === "string" ? body.detail : JSON.stringify(body.detail ?? res.statusText); + throw new ApiError(detail, res.status); + } + + return await res.json(); + } catch (err) { + if (err instanceof ApiError) throw err; + if (err instanceof Error && err.name === "AbortError") { + throw new ApiError( + "The backend is waking up from a cold start and is taking longer than expected. Please try again in a moment." + ); + } + throw new ApiError("Could not reach the backend. Is it running / deployed?"); + } finally { + clearTimeout(timeout); + } +} diff --git a/frontend/lib/results.ts b/frontend/lib/results.ts new file mode 100644 index 0000000..886b207 --- /dev/null +++ b/frontend/lib/results.ts @@ -0,0 +1,45 @@ +import fs from "fs"; +import path from "path"; + +export interface ArmResult { + arm: string; + accuracy: number; + macro_f1: number; + avg_latency: number; + samples: number; + adds: string; +} + +export interface Contrast { + from: string; + to: string; + effect: string; + delta_acc: number; + gains: number; + losses: number; + p_value: number; + significant: boolean; +} + +export interface BenchmarkSummary { + n: number; + seed: number; + model: string; + dataset: string; + arms: ArmResult[]; + contrasts: Contrast[]; +} + +// Reads the repo's canonical results/summary.json directly -- no duplicated +// numbers to drift out of sync with RESULTS.md. Runs server-side only +// (fs is unavailable in the browser); this page is statically generated at +// build time, so the file is read once per build, not per request. +// +// Vercel note: if this project's Root Directory is set to `frontend`, enable +// "Include source files outside of the Root Directory in the Build Step" in +// the Vercel project settings, or this import will fail to find the file. +export function loadBenchmarkSummary(): BenchmarkSummary { + const resultsPath = path.join(process.cwd(), "..", "results", "summary.json"); + const raw = fs.readFileSync(resultsPath, "utf-8"); + return JSON.parse(raw) as BenchmarkSummary; +} diff --git a/frontend/lib/types.ts b/frontend/lib/types.ts new file mode 100644 index 0000000..2240bcb --- /dev/null +++ b/frontend/lib/types.ts @@ -0,0 +1,29 @@ +export type ReasoningStepKind = "seed_chunk" | "parent_paper" | "concept_neighbour"; + +export interface ReasoningStep { + kind: ReasoningStepKind; + node_id: string; + label: string; + from_node: string | null; + edge: string | null; +} + +export interface QueryResponse { + answer: string; + reasoning_path: ReasoningStep[]; + sources: string[]; +} + +export interface QueryRequest { + question: string; + graph_id?: string; + use_concepts?: boolean; +} + +export interface ChatMessage { + role: "user" | "assistant"; + content: string; + sources?: string[]; + reasoningPath?: ReasoningStep[]; + error?: boolean; +} diff --git a/frontend/next.config.ts b/frontend/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/frontend/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..eb4fdf2 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,7346 @@ +{ + "name": "frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.1.0", + "dependencies": { + "next": "16.2.10", + "react": "19.2.4", + "react-dom": "19.2.4", + "reactflow": "^11.11.4" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.10", + "tailwindcss": "^4", + "typescript": "^5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@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-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "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/eslint-utils/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-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "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.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "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.1", + "minimatch": "^3.1.5", + "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/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "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.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "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/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@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==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@next/env": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.10.tgz", + "integrity": "sha512-zLPxg9M0MEHmygpj5OuxjQ+vHMiy/K7cSp74G8ecYolmgUWw0RwN02tF56npup/+qaI8JB97hQgS/r2Hb6QwVA==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.10.tgz", + "integrity": "sha512-Gs8D2m21VnJeFo9qvYIIqJH94frWerWYu41BprU1pLtRVF7PCQNLiFZZ3fG+iPuj3K83Cwv/rt+msLOy8Qgu3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.10.tgz", + "integrity": "sha512-v9IdJCa0H0mbo+8z5zwUpOk1Vj7RjkcI5uNYf5Ws1y6szf/p3Mzl9hLaST8SCt6L9h8NGnruZcd2+o0NTNwDhA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.10.tgz", + "integrity": "sha512-17IS0jJRViROGmA9uGdNR8VPJpfbnaVG7E9qhso5jDLkmyd0lSDORWxbcKINzcFqzZqGwGtMSnrFRxBpuUYjLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.10.tgz", + "integrity": "sha512-GRQRsRtuciNJvB54AvvuQTiq0oZtFwa1owQqtZD8wwnGpM2L39MV22kpI72YSXLKIyY40LC66EiLFv4PiicXxg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.10.tgz", + "integrity": "sha512-zkN9MQYS7UQBro+FnISUq1itaQjXI9xqISzuQ+2bc921NcJ1x4yPCqrn77tVN6/dOOXaaWVX3k6/bR07pPwK+A==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.10.tgz", + "integrity": "sha512-iCVJnwvrPYECvA6WM/7+oo+OiTvedIKLxtCLAZP4xZR3nXa1zmzZyLPbYCmWvpd4CvMYF1EMTafd0ii3DygLvA==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.10.tgz", + "integrity": "sha512-ov2g4H0dHY9bPoOU83m91hWT7Iq5qy13bUnyyshLU3HGR1Ownn0X9QpmDPc5iIUaahTp7f7LeGAhV4DSFtackw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.10.tgz", + "integrity": "sha512-DwAnhLX76HQiFFQNgWlcK+JzlnD1rZ+UK/WY0ZMI/deXpvgnesjNYrqcfo1JzBuz4Kf7o3brIBL0glI1junatA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.10.tgz", + "integrity": "sha512-0JXq3b85Jk9Jg4ntLUbXSPvoDw3gpZou7twuKdoFG2jOw635v7+IiXfTaa0TxVMyx78pUjnrVYwLgjKfX4e6/A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "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/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@reactflow/background": { + "version": "11.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", + "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls": { + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", + "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", + "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", + "license": "MIT", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.7.14", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", + "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", + "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", + "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "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/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.2.tgz", + "integrity": "sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "5.21.6", + "jiti": "^2.7.0", + "lightningcss": "1.32.0", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.2.tgz", + "integrity": "sha512-z8ZgnzX8gdNoWLBLqBPoh/sjnxkwvf9ZuWjnO0l0yIzbLa5/9S+eC5QxGZKRobVHIC3/1BoMWjHblqWjcgFgag==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-arm64": "4.3.2", + "@tailwindcss/oxide-darwin-x64": "4.3.2", + "@tailwindcss/oxide-freebsd-x64": "4.3.2", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.2", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.2", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.2", + "@tailwindcss/oxide-linux-x64-musl": "4.3.2", + "@tailwindcss/oxide-wasm32-wasi": "4.3.2", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.2", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.2" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.2.tgz", + "integrity": "sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.2.tgz", + "integrity": "sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.2.tgz", + "integrity": "sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.2.tgz", + "integrity": "sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.2.tgz", + "integrity": "sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.2.tgz", + "integrity": "sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.2.tgz", + "integrity": "sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.2.tgz", + "integrity": "sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.2.tgz", + "integrity": "sha512-cixpqbh2toJDmkuCRI68nXA8ZxNmdK9Y+9v5h3MC3ZQKy/0BO8AWzlkWyRM7JAFSGBlfig4YVTPsK6MVgqz1uw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.2.tgz", + "integrity": "sha512-4ec2Z/LOmRsAgU23CS4xeJfcJlmRg94A/XrbGRCF1gyU/zdDfRLYDVsS+ynSZCmGNxQ1jQriQOKMQeQxBA3Isw==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.1", + "@emnapi/runtime": "^1.11.1", + "@emnapi/wasi-threads": "^1.2.2", + "@napi-rs/wasm-runtime": "^1.1.4", + "@tybys/wasm-util": "^0.10.2", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.2.tgz", + "integrity": "sha512-Zyr/M0+XcYZu3bZrUytc7TXvrk0ftWfl8gN2MwekNDzhqhKRUucMPSeOzM0o0wH5AWOU49BsKRrfKxI2atCPMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.2.tgz", + "integrity": "sha512-QI9BO7KlNZsp2GuO0jwAAj5jCDABOKXRkCk2XuKTSaNEFSdfzqswYVTtCHBNKHLsqyjFyFkqlDiwkNbTYSssMQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.2.tgz", + "integrity": "sha512-rjVWYCa7Ngbi5AarT6k8TkxUG3Wl1QKzHdIZVsjZSzf36Jmo2IKZt/NHRAwly8oDkbBOH0YTu+CHuf9jPxMc+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.2", + "@tailwindcss/oxide": "4.3.2", + "postcss": "^8.5.15", + "tailwindcss": "4.3.2" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "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-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "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-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "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-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.4.tgz", + "integrity": "sha512-UHYId5WTCx4L4YNel7NU00XUXXgvgpgZOvp10PuvsQENjMDXhh2RyFc0KBjO7B45ne4Ha1yVH7ii0vnzKkuzWA==", + "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-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "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-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "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/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "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/node": { + "version": "20.19.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz", + "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.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.62.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.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.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3" + }, + "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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", + "debug": "^4.4.3" + }, + "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 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.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.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", + "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 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", + "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.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.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 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.62.1", + "eslint-visitor-keys": "^5.0.0" + }, + "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": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.12.2.tgz", + "integrity": "sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.12.2.tgz", + "integrity": "sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.12.2.tgz", + "integrity": "sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.12.2.tgz", + "integrity": "sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.12.2.tgz", + "integrity": "sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.12.2.tgz", + "integrity": "sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.12.2.tgz", + "integrity": "sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.12.2.tgz", + "integrity": "sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.12.2.tgz", + "integrity": "sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.12.2.tgz", + "integrity": "sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.12.2.tgz", + "integrity": "sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.12.2.tgz", + "integrity": "sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.12.2.tgz", + "integrity": "sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.12.2.tgz", + "integrity": "sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.12.2.tgz", + "integrity": "sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.12.2.tgz", + "integrity": "sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.12.2.tgz", + "integrity": "sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.12.2.tgz", + "integrity": "sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.12.2.tgz", + "integrity": "sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.12.2.tgz", + "integrity": "sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.12.2.tgz", + "integrity": "sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.12.2.tgz", + "integrity": "sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "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/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "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/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-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/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/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.12.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.12.1.tgz", + "integrity": "sha512-s7iGf5GaVMxEG0ENN9x+xTr7GFZCb1ZP/1uATUpCEK2X78nDB3RwbtFCo9pGAf9ru+VwoQ464DkaLEeRM08wJA==", + "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/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/baseline-browser-mapping": { + "version": "2.10.42", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.42.tgz", + "integrity": "sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "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": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "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/caniuse-lite": { + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", + "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/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/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "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/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/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/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/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true, + "license": "MIT" + }, + "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-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "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-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-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "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/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "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-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.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/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/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "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/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.387", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.387.tgz", + "integrity": "sha512-TaxwufTFDufvPEoXdhwVrA3UdFWBeWGkYoJ1K8ldF1xe6gKfth6iRNS5lTQ5JPNOHdGQm8PT1QYKUqFLCiUefQ==", + "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/enhanced-resolve": { + "version": "5.21.6", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.6.tgz", + "integrity": "sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "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-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "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.3.3", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.3.tgz", + "integrity": "sha512-0PuBxFi+4uPanB97iDxCLWuHeYud2FALrw5HFZGtAF38UpJDbDC8frwp2cnDyae692CQ0dou60UwWfhgsa4U/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.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", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "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.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "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.5", + "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-config-next": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.10.tgz", + "integrity": "sha512-HSybLOY0QKf39i4FWUqPN0xWiNDi6A6UqJmZtgDkS3zMqjXTqULvj/sueXx3cdCG0mVG+qH6k5/qdegklH1d1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.2.10", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "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": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.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.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "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-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/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-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-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-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "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 || ^10.0.0" + } + }, + "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": "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/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/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "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/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/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "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.4" + }, + "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/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "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/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.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "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/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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-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-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.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "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": "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/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/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/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/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.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/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/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/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/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-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-bun-module/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "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-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "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-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "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-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-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-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-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.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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-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/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/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/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/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/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "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.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/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "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/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/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/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "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/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "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.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "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/next": { + "version": "16.2.10", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.10.tgz", + "integrity": "sha512-2som5AVXb3kE6Yjine3/mNbBayYF58eguBWIVVUdr1y/L426xyVEgYxgBG+1QC34P2x5E+tcDup6XkuOAX3dCA==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.10", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.10", + "@next/swc-darwin-x64": "16.2.10", + "@next/swc-linux-arm64-gnu": "16.2.10", + "@next/swc-linux-arm64-musl": "16.2.10", + "@next/swc-linux-x64-gnu": "16.2.10", + "@next/swc-linux-x64-musl": "16.2.10", + "@next/swc-win32-arm64-msvc": "16.2.10", + "@next/swc-win32-x64-msvc": "16.2.10", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "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.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "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==", + "dev": true, + "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/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/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/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/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/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.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "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.12", + "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/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "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/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/reactflow": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", + "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", + "license": "MIT", + "dependencies": { + "@reactflow/background": "11.3.14", + "@reactflow/controls": "11.2.14", + "@reactflow/core": "11.11.4", + "@reactflow/minimap": "11.7.14", + "@reactflow/node-resizer": "2.2.14", + "@reactflow/node-toolbar": "1.3.14" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "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/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/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "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/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/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/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "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/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "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/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/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "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.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "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/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/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "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/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.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, + "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/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/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/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "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/tailwindcss": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.2.tgz", + "integrity": "sha512-WtctNNSH8A9jlMIqxzuYumOHU5uGZyRv0Q5svQl+oEPy5w84YpBxdb7MdqyiSPQge5jTJ6zFQLq0PFygdccSBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "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/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "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/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/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.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", + "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.62.1", + "@typescript-eslint/parser": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.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 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.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": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.12.2.tgz", + "integrity": "sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.4" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.12.2", + "@unrs/resolver-binding-android-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-arm64": "1.12.2", + "@unrs/resolver-binding-darwin-x64": "1.12.2", + "@unrs/resolver-binding-freebsd-x64": "1.12.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.12.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.12.2", + "@unrs/resolver-binding-linux-loong64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-loong64-musl": "1.12.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.12.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.12.2", + "@unrs/resolver-binding-linux-x64-musl": "1.12.2", + "@unrs/resolver-binding-openharmony-arm64": "1.12.2", + "@unrs/resolver-binding-wasm32-wasi": "1.12.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.12.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.12.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.12.2" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "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/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/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.22", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", + "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "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/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/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/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/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..bd0a3a3 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,27 @@ +{ + "name": "frontend", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "eslint" + }, + "dependencies": { + "next": "16.2.10", + "react": "19.2.4", + "react-dom": "19.2.4", + "reactflow": "^11.11.4" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.2.10", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/frontend/postcss.config.mjs b/frontend/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/frontend/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/frontend/public/file.svg b/frontend/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/frontend/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/globe.svg b/frontend/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/frontend/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/next.svg b/frontend/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/frontend/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/vercel.svg b/frontend/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/frontend/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/window.svg b/frontend/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/frontend/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..3a13f90 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +} diff --git a/notebooks/01_ingest.ipynb b/notebooks/01_ingest.ipynb new file mode 100644 index 0000000..8bd0838 --- /dev/null +++ b/notebooks/01_ingest.ipynb @@ -0,0 +1,89 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 01 — Ingest PubMedQA into ArangoDB (run once)\n", + "\n", + "Thin Colab wrapper around `scripts/ingest.py`. Builds the **leakage-free** knowledge graph\n", + "(Papers / Chunks / Concepts + HAS_CONTEXT / MENTIONS edges).\n", + "\n", + "**Before running**, add these to the Colab **Secrets** panel (key icon, left sidebar):\n", + "- `ARANGO_PASS` — your ArangoDB password (required)\n", + "- `ARANGO_HOST` — your endpoint, e.g. `https://.arangodb.cloud:8529`\n", + " (ArangoDB Oasis offers a free tier; or run any reachable ArangoDB)\n", + "\n", + "A **GPU** runtime (+ High-RAM) speeds the embedding pass. Run this notebook **once**,\n", + "then use `02_benchmark.ipynb`. Ingesting labeled + unlabeled (~62k papers) is mostly\n", + "network-bound on the inserts." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Reset-safe clone: always starts from /content and removes any prior copy,\n", + "# so re-running this cell can never nest a second checkout.\n", + "%cd /content\n", + "!rm -rf Knowledge_Graph_Question_Answering\n", + "!git clone -b main https://github.com/vardhjain/Knowledge_Graph_Question_Answering.git -q\n", + "%cd Knowledge_Graph_Question_Answering\n", + "!pip install -q -r requirements.txt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from google.colab import userdata\n", + "\n", + "# Pull connection settings from Colab Secrets (nothing is hardcoded).\n", + "for key in ['ARANGO_PASS', 'ARANGO_HOST', 'ARANGO_DB']:\n", + " try:\n", + " val = userdata.get(key)\n", + " if val:\n", + " os.environ[key] = val\n", + " except Exception:\n", + " pass\n", + "\n", + "assert os.environ.get('ARANGO_PASS'), 'Add ARANGO_PASS in the Secrets panel.'\n", + "print('ARANGO_HOST:', os.environ.get('ARANGO_HOST', '(default http://localhost:8529)'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Quick smoke test first (labeled split only, ~1k papers) to confirm the\n", + "# connection + schema before the full run:\n", + "!python scripts/ingest.py --no-unlabeled" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Full ingestion (labeled + unlabeled). Safe to re-run: papers/chunks upsert by key.\n", + "!python scripts/ingest.py" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": {"provenance": [], "gpuType": "A100", "machine_shape": "hm"}, + "kernelspec": {"display_name": "Python 3", "name": "python3"}, + "language_info": {"name": "python"} + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/notebooks/02_benchmark.ipynb b/notebooks/02_benchmark.ipynb new file mode 100644 index 0000000..4ad293a --- /dev/null +++ b/notebooks/02_benchmark.ipynb @@ -0,0 +1,141 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# 02 — Benchmark: 4-arm GraphRAG vs PlainRAG ablation\n", + "\n", + "Thin Colab wrapper around `scripts/run_benchmark.py` and `scripts/compare.py`.\n", + "\n", + "**Use a GPU runtime** (a faster GPU mainly cuts wall-clock since this is\n", + "LLM-inference-bound). Add `ARANGO_PASS` and `ARANGO_HOST` in the Colab **Secrets**\n", + "panel. Run `01_ingest.ipynb` first.\n", + "\n", + "Arms (each isolates one component):\n", + "\n", + "| arm | adds |\n", + "| --- | --- |\n", + "| `plain` | vector top-k chunks (baseline) |\n", + "| `plain_rr` | + cross-encoder reranker |\n", + "| `graph` | + parent-paper expansion (full abstracts) |\n", + "| `graph_concepts` | + MeSH concept-hop expansion |\n", + "\n", + "The runner retries failed questions and auto-restarts Ollama if it crashes, and it\n", + "checkpoints every 25 questions — so a transient error can't abort an arm." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Confirm the GPU (optional).\n", + "!nvidia-smi --query-gpu=name,memory.total --format=csv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Reset-safe clone: always starts from /content and removes any prior copy,\n", + "# so re-running this cell can never nest a second checkout.\n", + "%cd /content\n", + "!rm -rf Knowledge_Graph_Question_Answering\n", + "!git clone -b main https://github.com/vardhjain/Knowledge_Graph_Question_Answering.git -q\n", + "%cd Knowledge_Graph_Question_Answering\n", + "!pip install -q -r requirements.txt" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Install Ollama and pull the LLM (once). The benchmark script manages the\n", + "# server from here on (health-check + auto-restart).\n", + "!which ollama || (apt-get install -y zstd -q && curl -fsSL https://ollama.com/install.sh | sh)\n", + "import subprocess, time\n", + "subprocess.Popen(['ollama', 'serve'])\n", + "time.sleep(5)\n", + "!ollama pull deepseek-r1:8b" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from google.colab import userdata\n", + "\n", + "# Connection settings from Colab Secrets (nothing hardcoded).\n", + "for key in ['ARANGO_PASS', 'ARANGO_HOST', 'ARANGO_DB']:\n", + " try:\n", + " val = userdata.get(key)\n", + " if val:\n", + " os.environ[key] = val\n", + " except Exception:\n", + " pass\n", + "assert os.environ.get('ARANGO_PASS'), 'Add ARANGO_PASS in the Secrets panel.'\n", + "\n", + "# Generation knobs (identical across arms, so the comparison is unaffected).\n", + "# Raise NUM_CTX on a large-VRAM GPU; lower it on a small one if you hit OOM.\n", + "os.environ['LLM_NUM_CTX'] = '8192'\n", + "os.environ['LLM_NUM_PREDICT'] = '1024'\n", + "print('ARANGO_HOST:', os.environ.get('ARANGO_HOST', '(default http://localhost:8529)'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Run all four arms. The chunk corpus is downloaded once and cached, then\n", + "# reused by every arm (identical corpus -> fair comparison). Each arm saves its\n", + "# own results JSON, so if one dies you can re-run just that arm.\n", + "for arm in ['plain', 'plain_rr', 'graph', 'graph_concepts']:\n", + " print(f'\\n===== {arm} =====')\n", + " !python scripts/run_benchmark.py --arm {arm} --n 200" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Summary table, paired McNemar tests, and the ablation figure.\n", + "!python scripts/compare.py\n", + "from IPython.display import Image, display, Markdown\n", + "display(Markdown(open('results/summary.md').read()))\n", + "display(Image('results/ablation.png'))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Optional: commit results back to GitHub (set a PAT first).\n", + "# !git config user.email you@example.com && git config user.name you\n", + "# !git add results/ && git commit -m 'Add benchmark results' && git push" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": {"provenance": [], "gpuType": "A100", "machine_shape": "hm"}, + "kernelspec": {"display_name": "Python 3", "name": "python3"}, + "language_info": {"name": "python"} + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7e1c4a0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,57 @@ +[build-system] +requires = ["setuptools>=68"] +build-backend = "setuptools.build_meta" + +[project] +name = "kgqa" +version = "1.0.0" +description = "Fair GraphRAG vs PlainRAG comparison on PubMedQA" +readme = "README.md" +requires-python = ">=3.10" +license = { text = "MIT" } +authors = [{ name = "Vardh Jain", email = "vardhjain20@gmail.com" }] +keywords = ["graphrag", "rag", "knowledge-graph", "pubmedqa", "arangodb", "llm", "ablation"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Science/Research", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Scientific/Engineering :: Artificial Intelligence", +] + +[project.urls] +Homepage = "https://github.com/vardhjain/Knowledge_Graph_Question_Answering" +Repository = "https://github.com/vardhjain/Knowledge_Graph_Question_Answering" +Issues = "https://github.com/vardhjain/Knowledge_Graph_Question_Answering/issues" + +[project.optional-dependencies] +dev = ["pytest>=8.0", "ruff>=0.4.0", "pre-commit>=3.5"] + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.pytest.ini_options] +pythonpath = ["src", "."] +testpaths = ["tests", "backend"] +addopts = "-q" + +[tool.coverage.run] +source = ["kgqa", "graphrag"] + +[tool.coverage.report] +show_missing = true + +[tool.ruff] +line-length = 100 +src = ["src", "scripts", "tests", "app", "backend"] +target-version = "py310" + +[tool.ruff.lint] +select = ["E", "F", "I", "W", "UP", "B"] +ignore = ["E501"] # line length handled by formatter; long AQL strings are fine + +[tool.ruff.lint.per-file-ignores] +"scripts/*" = ["E402"] # sys.path insert before imports is intentional +"app/*" = ["E402"] # same: sys.path setup precedes imports +"backend/*" = ["E402"] # same: sys.path setup precedes imports diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000..83307c9 --- /dev/null +++ b/render.yaml @@ -0,0 +1,33 @@ +# Render Blueprint -- https://render.com/docs/blueprint-spec +# Deploy: connect this repo on Render and it picks this file up automatically, +# or `render blueprint launch` from the CLI. Free tier: sleeps after 15 min of +# inactivity, ~30-50s cold start on the next request -- see +# .github/workflows/keep-warm.yml for the mitigation. +services: + - type: web + name: graphrag-agent-api + runtime: python + plan: free + region: oregon + buildCommand: pip install -r backend/requirements.txt + startCommand: uvicorn backend.main:app --host 0.0.0.0 --port $PORT + healthCheckPath: /health + envVars: + - key: PYTHON_VERSION + value: 3.11.9 + - key: CORS_ORIGINS + # Placeholder until the Phase 3 frontend is deployed -- update this to + # the Vercel URL then, or the frontend's requests will be CORS-blocked. + value: http://localhost:3000 + - key: GROQ_API_KEY + sync: false + - key: GEMINI_API_KEY + sync: false + - key: ARANGO_HOST + sync: false + - key: ARANGO_USER + sync: false + - key: ARANGO_PASS + sync: false + - key: ARANGO_DB + sync: false diff --git a/requirements-app.txt b/requirements-app.txt new file mode 100644 index 0000000..ea3ec7b --- /dev/null +++ b/requirements-app.txt @@ -0,0 +1,5 @@ +-r requirements.txt + +# Interactive UIs (app/) +gradio>=4.0 # app/chat_app.py — live GraphRAG chat demo +streamlit>=1.30 # app/dashboard.py — benchmark results dashboard diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..23b34e5 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,6 @@ +-r requirements.txt + +# Testing & linting (CI) +pytest>=8.0 +pytest-cov>=5.0 +ruff>=0.4.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..40a5ff9 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,23 @@ +# Core ML / retrieval +sentence-transformers>=2.7.0 +datasets>=2.18.0 +numpy>=1.24 +scikit-learn>=1.3 +scipy>=1.10 + +# Knowledge graph +python-arango>=7.9.0 + +# LLM client +requests>=2.31 + +# Plotting / reporting +matplotlib>=3.7 +seaborn>=0.13 +pandas>=2.0 + +# Notebooks / UI (optional at runtime, used by notebooks) +tqdm>=4.66 + +# Config +python-dotenv>=1.0 diff --git a/results/ablation.png b/results/ablation.png new file mode 100644 index 0000000..a09a649 Binary files /dev/null and b/results/ablation.png differ diff --git a/results/summary.json b/results/summary.json new file mode 100644 index 0000000..afae7fb --- /dev/null +++ b/results/summary.json @@ -0,0 +1,17 @@ +{ + "n": 200, + "seed": 42, + "model": "deepseek-r1:8b", + "dataset": "PubMedQA (pqa_labeled)", + "arms": [ + {"arm": "plain", "accuracy": 30.0, "macro_f1": 29.69, "avg_latency": 6.4, "samples": 200, "adds": "baseline chunk RAG"}, + {"arm": "plain_rr", "accuracy": 37.0, "macro_f1": 35.21, "avg_latency": 6.6, "samples": 200, "adds": "+ cross-encoder reranker"}, + {"arm": "graph", "accuracy": 59.5, "macro_f1": 50.51, "avg_latency": 7.5, "samples": 200, "adds": "+ parent-paper expansion"}, + {"arm": "graph_concepts", "accuracy": 57.5, "macro_f1": 49.97, "avg_latency": 40.8, "samples": 200, "adds": "+ MeSH concept hop"} + ], + "contrasts": [ + {"from": "plain", "to": "plain_rr", "effect": "reranker", "delta_acc": 7.0, "gains": 35, "losses": 21, "p_value": 0.0814, "significant": false}, + {"from": "plain_rr", "to": "graph", "effect": "parent expansion", "delta_acc": 22.5, "gains": 71, "losses": 26, "p_value": 0.0000, "significant": true}, + {"from": "graph", "to": "graph_concepts", "effect": "concept hop", "delta_acc": -2.0, "gains": 26, "losses": 30, "p_value": 0.6889, "significant": false} + ] +} diff --git a/results/summary.md b/results/summary.md new file mode 100644 index 0000000..8f41522 --- /dev/null +++ b/results/summary.md @@ -0,0 +1,14 @@ +| Arm | Accuracy | Macro F1 | Avg latency (s) | n | +| --- | --- | --- | --- | --- | +| plain | 30.00% | 29.69% | 6.4 | 200 | +| plain_rr | 37.00% | 35.21% | 6.6 | 200 | +| graph | 59.50% | 50.51% | 7.5 | 200 | +| graph_concepts | 57.50% | 49.97% | 40.8 | 200 | + +### Significance (paired McNemar) + +| Contrast | Δacc (pp) | gains | losses | p | sig? | +| --- | --- | --- | --- | --- | --- | +| plain → plain_rr (reranker effect) | +7.00 | 35 | 21 | 0.0814 | no | +| plain_rr → graph (parent-expansion effect) | +22.50 | 71 | 26 | 0.0000 | yes | +| graph → graph_concepts (concept-hop effect) | -2.00 | 26 | 30 | 0.6889 | no | diff --git a/scripts/compare.py b/scripts/compare.py new file mode 100644 index 0000000..5f9e4b0 --- /dev/null +++ b/scripts/compare.py @@ -0,0 +1,168 @@ +"""Aggregate arm results: summary table, McNemar tests, and figures. + + python scripts/compare.py + +Reads results/{arm}_results.json for whichever arms are present and writes +figures + a markdown snippet to results/. The McNemar tests are paired on pubid, +so they only run for arms evaluated on the same seeded sample. +""" + +from __future__ import annotations + +import json +import os +import sys + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) + +from kgqa.config import DATASET_NAME, LLM_MODEL, RANDOM_SEED # noqa: E402 +from kgqa.evaluation import mcnemar_test # noqa: E402 + +RESULTS_DIR = os.path.join(ROOT, "results") +ARM_ORDER = ["plain", "plain_rr", "graph", "graph_concepts"] +ARM_ADDS = { + "plain": "baseline chunk RAG", + "plain_rr": "+ cross-encoder reranker", + "graph": "+ parent-paper expansion", + "graph_concepts": "+ MeSH concept hop", +} +# Adjacent-arm contrasts that isolate each component's contribution. +CONTRASTS = [ + ("plain", "plain_rr", "reranker"), + ("plain_rr", "graph", "parent expansion"), + ("graph", "graph_concepts", "concept hop"), +] + + +def load_results(): + out = {} + for arm in ARM_ORDER: + path = os.path.join(RESULTS_DIR, f"{arm}_results.json") + if os.path.exists(path): + with open(path) as f: + out[arm] = json.load(f) + return out + + +def aligned(a, b): + """Align two arms' predictions on shared pubids (same seed -> same order).""" + ids_a = a.get("ids") or list(range(len(a["y_pred"]))) + ids_b = b.get("ids") or list(range(len(b["y_pred"]))) + idx_b = {sid: i for i, sid in enumerate(ids_b)} + gt, pa, pb = [], [], [] + for i, sid in enumerate(ids_a): + j = idx_b.get(sid) + if j is None: + continue + gt.append(a["y_true"][i]) + pa.append(a["y_pred"][i]) + pb.append(b["y_pred"][j]) + return gt, pa, pb + + +def main(): + results = load_results() + if not results: + print(f"No results found in {RESULTS_DIR}. Run scripts/run_benchmark.py first.") + sys.exit(1) + + lines = ["| Arm | Accuracy | Macro F1 | Avg latency (s) | n |", + "| --- | --- | --- | --- | --- |"] + arms_json, contrasts_json, max_n = [], [], 0 + print("\n" + "=" * 64) + print(" RESULTS SUMMARY") + print("=" * 64) + for arm in ARM_ORDER: + if arm not in results: + continue + r = results[arm] + acc, f1 = r["accuracy"] * 100, r.get("macro_f1", 0) * 100 + lat, n = r["avg_latency"], r["samples"] + max_n = max(max_n, n) + print(f" {arm:<16} acc={acc:6.2f}% f1={f1:6.2f}% lat={lat:5.1f}s n={n}") + lines.append(f"| {arm} | {acc:.2f}% | {f1:.2f}% | {lat:.1f} | {n} |") + arms_json.append({"arm": arm, "accuracy": round(acc, 2), "macro_f1": round(f1, 2), + "avg_latency": round(lat, 1), "samples": n, + "adds": ARM_ADDS.get(arm, "")}) + + print("\n" + "=" * 64) + print(" PAIRED McNEMAR TESTS (adjacent ablation contrasts)") + print("=" * 64) + lines += ["", "### Significance (paired McNemar)", "", + "| Contrast | Δacc (pp) | gains | losses | p | sig? |", + "| --- | --- | --- | --- | --- | --- |"] + for a_name, b_name, desc in CONTRASTS: + if a_name not in results or b_name not in results: + continue + gt, pa, pb = aligned(results[a_name], results[b_name]) + if not gt: + continue + test = mcnemar_test(gt, pa, pb) + acc_a = sum(p == g for p, g in zip(pa, gt, strict=False)) / len(gt) + acc_b = sum(p == g for p, g in zip(pb, gt, strict=False)) / len(gt) + d = (acc_b - acc_a) * 100 + sig = "yes" if test["significant_at_0.05"] else "no" + print(f" {a_name} -> {b_name} ({desc})") + print(f" Δacc={d:+.2f}pp gains={test['b_gains']} losses={test['c_losses']}" + f" p={test['p_value']:.4f} sig={sig}") + lines.append(f"| {a_name} → {b_name} ({desc}) | {d:+.2f} | {test['b_gains']} " + f"| {test['c_losses']} | {test['p_value']:.4f} | {sig} |") + contrasts_json.append({"from": a_name, "to": b_name, "effect": desc, + "delta_acc": round(d, 2), "gains": test["b_gains"], + "losses": test["c_losses"], + "p_value": round(test["p_value"], 4), + "significant": test["significant_at_0.05"]}) + + md_path = os.path.join(RESULTS_DIR, "summary.md") + with open(md_path, "w") as f: + f.write("\n".join(lines) + "\n") + print(f"\nWrote {md_path}") + + json_path = os.path.join(RESULTS_DIR, "summary.json") + with open(json_path, "w") as f: + json.dump({"n": max_n, "seed": RANDOM_SEED, "model": LLM_MODEL, + "dataset": "PubMedQA (pqa_labeled)" if "PubMedQA" in DATASET_NAME + else DATASET_NAME, + "arms": arms_json, "contrasts": contrasts_json}, f, indent=2) + print(f"Wrote {json_path}") + + _plot(results) + + +def _plot(results): + try: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + except Exception as exc: # pragma: no cover + print(f"(skipping figures: {exc})") + return + + arms = [a for a in ARM_ORDER if a in results] + accs = [results[a]["accuracy"] * 100 for a in arms] + f1s = [results[a].get("macro_f1", 0) * 100 for a in arms] + + fig, ax = plt.subplots(figsize=(9, 5)) + import numpy as np + x = np.arange(len(arms)) + w = 0.38 + ax.bar(x - w / 2, accs, w, label="Accuracy", color="#2196F3") + ax.bar(x + w / 2, f1s, w, label="Macro F1", color="#FF9800") + ax.set_xticks(x) + ax.set_xticklabels(arms, rotation=15) + ax.set_ylabel("%") + ax.set_ylim(0, 100) + ax.set_title("4-arm ablation — PubMedQA") + ax.legend() + for i, (a, f) in enumerate(zip(accs, f1s, strict=False)): + ax.text(i - w / 2, a + 1, f"{a:.1f}", ha="center", fontsize=8) + ax.text(i + w / 2, f + 1, f"{f:.1f}", ha="center", fontsize=8) + fig.tight_layout() + out = os.path.join(RESULTS_DIR, "ablation.png") + fig.savefig(out, dpi=150, bbox_inches="tight") + print(f"Wrote {out}") + + +if __name__ == "__main__": + main() diff --git a/scripts/ingest.py b/scripts/ingest.py new file mode 100644 index 0000000..311e151 --- /dev/null +++ b/scripts/ingest.py @@ -0,0 +1,139 @@ +"""Build the ArangoDB knowledge graph from PubMedQA — leakage-free schema. + +Differences from the original ingestion (the fairness fixes): + * Papers store NO question-derived title and NO final_decision, so the + benchmark question/answer can never leak into a retrieved context. + * Chunks carry an explicit ``paper_key`` for fast, unambiguous corpus loading. + +Run ONCE before benchmarking: + export ARANGO_PASS=... # or set in PowerShell / Colab Secrets + python scripts/ingest.py +""" + +from __future__ import annotations + +import argparse +import os +import sys +import time + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) + +from arango import ArangoClient # noqa: E402 +from datasets import load_dataset # noqa: E402 +from sentence_transformers import SentenceTransformer # noqa: E402 +from tqdm import tqdm # noqa: E402 + +from kgqa.config import ( # noqa: E402 + DATASET_NAME, + EDGE_COLLECTIONS, + EMBEDDING_MODEL, + LABELED_CONFIG, + NODE_COLLECTIONS, + UNLABELED_CONFIG, + ArangoConfig, +) + + +def setup_schema(db): + for col in NODE_COLLECTIONS: + if not db.has_collection(col): + db.create_collection(col) + print(f" created node collection: {col}") + for col in EDGE_COLLECTIONS: + if not db.has_collection(col): + db.create_collection(col, edge=True) + print(f" created edge collection: {col}") + + +def ingest_split(db, dataset, model, on_duplicate_paper="ignore", batch_size=50): + papers, chunks, concepts, has_ctx, mentions = [], [], [], [], [] + count = 0 + + def flush(): + if papers: + db.collection("Papers").import_bulk(papers, on_duplicate=on_duplicate_paper) + if concepts: + db.collection("Concepts").import_bulk(concepts, on_duplicate="ignore") + if chunks: + db.collection("Chunks").import_bulk(chunks, on_duplicate="ignore") + if has_ctx: + db.collection("HAS_CONTEXT").import_bulk(has_ctx, on_duplicate="ignore") + if mentions: + db.collection("MENTIONS").import_bulk(mentions, on_duplicate="ignore") + for buf in (papers, chunks, concepts, has_ctx, mentions): + buf.clear() + + for row in tqdm(dataset): + paper_key = str(row["pubid"]) + # Leakage-free Paper node: no title, no final_decision. + papers.append({"_key": paper_key}) + + for mesh in row.get("context", {}).get("meshes", []): + mesh_key = "".join(c for c in mesh if c.isalnum()) + if not mesh_key: + continue + concepts.append({"_key": mesh_key, "name": mesh}) + mentions.append({"_from": f"Papers/{paper_key}", "_to": f"Concepts/{mesh_key}"}) + + ctx_texts = row.get("context", {}).get("contexts", []) + ctx_labels = row.get("context", {}).get("labels", []) + if ctx_texts: + embeddings = model.encode(ctx_texts) + for idx, (text, emb) in enumerate(zip(ctx_texts, embeddings, strict=False)): + chunk_key = f"{paper_key}_{idx}" + chunks.append({ + "_key": chunk_key, + "paper_key": paper_key, + "text": text, + "label": ctx_labels[idx] if idx < len(ctx_labels) else "context", + "embedding": emb.tolist(), + }) + has_ctx.append({"_from": f"Papers/{paper_key}", "_to": f"Chunks/{chunk_key}"}) + + count += 1 + if count % batch_size == 0: + flush() + flush() + return count + + +def main(): + parser = argparse.ArgumentParser(description="Ingest PubMedQA into ArangoDB.") + parser.add_argument("--no-unlabeled", action="store_true", + help="Ingest only the labeled split (faster, for testing).") + args = parser.parse_args() + + cfg = ArangoConfig() + cfg.require_password() + client = ArangoClient(hosts=cfg.host) + sys_db = client.db("_system", username=cfg.user, password=cfg.password) + if not sys_db.has_database(cfg.db_name): + sys_db.create_database(cfg.db_name) + print(f"created database: {cfg.db_name}") + db = client.db(cfg.db_name, username=cfg.user, password=cfg.password) + + setup_schema(db) + model = SentenceTransformer(EMBEDDING_MODEL) + + if not args.no_unlabeled: + print("Ingesting pqa_unlabeled...") + ds = load_dataset(DATASET_NAME, UNLABELED_CONFIG, split="train") + t0 = time.time() + n = ingest_split(db, ds, model, on_duplicate_paper="ignore") + print(f" {n:,} papers in {time.time() - t0:.1f}s") + + print("Ingesting pqa_labeled...") + ds = load_dataset(DATASET_NAME, LABELED_CONFIG, split="train") + t0 = time.time() + n = ingest_split(db, ds, model, on_duplicate_paper="update") + print(f" {n:,} papers in {time.time() - t0:.1f}s") + + print("\nCollection counts:") + for col in (*NODE_COLLECTIONS, *EDGE_COLLECTIONS): + print(f" {col:<15}: {db.collection(col).count():>8,}") + + +if __name__ == "__main__": + main() diff --git a/scripts/run_benchmark.py b/scripts/run_benchmark.py new file mode 100644 index 0000000..fa0f787 --- /dev/null +++ b/scripts/run_benchmark.py @@ -0,0 +1,175 @@ +"""Run one arm of the GraphRAG vs PlainRAG ablation on PubMedQA. + + python scripts/run_benchmark.py --arm plain_rr --n 200 + +Arms: + plain vector top-k chunks (baseline) + plain_rr + cross-encoder rerank + graph + parent-paper expansion (full abstracts) + graph_concepts + MeSH concept-hop expansion + +All arms share one ArangoDB-backed chunk corpus (cached locally), the same +encoder, reranker, prompt, LLM, seed and sample — so results are comparable and +the only moving part is the retrieval strategy named by --arm. + +Resilience: each question is retried, and a wedged/crashed Ollama is restarted +between attempts, so a single 500/timeout cannot abort the whole arm. Partial +results are checkpointed every 25 questions. +""" + +from __future__ import annotations + +import argparse +import os +import subprocess +import sys +import time + +import requests + +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, os.path.join(ROOT, "src")) + +ARMS = ("plain", "plain_rr", "graph", "graph_concepts") +MAX_TRIES = 3 +CHECKPOINT_EVERY = 25 + + +def _ollama_base(api_url: str) -> str: + return api_url.split("/api/")[0] + + +def _ollama_healthy(api_url: str, timeout: int = 5) -> bool: + try: + return requests.get(_ollama_base(api_url) + "/api/tags", timeout=timeout).ok + except Exception: + return False + + +def ensure_ollama(api_url: str, model: str, restart: bool = False, wait: int = 90) -> bool: + """Make sure a healthy Ollama is serving; (re)start it if not.""" + import shutil + + if restart: + try: + subprocess.run(["pkill", "-f", "ollama"], capture_output=True) + time.sleep(3) + except FileNotFoundError: + pass # no pkill (e.g. Windows) — fall through and try to start + + if not restart and _ollama_healthy(api_url): + return True + + ollama = shutil.which("ollama") or "/usr/local/bin/ollama" + try: + subprocess.Popen([ollama, "serve"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + except FileNotFoundError: + print("[Ollama] binary not found; assuming a server is reachable elsewhere.") + + deadline = time.time() + wait + while time.time() < deadline: + if _ollama_healthy(api_url): + try: # warm the model so the next real call isn't a cold load + requests.post( + _ollama_base(api_url) + "/api/generate", + json={"model": model, "prompt": "ok", "stream": False, + "keep_alive": "30m", "options": {"num_predict": 1}}, + timeout=180, + ) + except Exception: + pass + return True + time.sleep(2) + print("[Ollama] WARNING: server did not become healthy in time.") + return False + + +def build_retriever(arm, store, encoder, reranker, db): + from kgqa.retrieval import GraphRetriever, PlainRetriever + + if arm == "plain": + return PlainRetriever(store, encoder, reranker=None) + if arm == "plain_rr": + return PlainRetriever(store, encoder, reranker=reranker) + if arm == "graph": + return GraphRetriever(store, encoder, db, reranker=reranker, use_concepts=False) + if arm == "graph_concepts": + return GraphRetriever(store, encoder, db, reranker=reranker, use_concepts=True) + raise ValueError(f"unknown arm: {arm}") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--arm", required=True, choices=ARMS) + parser.add_argument("--n", type=int, default=None, help="sample size (default: config BENCHMARK_N)") + parser.add_argument("--seed", type=int, default=None, help="random seed (default: config RANDOM_SEED)") + parser.add_argument("--output", default=None, help="results JSON path") + parser.add_argument("--no-ollama-start", action="store_true", + help="don't auto-start/health-check the Ollama server") + args = parser.parse_args() + + from kgqa.config import BENCHMARK_N, LLM_MODEL, OLLAMA_API, RANDOM_SEED, ArangoConfig + from kgqa.data import load_benchmark_samples + from kgqa.evaluation import Evaluator, FuzzyEvaluator + from kgqa.models import connect_arango, load_encoder, load_reranker + from kgqa.retrieval import ChunkStore + + if not args.no_ollama_start: + print("[Ollama] Ensuring server is healthy...") + ensure_ollama(OLLAMA_API, LLM_MODEL) + + n = args.n or BENCHMARK_N + seed = args.seed if args.seed is not None else RANDOM_SEED + results_dir = os.path.join(ROOT, "results") + os.makedirs(results_dir, exist_ok=True) + out_path = args.output or os.path.join(results_dir, f"{args.arm}_results.json") + cache_file = os.path.join(ROOT, "pubmed_vectors_cache.pkl") + + db = connect_arango(ArangoConfig()) + print("[Corpus] Loading chunk store from ArangoDB (cached after first run)...") + store = ChunkStore.from_arango(db, cache_file=cache_file) + print(f"[Corpus] {len(store):,} chunks loaded.") + + encoder = load_encoder() + reranker = load_reranker() if args.arm != "plain" else None + + retriever = build_retriever(args.arm, store, encoder, reranker, db) + samples = load_benchmark_samples(n=n, seed=seed) + + fuzzy = FuzzyEvaluator() + evaluator = Evaluator(args.arm) + print(f"\n=== Benchmark: {args.arm} (n={len(samples)}, seed={seed}) ===") + for i, s in enumerate(samples): + t0 = time.time() + raw = None + for attempt in range(1, MAX_TRIES + 1): + try: + raw = retriever.answer_benchmark(s.question) + break + except Exception as exc: + print(f" [warn] q{i + 1} attempt {attempt}/{MAX_TRIES} failed: " + f"{type(exc).__name__}: {exc}") + if attempt < MAX_TRIES and not args.no_ollama_start: + ensure_ollama(OLLAMA_API, LLM_MODEL, restart=True) + latency = time.time() - t0 + + if raw is None: + pred = "maybe" # last resort so one bad call doesn't abort the arm + print(f"[{i + 1:3d}] GT={s.final_decision:<5} Pred={pred:<5} ! " + f"(skipped after {MAX_TRIES} tries)") + else: + pred = fuzzy.extract_answer(raw) + icon = "v" if pred == s.final_decision.lower().strip() else "x" + print(f"[{i + 1:3d}] GT={s.final_decision:<5} Pred={pred:<5} {icon} ({latency:.1f}s)") + + evaluator.record(s.final_decision, pred, latency, sample_id=s.pubid) + if (i + 1) % CHECKPOINT_EVERY == 0: + evaluator.save(out_path) # checkpoint partial progress + + evaluator.report() + evaluator.save(out_path) + + +if __name__ == "__main__": + main() diff --git a/src/graphrag/__init__.py b/src/graphrag/__init__.py new file mode 100644 index 0000000..be88156 --- /dev/null +++ b/src/graphrag/__init__.py @@ -0,0 +1,12 @@ +"""Public package for the hosted GraphRAG agent. + +Thin re-export over ``kgqa.service`` -- the research/benchmark code lives in +``kgqa``, this is the stable import surface a web backend (FastAPI, etc.) +depends on: ``from graphrag import answer``. +""" + +from __future__ import annotations + +from kgqa.service import answer + +__all__ = ["answer"] diff --git a/src/kgqa/__init__.py b/src/kgqa/__init__.py new file mode 100644 index 0000000..27cf1c6 --- /dev/null +++ b/src/kgqa/__init__.py @@ -0,0 +1,14 @@ +"""Knowledge Graph Question Answering — fair GraphRAG vs PlainRAG comparison. + +A 4-arm ablation on PubMedQA that isolates exactly what a knowledge graph +contributes to retrieval-augmented QA, holding every other layer constant +(corpus, chunking, embedder, reranker, prompt, LLM, top-k). + +Arms: + plain vector search -> top-k chunks (baseline) + plain_rr vector search -> cross-encoder rerank -> top-k chunks + graph plain_rr -> parent-paper expansion (full abstracts) + graph_concepts graph -> MeSH concept-hop expansion (related papers) +""" + +__version__ = "1.0.0" diff --git a/src/kgqa/config.py b/src/kgqa/config.py new file mode 100644 index 0000000..fa14d3f --- /dev/null +++ b/src/kgqa/config.py @@ -0,0 +1,75 @@ +"""Central configuration — the single source of truth for every constant. + +Every arm of the comparison reads from here, so the *only* differences between +PlainRAG and GraphRAG are the retrieval strategy and context assembly. Anything +that could confound the comparison (embedder, reranker, prompt, LLM, top-k, +sample size, seed) lives in this file and nowhere else. +""" + +from __future__ import annotations + +import os +from dataclasses import dataclass, field + +try: # optional: load a local .env if python-dotenv is installed + from dotenv import load_dotenv + + load_dotenv() +except Exception: # pragma: no cover - dotenv is optional + pass + + +# ── Shared models (identical across all arms) ───────────────────────────────── +EMBEDDING_MODEL = "sentence-transformers/all-MiniLM-L6-v2" # 384-dim +CROSS_ENCODER = "cross-encoder/ms-marco-MiniLM-L-6-v2" +LLM_MODEL = os.environ.get("LLM_MODEL", "deepseek-r1:8b") + +# ── Retrieval hyper-parameters (identical across all arms) ──────────────────── +TOP_K_FINAL = 3 # documents handed to the LLM +TOP_K_CANDIDATES = 75 # wide pool fed to the reranker (rerank arms only) +CONCEPT_HOP_PAPERS = 3 # extra related papers pulled in by the concept arm + +# ── Benchmark protocol (identical across all arms) ──────────────────────────── +BENCHMARK_N = int(os.environ.get("BENCHMARK_N", "200")) +RANDOM_SEED = int(os.environ.get("RANDOM_SEED", "42")) +DATASET_NAME = "qiaojin/PubMedQA" +LABELED_CONFIG = "pqa_labeled" +UNLABELED_CONFIG = "pqa_unlabeled" + +# ── LLM serving ─────────────────────────────────────────────────────────────── +OLLAMA_API = os.environ.get("OLLAMA_API", "http://localhost:11434/api/chat") +LLM_TEMPERATURE = 0.0 # deterministic for benchmarking +# Env-tunable so the run can be sized to the GPU without code changes. num_predict +# caps generation so a runaway reasoning chain can't stall (or crash) the server; +# the answer extractor tolerates a truncated chain. Lower NUM_CTX to 4096 on a +# small-VRAM GPU (e.g. T4) if you hit out-of-memory 500s. +LLM_NUM_CTX = int(os.environ.get("LLM_NUM_CTX", "4096")) +LLM_NUM_PREDICT = int(os.environ.get("LLM_NUM_PREDICT", "1024")) +LLM_KEEP_ALIVE = os.environ.get("LLM_KEEP_ALIVE", "30m") +LLM_TIMEOUT = int(os.environ.get("LLM_TIMEOUT", "180")) + +# ── Graph schema (must match scripts/ingest.py) ─────────────────────────────── +NODE_COLLECTIONS = ("Papers", "Chunks", "Concepts") +EDGE_COLLECTIONS = ("HAS_CONTEXT", "MENTIONS") +HAS_CONTEXT = "HAS_CONTEXT" # Paper -> Chunk +MENTIONS = "MENTIONS" # Paper -> Concept + + +@dataclass +class ArangoConfig: + """ArangoDB Oasis connection settings, read from the environment.""" + + host: str = field(default_factory=lambda: os.environ.get( + "ARANGO_HOST", "http://localhost:8529")) + user: str = field(default_factory=lambda: os.environ.get("ARANGO_USER", "root")) + password: str = field(default_factory=lambda: os.environ.get("ARANGO_PASS", "")) + db_name: str = field(default_factory=lambda: os.environ.get("ARANGO_DB", "pubmed_graph")) + + def require_password(self) -> None: + if not self.password: + raise OSError( + "ARANGO_PASS is not set. Set it before connecting:\n" + ' PowerShell : $env:ARANGO_PASS = "your_password"\n' + " bash : export ARANGO_PASS=your_password\n" + " Colab : add ARANGO_PASS in the Secrets panel" + ) diff --git a/src/kgqa/data.py b/src/kgqa/data.py new file mode 100644 index 0000000..d4584e8 --- /dev/null +++ b/src/kgqa/data.py @@ -0,0 +1,77 @@ +"""Dataset loading, seeded sampling, and chunk-corpus construction. + +The chunk corpus is built the same way the graph is ingested (per-section +chunks from the labeled + unlabeled splits), so every arm retrieves over an +identical pool of documents. +""" + +from __future__ import annotations + +import random +from dataclasses import dataclass + +from .config import ( + BENCHMARK_N, + DATASET_NAME, + LABELED_CONFIG, + RANDOM_SEED, + UNLABELED_CONFIG, +) + + +@dataclass +class BenchmarkSample: + pubid: str + question: str + final_decision: str + + +def load_benchmark_samples(n: int = BENCHMARK_N, seed: int = RANDOM_SEED) -> list[BenchmarkSample]: + """Return a deterministic random sample of labeled PubMedQA questions. + + Uses a seeded shuffle so the same questions are evaluated across every arm + and across re-runs — a prerequisite for the paired McNemar test. + """ + from datasets import load_dataset + + ds = load_dataset(DATASET_NAME, LABELED_CONFIG, split="train") + indices = list(range(len(ds))) + random.Random(seed).shuffle(indices) + + samples: list[BenchmarkSample] = [] + for idx in indices: + item = ds[idx] + decision = item.get("final_decision") + if not item.get("question") or not decision: + continue + samples.append(BenchmarkSample( + pubid=str(item["pubid"]), + question=item["question"], + final_decision=decision, + )) + if len(samples) >= n: + break + return samples + + +def iter_chunks(include_unlabeled: bool = True): + """Yield ``(paper_key, chunk_index, text)`` for every abstract section. + + This is the canonical chunking used both at ingestion time and when + building the in-memory PlainRAG corpus, guaranteeing an identical document + pool across arms. + """ + from datasets import load_dataset + + configs = [LABELED_CONFIG] + if include_unlabeled: + configs.append(UNLABELED_CONFIG) + + for config in configs: + ds = load_dataset(DATASET_NAME, config, split="train") + for item in ds: + paper_key = str(item["pubid"]) + contexts = item.get("context", {}).get("contexts", []) + for idx, text in enumerate(contexts): + if text and text.strip(): + yield paper_key, idx, text diff --git a/src/kgqa/evaluation.py b/src/kgqa/evaluation.py new file mode 100644 index 0000000..d53a79b --- /dev/null +++ b/src/kgqa/evaluation.py @@ -0,0 +1,131 @@ +"""Answer extraction, metrics, and significance testing. + +Kept free of any plotting import at module load so it is importable in headless +CI. Figure generation lives in ``scripts/compare.py``. +""" + +from __future__ import annotations + +import json +import re +from dataclasses import dataclass, field + +from sklearn.metrics import accuracy_score, classification_report, f1_score + +LABELS = ("yes", "no", "maybe") + + +class FuzzyEvaluator: + """Extracts a normalised yes/no/maybe from verbose model output.""" + + def extract_answer(self, text: str) -> str: + clean = re.sub(r".*?", "", text, flags=re.DOTALL).lower() + match = re.search(r"final answer\s*:\s*(yes|no|maybe)", clean) + if match: + return match.group(1) + matches = re.findall(r"\b(yes|no|maybe)\b", clean) + return matches[-1] if matches else "maybe" + + +@dataclass +class Evaluator: + """Accumulates predictions and computes plot-free metrics. + + ``ids`` records the dataset pubid of each sample so a paired significance + test (McNemar) can be run across arms on exactly the same questions. + """ + + model_name: str + y_true: list = field(default_factory=list) + y_pred: list = field(default_factory=list) + latencies: list = field(default_factory=list) + ids: list = field(default_factory=list) + + def record(self, ground_truth: str, prediction: str, + latency: float = 0.0, sample_id: str | None = None) -> None: + pred = prediction.lower().strip() + if pred not in LABELS: + pred = "maybe" + self.y_true.append(ground_truth.lower().strip()) + self.y_pred.append(pred) + self.latencies.append(latency) + self.ids.append(sample_id) + + # ── metrics ─────────────────────────────────────────────────────────────── + def accuracy(self) -> float: + return accuracy_score(self.y_true, self.y_pred) if self.y_true else 0.0 + + def macro_f1(self) -> float: + if not self.y_true: + return 0.0 + return f1_score(self.y_true, self.y_pred, labels=list(LABELS), + average="macro", zero_division=0) + + def avg_latency(self) -> float: + return sum(self.latencies) / len(self.latencies) if self.latencies else 0.0 + + def summary(self) -> dict: + return { + "model": self.model_name, + "accuracy": self.accuracy(), + "macro_f1": self.macro_f1(), + "samples": len(self.y_true), + "total_time": sum(self.latencies), + "avg_latency": self.avg_latency(), + "y_true": self.y_true, + "y_pred": self.y_pred, + "ids": self.ids, + } + + def report(self) -> dict: + if not self.y_true: + print("No data recorded.") + return {} + print(f"\n{'=' * 52}") + print(f" {self.model_name} — Evaluation Report") + print(f"{'=' * 52}") + print(f" Samples : {len(self.y_true)}") + print(f" Accuracy : {self.accuracy():.2%}") + print(f" Macro F1 : {self.macro_f1():.2%}") + print(f" Avg/query : {self.avg_latency():.1f}s") + print(f"{'-' * 52}") + print(classification_report(self.y_true, self.y_pred, + labels=list(LABELS), zero_division=0)) + return self.summary() + + def save(self, path: str) -> None: + with open(path, "w") as f: + json.dump(self.summary(), f, indent=2) + print(f"Results saved to {path}") + + +def mcnemar_test(y_true: list, pred_a: list, pred_b: list) -> dict: + """Paired McNemar test: is arm B's accuracy change over arm A significant? + + Compares the two arms only on the samples where exactly one is correct + (the discordant pairs). Uses the exact binomial test, which is valid for + the small discordant counts typical of n~200 benchmarks. + """ + from scipy.stats import binomtest + + if not (len(y_true) == len(pred_a) == len(pred_b)): + raise ValueError("y_true, pred_a, pred_b must be the same length") + + # b: A wrong, B right (B's gains). c: A right, B wrong (B's losses). + b = c = 0 + for gt, a, bb in zip(y_true, pred_a, pred_b, strict=False): + a_ok, b_ok = (a == gt), (bb == gt) + if a_ok and not b_ok: + c += 1 + elif b_ok and not a_ok: + b += 1 + + n = b + c + p_value = float(binomtest(b, n, 0.5).pvalue) if n > 0 else 1.0 + return { + "b_gains": b, # B right, A wrong + "c_losses": c, # A right, B wrong + "discordant": n, + "p_value": p_value, + "significant_at_0.05": bool(p_value < 0.05), + } diff --git a/src/kgqa/llm.py b/src/kgqa/llm.py new file mode 100644 index 0000000..94b956c --- /dev/null +++ b/src/kgqa/llm.py @@ -0,0 +1,44 @@ +"""Thin Ollama client — the single LLM entry point shared by all arms.""" + +from __future__ import annotations + +import requests + +from .config import ( + LLM_KEEP_ALIVE, + LLM_MODEL, + LLM_NUM_CTX, + LLM_NUM_PREDICT, + LLM_TEMPERATURE, + LLM_TIMEOUT, + OLLAMA_API, +) + + +def call_ollama( + prompt: str, + system: str = "", + temperature: float = LLM_TEMPERATURE, + model: str = LLM_MODEL, + api_url: str = OLLAMA_API, +) -> str: + """Single synchronous chat completion against a local Ollama server.""" + messages = [] + if system: + messages.append({"role": "system", "content": system}) + messages.append({"role": "user", "content": prompt}) + + payload = { + "model": model, + "messages": messages, + "stream": False, + "keep_alive": LLM_KEEP_ALIVE, # keep the model resident across the run + "options": { + "temperature": temperature, + "num_ctx": LLM_NUM_CTX, + "num_predict": LLM_NUM_PREDICT, # cap generation so a call can't run away + }, + } + resp = requests.post(api_url, json=payload, timeout=LLM_TIMEOUT) + resp.raise_for_status() + return resp.json()["message"]["content"] diff --git a/src/kgqa/models.py b/src/kgqa/models.py new file mode 100644 index 0000000..6af8a47 --- /dev/null +++ b/src/kgqa/models.py @@ -0,0 +1,44 @@ +"""Lazy loaders for the shared embedder and reranker. + +Kept here so every script and notebook instantiates the *same* models the same +way. Imports are local so the package can be imported without the heavy ML deps +installed (e.g. in unit tests that inject fakes).""" + +from __future__ import annotations + +from .config import CROSS_ENCODER, EMBEDDING_MODEL + + +def load_encoder(model_name: str = EMBEDDING_MODEL, device: str | None = None): + from sentence_transformers import SentenceTransformer + + return SentenceTransformer(model_name, device=device) + + +def load_reranker(model_name: str = CROSS_ENCODER, device: str | None = None): + from sentence_transformers import CrossEncoder + + return CrossEncoder(model_name, device=device) + + +def connect_arango(cfg, max_retries: int = 5): + """Connect to ArangoDB Oasis with retries. ``cfg`` is an ArangoConfig.""" + import time + + from arango import ArangoClient + from arango.exceptions import ArangoServerError, ServerConnectionError + + cfg.require_password() + client = ArangoClient(hosts=cfg.host) + for attempt in range(max_retries): + try: + sys_db = client.db("_system", username=cfg.user, password=cfg.password) + sys_db.version() + db = client.db(cfg.db_name, username=cfg.user, password=cfg.password) + print("[ArangoDB] Connected.") + return db + except (ServerConnectionError, ArangoServerError): + wait = (attempt + 1) * 5 + print(f"[ArangoDB] Attempt {attempt + 1} failed. Retrying in {wait}s...") + time.sleep(wait) + raise ConnectionError("Could not connect to ArangoDB.") diff --git a/src/kgqa/prompts.py b/src/kgqa/prompts.py new file mode 100644 index 0000000..ca67313 --- /dev/null +++ b/src/kgqa/prompts.py @@ -0,0 +1,28 @@ +"""Prompts — word-for-word identical across every arm. + +The benchmark prompt classifies a PubMedQA question as yes/no/maybe. It is the +same string for PlainRAG and GraphRAG; only the retrieved ``context`` differs. +""" + +BENCHMARK_SYSTEM_PROMPT = ( + "You are a PubMedQA annotator. Classify the answer as yes, no, or maybe.\n\n" + "Guidelines:\n" + "- YES : the study finds a positive outcome, correlation, or association,\n" + " even if further research is recommended.\n" + "- NO : the study finds no significant difference or a negative result.\n" + "- MAYBE: only if the abstract explicitly states inconclusive results\n" + " with no supporting data.\n\n" + "End your response with exactly: Final Answer: [yes/no/maybe]" +) + +CHAT_SYSTEM_PROMPT = ( + "You are a helpful medical AI assistant. " + "Use the provided research abstracts to answer the user question. " + "If studies conflict, explain the conflict. " + "If the context is insufficient, say so and give your best assessment." +) + + +def build_prompt(context: str, question: str) -> str: + """Assemble the user-turn prompt — identical structure for every arm.""" + return f"Context:\n{context}\n\nQuestion: {question}" diff --git a/src/kgqa/providers.py b/src/kgqa/providers.py new file mode 100644 index 0000000..e964b60 --- /dev/null +++ b/src/kgqa/providers.py @@ -0,0 +1,104 @@ +"""Multi-provider LLM client with per-task defaults and automatic fallback. + +Free-tier providers deprecate models without notice, so every call site picks +a *task* ("decompose", "extract", "synthesize"), not a provider directly. Each +task has a configured provider chain (primary, then fallbacks); if the primary +errors or its API key is unset, the next provider in the chain is tried. + +Task defaults (overridable via env, see ``_CHAINS`` below): + decompose / extract -> Groq (fast, free, several calls per question) + synthesize -> Gemini Flash (bigger context for the retrieved subgraph) +Both fall back to a local Ollama call so the service still works with no cloud +API keys configured at all (e.g. in tests or offline dev). +""" + +from __future__ import annotations + +import os + +import requests + +from .config import LLM_TEMPERATURE +from .llm import call_ollama + +GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "") +GROQ_MODEL = os.environ.get("GROQ_MODEL", "llama-3.1-8b-instant") +GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions" + +GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "") +GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-1.5-flash") +GEMINI_API_URL = ( + f"https://generativelanguage.googleapis.com/v1beta/models/{GEMINI_MODEL}:generateContent" +) + + +def call_groq(prompt: str, system: str = "", temperature: float = LLM_TEMPERATURE) -> str: + if not GROQ_API_KEY: + raise RuntimeError("GROQ_API_KEY is not set") + messages = [] + if system: + messages.append({"role": "system", "content": system}) + messages.append({"role": "user", "content": prompt}) + resp = requests.post( + GROQ_API_URL, + headers={"Authorization": f"Bearer {GROQ_API_KEY}"}, + json={"model": GROQ_MODEL, "messages": messages, "temperature": temperature}, + timeout=60, + ) + resp.raise_for_status() + return resp.json()["choices"][0]["message"]["content"] + + +def call_gemini(prompt: str, system: str = "", temperature: float = LLM_TEMPERATURE) -> str: + if not GEMINI_API_KEY: + raise RuntimeError("GEMINI_API_KEY is not set") + text = f"{system}\n\n{prompt}" if system else prompt + resp = requests.post( + GEMINI_API_URL, + params={"key": GEMINI_API_KEY}, + json={ + "contents": [{"parts": [{"text": text}]}], + "generationConfig": {"temperature": temperature}, + }, + timeout=60, + ) + resp.raise_for_status() + return resp.json()["candidates"][0]["content"]["parts"][0]["text"] + + +def _call_ollama_task(prompt: str, system: str = "", temperature: float = LLM_TEMPERATURE) -> str: + return call_ollama(prompt, system=system, temperature=temperature) + + +# Provider chains per task: (name, fn). Order matters -- first that succeeds wins. +_PROVIDERS = { + "groq": call_groq, + "gemini": call_gemini, + "ollama": _call_ollama_task, +} + +_CHAINS = { + "decompose": os.environ.get("LLM_CHAIN_DECOMPOSE", "groq,ollama").split(","), + "extract": os.environ.get("LLM_CHAIN_EXTRACT", "groq,ollama").split(","), + "synthesize": os.environ.get("LLM_CHAIN_SYNTHESIZE", "gemini,ollama").split(","), +} + + +def call_llm(task: str, prompt: str, system: str = "", temperature: float = LLM_TEMPERATURE) -> str: + """Run ``prompt`` through the provider chain configured for ``task``. + + Tries each provider in order, falling back on any exception (missing key, + network error, rate limit) so a single provider outage or deprecation + doesn't take the whole service down. + """ + chain = _CHAINS.get(task, ["ollama"]) + errors = [] + for name in chain: + fn = _PROVIDERS.get(name.strip()) + if fn is None: + continue + try: + return fn(prompt, system=system, temperature=temperature) + except Exception as exc: # noqa: BLE001 - deliberately broad, this is a fallback chain + errors.append(f"{name}: {exc}") + raise RuntimeError(f"All providers failed for task '{task}': {'; '.join(errors)}") diff --git a/src/kgqa/retrieval/__init__.py b/src/kgqa/retrieval/__init__.py new file mode 100644 index 0000000..b38798a --- /dev/null +++ b/src/kgqa/retrieval/__init__.py @@ -0,0 +1,13 @@ +"""Retrieval arms for the GraphRAG vs PlainRAG ablation.""" + +from .base import BaseRetriever, Candidate, ChunkStore +from .graph import GraphRetriever +from .plain import PlainRetriever + +__all__ = [ + "BaseRetriever", + "ChunkStore", + "Candidate", + "PlainRetriever", + "GraphRetriever", +] diff --git a/src/kgqa/retrieval/base.py b/src/kgqa/retrieval/base.py new file mode 100644 index 0000000..0bfbb4b --- /dev/null +++ b/src/kgqa/retrieval/base.py @@ -0,0 +1,181 @@ +"""Shared retrieval scaffolding. + +``ChunkStore`` is the single document pool every arm searches over, so the +corpus, chunking, and embeddings are provably identical across arms. +``BaseRetriever`` owns the encode -> (optional) rerank -> select pipeline; each +subclass only customises how the selected chunks become an LLM context string. +""" + +from __future__ import annotations + +import pickle +from abc import ABC, abstractmethod +from dataclasses import dataclass + +import numpy as np + +from ..config import TOP_K_CANDIDATES, TOP_K_FINAL +from ..llm import call_ollama +from ..prompts import BENCHMARK_SYSTEM_PROMPT, CHAT_SYSTEM_PROMPT, build_prompt + + +@dataclass +class Candidate: + """A retrieved chunk plus its provenance.""" + + chunk_id: str # ArangoDB _id or local id, e.g. "Chunks/12345_0" + paper_key: str # owning paper, e.g. "12345" + text: str + score: float = 0.0 + + +def _normalize(matrix: np.ndarray) -> np.ndarray: + norms = np.linalg.norm(matrix, axis=1, keepdims=True) + norms[norms == 0] = 1.0 + return matrix / norms + + +class ChunkStore: + """In-memory, L2-normalised chunk embeddings with cosine search.""" + + def __init__(self, ids: list[str], paper_keys: list[str], + texts: list[str], embeddings: np.ndarray): + self.ids = ids + self.paper_keys = paper_keys + self.texts = texts + self.embeddings = _normalize(np.asarray(embeddings, dtype=np.float32)) \ + if len(embeddings) else np.zeros((0, 0), dtype=np.float32) + + def __len__(self) -> int: + return len(self.ids) + + def search(self, query_emb: np.ndarray, k: int) -> list[int]: + """Return indices of the top-k chunks by cosine similarity.""" + if len(self) == 0: + return [] + q = _normalize(np.atleast_2d(np.asarray(query_emb, dtype=np.float32))) + sims = (self.embeddings @ q[0]) + k = min(k, len(self)) + top = np.argpartition(sims, -k)[-k:] + return list(top[np.argsort(sims[top])[::-1]]) + + def candidate(self, idx: int, score: float = 0.0) -> Candidate: + return Candidate(self.ids[idx], self.paper_keys[idx], self.texts[idx], score) + + # ── builders ─────────────────────────────────────────────────────────────── + @classmethod + def from_dataset(cls, encoder, include_unlabeled: bool = True, + batch_size: int = 128) -> ChunkStore: + """Build the corpus locally from PubMedQA (no ArangoDB needed).""" + from ..data import iter_chunks + + ids, paper_keys, texts = [], [], [] + for paper_key, chunk_idx, text in iter_chunks(include_unlabeled): + ids.append(f"Chunks/{paper_key}_{chunk_idx}") + paper_keys.append(paper_key) + texts.append(text) + embeddings = encoder.encode( + texts, batch_size=batch_size, convert_to_numpy=True, + normalize_embeddings=True, show_progress_bar=True, + ) + return cls(ids, paper_keys, texts, embeddings) + + @classmethod + def from_arango(cls, db, collection: str = "Chunks", batch: int = 5000, + cache_file: str | None = None) -> ChunkStore: + """Download chunk vectors from ArangoDB (with optional pickle cache).""" + if cache_file: + import os + + if os.path.exists(cache_file): + with open(cache_file, "rb") as f: + data = pickle.load(f) + if len(data["embeddings"]): + return cls(data["ids"], data["paper_keys"], + data["texts"], np.asarray(data["embeddings"])) + + ids, paper_keys, texts, embeddings = [], [], [], [] + offset = 0 + while True: + aql = f""" + FOR c IN {collection} + FILTER c.embedding != null + LIMIT {offset}, {batch} + RETURN {{ id: c._id, paper: c.paper_key, + text: c.text, emb: c.embedding }} + """ + page = list(db.aql.execute(aql, ttl=3600)) + if not page: + break + for doc in page: + ids.append(doc["id"]) + paper_keys.append(doc.get("paper") or doc["id"].split("/")[-1].rsplit("_", 1)[0]) + texts.append(doc["text"]) + embeddings.append(doc["emb"]) + offset += len(page) + if len(page) < batch: + break + + embeddings_np = np.asarray(embeddings, dtype=np.float32) + if cache_file and ids: + with open(cache_file, "wb") as f: + pickle.dump({"ids": ids, "paper_keys": paper_keys, + "texts": texts, "embeddings": embeddings_np}, f) + return cls(ids, paper_keys, texts, embeddings_np) + + +class BaseRetriever(ABC): + """encode -> (optional) rerank -> select -> build context -> answer.""" + + name: str = "base" + + def __init__(self, store: ChunkStore, encoder, reranker=None, + top_k_final: int = TOP_K_FINAL, + top_k_candidates: int = TOP_K_CANDIDATES): + self.store = store + self.encoder = encoder + self.reranker = reranker + self.top_k_final = top_k_final + self.top_k_candidates = top_k_candidates + + def _select(self, query: str) -> list[Candidate]: + """Top-k chunks, optionally cross-encoder reranked from a wide pool.""" + query_emb = self.encoder.encode([query], normalize_embeddings=True) + pool_k = self.top_k_candidates if self.reranker else self.top_k_final + idxs = self.store.search(query_emb, pool_k) + candidates = [self.store.candidate(i) for i in idxs] + + if self.reranker and candidates: + scores = self.reranker.predict([[query, c.text] for c in candidates]) + order = np.argsort(scores)[::-1][:self.top_k_final] + return [ + Candidate(candidates[i].chunk_id, candidates[i].paper_key, + candidates[i].text, float(scores[i])) + for i in order + ] + return candidates[:self.top_k_final] + + @abstractmethod + def _build_context(self, query: str, candidates: list[Candidate]) -> str: + """Turn selected chunks into the LLM context string.""" + + def retrieve(self, query: str) -> str: + return self._build_context(query, self._select(query)) + + def answer_benchmark(self, question: str) -> str: + context = self.retrieve(question) + return call_ollama(build_prompt(context, question), + system=BENCHMARK_SYSTEM_PROMPT) + + def chat(self, question: str, temperature: float = 0.3) -> dict: + """Conversational answer plus the source paper pubids it retrieved. + + Runs retrieval once and returns the cited papers (their PubMedQA pubids, + which are real PubMed IDs) so a UI can link back to the sources. + """ + candidates = self._select(question) + context = self._build_context(question, candidates) + answer = call_ollama(build_prompt(context, question), + system=CHAT_SYSTEM_PROMPT, temperature=temperature) + sources = list(dict.fromkeys(c.paper_key for c in candidates)) + return {"answer": answer, "sources": sources, "context": context} diff --git a/src/kgqa/retrieval/graph.py b/src/kgqa/retrieval/graph.py new file mode 100644 index 0000000..9c37f5c --- /dev/null +++ b/src/kgqa/retrieval/graph.py @@ -0,0 +1,137 @@ +"""GraphRAG arms: ``graph`` (parent expansion) and ``graph_concepts``. + +Both reuse the identical encode + rerank + select pipeline from ``BaseRetriever`` +(so the reranker is *controlled for*, not a confound). The graph then adds: + + graph parent-paper expansion — reconstruct each selected chunk's + full abstract via HAS_CONTEXT traversal. + graph_concepts the above, plus a MeSH concept hop — pull in a few related + papers that share concepts with the selected papers. + +Leakage is stripped: studies are labelled generically ("=== STUDY n ===") and +no question-derived title or ``final_decision`` ever reaches the prompt. +""" + +from __future__ import annotations + +from ..config import CONCEPT_HOP_PAPERS, HAS_CONTEXT, MENTIONS +from .base import BaseRetriever, Candidate + +# Reconstruct the full abstract of each selected chunk's parent paper. +_PARENT_AQL = """ + WITH Papers, Chunks + FOR cid IN @ids + LET chunk = DOCUMENT(cid) + FOR paper IN 1..1 INBOUND chunk @@has_context + LET sections = ( + FOR c IN 1..1 OUTBOUND paper @@has_context + SORT c._key + RETURN c.text + ) + RETURN DISTINCT { + paper: paper._key, + abstract: CONCAT_SEPARATOR(" ", sections) + } +""" + +# From the seed papers, hop across shared MeSH concepts to related papers. +# Two-stage: rank neighbours by how many concepts they share with the seeds +# (cheap), then reconstruct abstracts only for the top-N (avoids building an +# abstract for every candidate on every query). +_CONCEPT_AQL = """ + WITH Papers, Chunks, Concepts + LET seeds = @paper_keys + LET ranked = ( + FOR pkey IN seeds + LET paper = DOCUMENT(CONCAT("Papers/", pkey)) + FILTER paper != null + FOR concept IN 1..1 OUTBOUND paper @@mentions + FOR neighbour IN 1..1 INBOUND concept @@mentions + FILTER neighbour._key NOT IN seeds + COLLECT nkey = neighbour._key WITH COUNT INTO shared + SORT shared DESC + LIMIT @limit + RETURN { nkey: nkey, shared: shared } + ) + FOR n IN ranked + LET sections = ( + FOR c IN 1..1 OUTBOUND DOCUMENT(CONCAT("Papers/", n.nkey)) @@has_context + SORT c._key + RETURN c.text + ) + RETURN { paper: n.nkey, abstract: CONCAT_SEPARATOR(" ", sections), shared: n.shared } +""" + + +class GraphRetriever(BaseRetriever): + name = "graph" + + def __init__(self, store, encoder, db, reranker=None, + use_concepts: bool = False, + concept_hop_papers: int = CONCEPT_HOP_PAPERS, **kwargs): + super().__init__(store, encoder, reranker=reranker, **kwargs) + self.db = db + self.use_concepts = use_concepts + self.concept_hop_papers = concept_hop_papers + if use_concepts: + self.name = "graph_concepts" + + def _parent_abstracts(self, chunk_ids: list[str]) -> list[tuple[str, str]]: + rows = self.db.aql.execute( + _PARENT_AQL, + bind_vars={"ids": chunk_ids, "@has_context": HAS_CONTEXT}, + ) + out, seen = [], set() + for row in rows: + key = row["paper"] + if key in seen: + continue + seen.add(key) + out.append((key, row.get("abstract", ""))) + return out + + def _concept_neighbours(self, paper_keys: list[str]) -> list[tuple[str, str]]: + rows = self.db.aql.execute( + _CONCEPT_AQL, + bind_vars={ + "paper_keys": paper_keys, + "@mentions": MENTIONS, + "@has_context": HAS_CONTEXT, + "limit": self.concept_hop_papers, + }, + ) + return [(row["paper"], row.get("abstract", "")) for row in rows] + + def gather_studies(self, candidates: list[Candidate]) -> list[tuple[str, str]]: + """Expand ``candidates`` to (paper_key, full_abstract) pairs via the graph. + + Degrades to the raw retrieved chunks if the graph is unreachable. + Exposed (not just used internally) so callers that also need the + expansion result -- e.g. to build a reasoning-path visualization -- + don't have to re-run the AQL traversal themselves. + """ + chunk_ids = [c.chunk_id for c in candidates] + try: + studies = self._parent_abstracts(chunk_ids) + seed_keys = [k for k, _ in studies] + if self.use_concepts and seed_keys: + for key, abstract in self._concept_neighbours(seed_keys): + if key not in seed_keys and abstract: + studies.append((key, abstract)) + except Exception as exc: # graph unreachable -> degrade to raw chunks + print(f"[GraphRAG] Graph expansion failed ({exc}). Using raw chunks.") + studies = [(c.paper_key, c.text) for c in candidates] + return studies + + def _build_context(self, query: str, candidates: list[Candidate]) -> str: + return format_studies(self.gather_studies(candidates)) + + +def format_studies(studies: list[tuple[str, str]]) -> str: + """Render (paper_key, abstract) pairs into the shared ``=== STUDY n ===`` context.""" + parts = [ + f"=== STUDY {i + 1} ===\n{abstract}" + for i, (_key, abstract) in enumerate(studies) + if abstract + ] + return "\n\n".join(parts) if parts else "No context found." diff --git a/src/kgqa/retrieval/plain.py b/src/kgqa/retrieval/plain.py new file mode 100644 index 0000000..15b6aa1 --- /dev/null +++ b/src/kgqa/retrieval/plain.py @@ -0,0 +1,27 @@ +"""PlainRAG arms: ``plain`` (no rerank) and ``plain_rr`` (with rerank). + +Context is the raw retrieved chunk text — no graph structure is used. With +``reranker=None`` this is the baseline; pass a CrossEncoder for the ``plain_rr`` +arm that isolates the reranker's contribution. +""" + +from __future__ import annotations + +from .base import BaseRetriever, Candidate + + +class PlainRetriever(BaseRetriever): + name = "plain" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + if self.reranker is not None: + self.name = "plain_rr" + + def _build_context(self, query: str, candidates: list[Candidate]) -> str: + if not candidates: + return "No context available." + return "\n\n".join( + f"Abstract {i + 1}: {c.text}" + for i, c in enumerate(candidates) + ) diff --git a/src/kgqa/service.py b/src/kgqa/service.py new file mode 100644 index 0000000..83fd734 --- /dev/null +++ b/src/kgqa/service.py @@ -0,0 +1,178 @@ +"""Service boundary: ``answer(question, graph_id)`` is the one entry point a +web backend calls. Everything upstream (ingestion, extraction, graph +construction, multi-hop retrieval) stays internal to this package; a caller +never needs to know about ``ChunkStore``, ``GraphRetriever``, or ArangoDB. + +The ``reasoning_path`` in the return value is an ordered list of graph steps +(seed chunk -> parent paper -> optional concept-hop neighbour) so a frontend +can draw the traversed subgraph without re-deriving it from the answer text. +""" + +from __future__ import annotations + +import os +import tempfile +from dataclasses import dataclass, field + +from .config import ArangoConfig +from .prompts import CHAT_SYSTEM_PROMPT, build_prompt +from .providers import call_llm +from .retrieval import ChunkStore +from .retrieval.graph import GraphRetriever, format_studies + +_STORE_CACHE: dict[str, ChunkStore] = {} +_RETRIEVER_CACHE: dict[tuple[str, bool], GraphRetriever] = {} +_DB_CACHE: dict[str, object] = {} +_ENCODER = None +_RERANKER = None +_CACHE_DIR = os.environ.get("KGQA_CACHE_DIR", os.path.join(tempfile.gettempdir(), "kgqa_cache")) +os.makedirs(_CACHE_DIR, exist_ok=True) + + +def _shared_encoder(): + global _ENCODER + if _ENCODER is None: + from .models import load_encoder + + _ENCODER = load_encoder() + return _ENCODER + + +def _shared_reranker(): + global _RERANKER + if _RERANKER is None: + from .models import load_reranker + + _RERANKER = load_reranker() + return _RERANKER + + +def _shared_db(graph_id: str): + """One ArangoDB connection per ``graph_id``, reused by the store and retriever. + + ``graph_id="demo"`` connects to the env-configured default database (the + preloaded demo graph); any other id is treated as the database name. + + Returns ``None`` (cached, so this is tried at most once per process) if + ArangoDB isn't configured or isn't reachable -- ``GraphRetriever`` already + degrades to raw retrieved chunks when its ``db`` calls fail, so the + service still answers (without parent-document expansion) rather than + hard-crashing when no graph is available. + """ + if graph_id not in _DB_CACHE: + from .models import connect_arango + + cfg = ArangoConfig() if graph_id == "demo" else ArangoConfig(db_name=graph_id) + try: + _DB_CACHE[graph_id] = connect_arango(cfg, max_retries=1) + except Exception as exc: # noqa: BLE001 - degrade to no-graph, not a crash + print(f"[GraphRAG] ArangoDB unavailable for graph_id={graph_id!r} ({exc}). " + "Falling back to ungraphed retrieval.") + _DB_CACHE[graph_id] = None + return _DB_CACHE[graph_id] + + +@dataclass +class ReasoningStep: + kind: str # "seed_chunk" | "parent_paper" | "concept_neighbour" + node_id: str + label: str + from_node: str | None = None + edge: str | None = None + + +@dataclass +class AnswerResult: + answer: str + reasoning_path: list[dict] = field(default_factory=list) + sources: list[str] = field(default_factory=list) + + +def _get_store(graph_id: str) -> ChunkStore: + """Resolve a ``graph_id`` to its chunk store, building/caching on first use. + + Prefers ``ChunkStore.from_arango`` -- ``scripts/ingest.py`` pre-computes + and stores every chunk's embedding in ArangoDB, so this just downloads + vectors (fast, no re-encoding; a local pickle cache makes even that a + one-time cost). This is what "preloaded demo graph" means: encoding the + ~62k-chunk PubMedQA corpus live on a request would take *hours* on a + CPU-only host, not a tolerable cold start. + + Only falls back to ``ChunkStore.from_dataset`` (encoding chunks locally) + when ArangoDB isn't reachable, and only over the small labeled split, so + the fallback is a bounded, if slower, degraded local-dev mode -- never + the full corpus. + """ + if graph_id in _STORE_CACHE: + return _STORE_CACHE[graph_id] + + encoder = _shared_encoder() + db = _shared_db(graph_id) + if db is not None: + cache_file = os.path.join(_CACHE_DIR, f"{graph_id}_vectors.pkl") + store = ChunkStore.from_arango(db, cache_file=cache_file) + else: + print(f"[GraphRAG] No ArangoDB for graph_id={graph_id!r}; encoding the labeled " + "split locally as a bounded fallback (this is slow -- not for production).") + store = ChunkStore.from_dataset(encoder, include_unlabeled=False) + _STORE_CACHE[graph_id] = store + return store + + +def _get_retriever(graph_id: str, use_concepts: bool = False) -> GraphRetriever: + key = (graph_id, use_concepts) + if key in _RETRIEVER_CACHE: + return _RETRIEVER_CACHE[key] + + store = _get_store(graph_id) + retriever = GraphRetriever( + store, _shared_encoder(), _shared_db(graph_id), + reranker=_shared_reranker(), use_concepts=use_concepts, + ) + _RETRIEVER_CACHE[key] = retriever + return retriever + + +def _build_reasoning_path(candidates, studies: list[tuple[str, str]]) -> list[dict]: + steps: list[ReasoningStep] = [] + seed_papers = {c.paper_key for c in candidates} + for c in candidates: + steps.append(ReasoningStep(kind="seed_chunk", node_id=c.chunk_id, label=c.text[:80])) + for paper_key, _abstract in studies: + if paper_key in seed_papers: + for c in candidates: + if c.paper_key == paper_key: + steps.append( + ReasoningStep( + kind="parent_paper", node_id=f"Papers/{paper_key}", + label=paper_key, from_node=c.chunk_id, edge="HAS_CONTEXT", + ) + ) + else: + steps.append( + ReasoningStep( + kind="concept_neighbour", node_id=f"Papers/{paper_key}", + label=paper_key, from_node=None, edge="MENTIONS", + ) + ) + return [step.__dict__ for step in steps] + + +def answer(question: str, graph_id: str = "demo", use_concepts: bool = False) -> dict: + """Answer ``question`` against ``graph_id``. + + Returns ``{"answer": str, "reasoning_path": list[dict], "sources": list[str]}``. + Synthesis runs through the ``synthesize`` provider chain (Gemini Flash by + default, falling back to local Ollama) so the service degrades gracefully + without a cloud API key configured. + """ + retriever = _get_retriever(graph_id, use_concepts=use_concepts) + candidates = retriever._select(question) + studies = retriever.gather_studies(candidates) + context = format_studies(studies) + + response = call_llm("synthesize", build_prompt(context, question), system=CHAT_SYSTEM_PROMPT) + reasoning_path = _build_reasoning_path(candidates, studies) + sources = list(dict.fromkeys(c.paper_key for c in candidates)) + + return AnswerResult(answer=response, reasoning_path=reasoning_path, sources=sources).__dict__ diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..b17632d --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,84 @@ +"""Shared fakes so the suite runs on CPU with no Ollama, ArangoDB, or ML deps.""" + +from __future__ import annotations + +import numpy as np +import pytest + + +class FakeEncoder: + """Deterministic hashing encoder — stable vectors without downloading a model.""" + + dim = 16 + + def encode(self, texts, normalize_embeddings=False, convert_to_numpy=True, + batch_size=32, show_progress_bar=False): + single = isinstance(texts, str) + items = [texts] if single else list(texts) + vecs = np.zeros((len(items), self.dim), dtype=np.float32) + for i, t in enumerate(items): + for token in str(t).lower().split(): + vecs[i, hash(token) % self.dim] += 1.0 + if normalize_embeddings: + norms = np.linalg.norm(vecs, axis=1, keepdims=True) + norms[norms == 0] = 1.0 + vecs = vecs / norms + return vecs[0] if single else vecs + + +class FakeReranker: + """Scores by lexical overlap between query and candidate text.""" + + def predict(self, pairs): + scores = [] + for query, text in pairs: + q = set(str(query).lower().split()) + d = set(str(text).lower().split()) + scores.append(float(len(q & d))) + return np.array(scores) + + +class FakeAQL: + def __init__(self, db): + self.db = db + + def execute(self, query, bind_vars=None, **kwargs): + bind_vars = bind_vars or {} + # Parent expansion: map chunk ids -> parent paper full abstracts. + if "INBOUND chunk" in query: + seen, out = set(), [] + for cid in bind_vars["ids"]: + pkey = cid.split("/")[-1].rsplit("_", 1)[0] + if pkey in seen: + continue + seen.add(pkey) + out.append({"paper": pkey, "abstract": self.db.abstracts[pkey]}) + return out + # Concept hop: return configured neighbours for the seed papers. + if "@mentions" in query or "mentions" in query.lower(): + seeds = set(bind_vars["paper_keys"]) + out = [] + for nkey, abstract in self.db.neighbours: + if nkey not in seeds: + out.append({"paper": nkey, "abstract": abstract, "shared": 1}) + return out[: bind_vars.get("limit", 3)] + return [] + + +class FakeDB: + """Minimal ArangoDB stand-in for graph-expansion tests.""" + + def __init__(self, abstracts, neighbours=()): + self.abstracts = abstracts # {paper_key: full abstract} + self.neighbours = list(neighbours) # [(paper_key, abstract), ...] + self.aql = FakeAQL(self) + + +@pytest.fixture +def fake_encoder(): + return FakeEncoder() + + +@pytest.fixture +def fake_reranker(): + return FakeReranker() diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..88fbecf --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,25 @@ +import pytest + +from kgqa.config import TOP_K_CANDIDATES, TOP_K_FINAL, ArangoConfig +from kgqa.prompts import build_prompt + + +def test_arango_requires_password(): + cfg = ArangoConfig(password="") + with pytest.raises(EnvironmentError): + cfg.require_password() + + +def test_arango_password_ok(): + ArangoConfig(password="secret").require_password() # no raise + + +def test_retrieval_constants_sane(): + assert TOP_K_FINAL >= 1 + assert TOP_K_CANDIDATES >= TOP_K_FINAL + + +def test_build_prompt_structure(): + p = build_prompt("CTX", "Q?") + assert "Context:\nCTX" in p + assert "Question: Q?" in p diff --git a/tests/test_data.py b/tests/test_data.py new file mode 100644 index 0000000..57159db --- /dev/null +++ b/tests/test_data.py @@ -0,0 +1,43 @@ +"""Tests for dataset sampling and chunking — the `datasets` dependency is faked +so these run without it installed and without any download.""" + +from __future__ import annotations + +import sys +import types + + +def _fake_datasets(monkeypatch, rows): + mod = types.ModuleType("datasets") + mod.load_dataset = lambda *a, **k: rows + monkeypatch.setitem(sys.modules, "datasets", mod) + + +def test_load_benchmark_samples_seeded_and_filtered(monkeypatch): + from kgqa import data + + rows = [{"pubid": i, "question": f"q{i}", "final_decision": ["yes", "no", "maybe"][i % 3]} + for i in range(30)] + rows.append({"pubid": 900, "question": "", "final_decision": "yes"}) # dropped: no question + rows.append({"pubid": 901, "question": "x", "final_decision": None}) # dropped: no label + _fake_datasets(monkeypatch, rows) + + a = data.load_benchmark_samples(n=10, seed=42) + b = data.load_benchmark_samples(n=10, seed=42) + assert len(a) == 10 + assert [s.pubid for s in a] == [s.pubid for s in b] # deterministic + assert all(s.question and s.final_decision for s in a) # filtered + assert all(isinstance(s.pubid, str) for s in a) # pubid stringified + assert data.load_benchmark_samples(n=10, seed=7) != a # seed changes order + + +def test_iter_chunks_skips_empty_and_yields_indices(monkeypatch): + from kgqa import data + + rows = [{"pubid": 5, "context": {"contexts": ["alpha", "beta", " "]}}] + _fake_datasets(monkeypatch, rows) + + chunks = list(data.iter_chunks(include_unlabeled=False)) + assert ("5", 0, "alpha") in chunks + assert ("5", 1, "beta") in chunks + assert len(chunks) == 2 # blank section dropped diff --git a/tests/test_evaluation.py b/tests/test_evaluation.py new file mode 100644 index 0000000..067b701 --- /dev/null +++ b/tests/test_evaluation.py @@ -0,0 +1,71 @@ +from kgqa.evaluation import Evaluator, FuzzyEvaluator, mcnemar_test + + +def test_extract_final_answer_tag(): + fz = FuzzyEvaluator() + assert fz.extract_answer("blah blah Final Answer: yes") == "yes" + assert fz.extract_answer("FINAL ANSWER : No") == "no" + + +def test_extract_strips_think_block(): + fz = FuzzyEvaluator() + text = "maybe yes no The study shows ... Final Answer: maybe" + assert fz.extract_answer(text) == "maybe" + + +def test_extract_falls_back_to_last_mention(): + fz = FuzzyEvaluator() + assert fz.extract_answer("I think the answer is no") == "no" + assert fz.extract_answer("nothing useful here") == "maybe" + + +def test_evaluator_metrics_and_normalisation(): + ev = Evaluator("plain") + ev.record("yes", "yes", 1.0, sample_id="1") + ev.record("no", "garbage", 2.0, sample_id="2") # invalid -> maybe + ev.record("maybe", "maybe", 3.0, sample_id="3") + s = ev.summary() + assert s["samples"] == 3 + assert s["y_pred"][1] == "maybe" + assert abs(s["accuracy"] - 2 / 3) < 1e-9 + assert abs(s["avg_latency"] - 2.0) < 1e-9 + assert s["ids"] == ["1", "2", "3"] + + +def test_mcnemar_detects_one_sided_gain(): + gt = ["yes"] * 10 + a = ["no"] * 10 # arm A always wrong + b = ["yes"] * 10 # arm B always right + res = mcnemar_test(gt, a, b) + assert res["b_gains"] == 10 + assert res["c_losses"] == 0 + assert res["significant_at_0.05"] is True + + +def test_mcnemar_no_difference(): + gt = ["yes", "no", "maybe"] + res = mcnemar_test(gt, gt, gt) + assert res["discordant"] == 0 + assert res["p_value"] == 1.0 + + +def test_mcnemar_length_mismatch_raises(): + import pytest + with pytest.raises(ValueError): + mcnemar_test(["yes"], ["yes"], ["yes", "no"]) + + +def test_report_and_save_roundtrip(tmp_path): + import json + ev = Evaluator("graph") + ev.record("yes", "yes", 1.0, "1") + ev.record("no", "yes", 2.0, "2") + summary = ev.report() + assert summary["model"] == "graph" and summary["samples"] == 2 + assert "macro_f1" in summary + + path = tmp_path / "results.json" + ev.save(str(path)) + loaded = json.loads(path.read_text()) + assert loaded["samples"] == 2 + assert loaded["ids"] == ["1", "2"] diff --git a/tests/test_llm.py b/tests/test_llm.py new file mode 100644 index 0000000..4960521 --- /dev/null +++ b/tests/test_llm.py @@ -0,0 +1,33 @@ +"""Tests for the Ollama client — requests.post is faked, so no server is needed.""" + +from __future__ import annotations + + +def test_call_ollama_builds_payload_and_returns_content(monkeypatch): + import kgqa.llm as llm + + captured = {} + + class FakeResp: + def raise_for_status(self): + pass + + def json(self): + return {"message": {"content": "the answer"}} + + def fake_post(url, json=None, timeout=None): + captured["url"] = url + captured["payload"] = json + return FakeResp() + + monkeypatch.setattr(llm.requests, "post", fake_post) + + out = llm.call_ollama("my prompt", system="be helpful", temperature=0.0) + assert out == "the answer" + + payload = captured["payload"] + assert payload["messages"][0] == {"role": "system", "content": "be helpful"} + assert payload["messages"][-1] == {"role": "user", "content": "my prompt"} + assert payload["stream"] is False + assert "num_predict" in payload["options"] # generation cap is applied + assert "keep_alive" in payload # model kept resident diff --git a/tests/test_providers.py b/tests/test_providers.py new file mode 100644 index 0000000..dcfb129 --- /dev/null +++ b/tests/test_providers.py @@ -0,0 +1,90 @@ +"""Tests for the multi-provider LLM chain -- all HTTP calls are faked.""" + +from __future__ import annotations + +import pytest + + +def test_call_groq_builds_payload_and_returns_content(monkeypatch): + import kgqa.providers as providers + + monkeypatch.setattr(providers, "GROQ_API_KEY", "test-key") + captured = {} + + class FakeResp: + def raise_for_status(self): + pass + + def json(self): + return {"choices": [{"message": {"content": "groq answer"}}]} + + def fake_post(url, headers=None, json=None, timeout=None): + captured["url"] = url + captured["headers"] = headers + captured["payload"] = json + return FakeResp() + + monkeypatch.setattr(providers.requests, "post", fake_post) + out = providers.call_groq("prompt", system="sys") + assert out == "groq answer" + assert captured["headers"]["Authorization"] == "Bearer test-key" + assert captured["payload"]["messages"][0] == {"role": "system", "content": "sys"} + + +def test_call_groq_without_key_raises(monkeypatch): + import kgqa.providers as providers + + monkeypatch.setattr(providers, "GROQ_API_KEY", "") + with pytest.raises(RuntimeError, match="GROQ_API_KEY"): + providers.call_groq("prompt") + + +def test_call_gemini_builds_payload_and_returns_content(monkeypatch): + import kgqa.providers as providers + + monkeypatch.setattr(providers, "GEMINI_API_KEY", "test-key") + + class FakeResp: + def raise_for_status(self): + pass + + def json(self): + return {"candidates": [{"content": {"parts": [{"text": "gemini answer"}]}}]} + + def fake_post(url, params=None, json=None, timeout=None): + assert params == {"key": "test-key"} + return FakeResp() + + monkeypatch.setattr(providers.requests, "post", fake_post) + out = providers.call_gemini("prompt", system="sys") + assert out == "gemini answer" + + +def test_call_llm_falls_back_when_primary_provider_fails(monkeypatch): + import kgqa.providers as providers + + def broken(*args, **kwargs): + raise RuntimeError("groq is down") + + def works(*args, **kwargs): + return "fallback answer" + + monkeypatch.setitem(providers._PROVIDERS, "groq", broken) + monkeypatch.setitem(providers._PROVIDERS, "ollama", works) + monkeypatch.setitem(providers._CHAINS, "decompose", ["groq", "ollama"]) + + assert providers.call_llm("decompose", "prompt") == "fallback answer" + + +def test_call_llm_raises_when_all_providers_fail(monkeypatch): + import kgqa.providers as providers + + def broken(*args, **kwargs): + raise RuntimeError("nope") + + monkeypatch.setitem(providers._PROVIDERS, "groq", broken) + monkeypatch.setitem(providers._PROVIDERS, "ollama", broken) + monkeypatch.setitem(providers._CHAINS, "decompose", ["groq", "ollama"]) + + with pytest.raises(RuntimeError, match="All providers failed"): + providers.call_llm("decompose", "prompt") diff --git a/tests/test_results_regression.py b/tests/test_results_regression.py new file mode 100644 index 0000000..6f87580 --- /dev/null +++ b/tests/test_results_regression.py @@ -0,0 +1,80 @@ +"""CI eval gate: fails the build if the checked-in benchmark results regress. + +This does NOT re-run the LLM benchmark in CI -- that needs a GPU and a live +ArangoDB (see project notes: this repo's benchmark runs on Colab), neither of +which CI has. Instead it guards the artifact everything else (README, +RESULTS.md, the /benchmark dashboard, resume claims) points to: if +``results/summary.json`` is ever edited down, or a re-run regresses, this +test catches it instead of a human noticing it went stale. + +Thresholds are set with headroom below the actual recorded numbers (see +RESULTS.md) so normal run-to-run noise doesn't false-positive, while still +catching a real regression or accidental edit. +""" + +from __future__ import annotations + +import json +import os + +RESULTS_PATH = os.path.join( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "results", "summary.json" +) + + +def _load(): + with open(RESULTS_PATH) as f: + return json.load(f) + + +def _arm(data, name): + for a in data["arms"]: + if a["arm"] == name: + return a + raise AssertionError(f"arm {name!r} not found in results/summary.json -- was it renamed or removed?") + + +def _contrast(data, from_arm, to_arm): + for c in data["contrasts"]: + if c["from"] == from_arm and c["to"] == to_arm: + return c + raise AssertionError( + f"contrast {from_arm!r} -> {to_arm!r} not found in results/summary.json -- was it renamed or removed?" + ) + + +def test_results_file_has_minimum_sample_size(): + data = _load() + assert data["n"] >= 100, "benchmark sample size dropped below a trustworthy floor" + + +def test_graph_arm_accuracy_has_not_regressed(): + graph = _arm(_load(), "graph") + assert graph["accuracy"] >= 55.0, "graph arm accuracy regressed below floor" + assert graph["macro_f1"] >= 45.0, "graph arm macro-F1 regressed below floor" + + +def test_graph_beats_reranked_baseline(): + data = _load() + graph, plain_rr = _arm(data, "graph"), _arm(data, "plain_rr") + assert graph["accuracy"] > plain_rr["accuracy"], ( + "graph arm no longer beats the reranked baseline -- the headline claim is broken" + ) + + +def test_parent_expansion_effect_is_still_significant(): + """The repo's whole pitch is +22.5pp from parent-document expansion, + McNemar p<0.0001 -- this is the one number that must never quietly break.""" + contrast = _contrast(_load(), "plain_rr", "graph") + assert contrast["significant"] is True + assert contrast["p_value"] < 0.05 + assert contrast["delta_acc"] >= 15.0, "parent-expansion lift shrank well below the claimed +22.5pp" + + +def test_graph_concepts_latency_within_bounds(): + """Not a regression gate on accuracy (graph_concepts isn't the shipped arm), + just a sanity check that the recorded latency multiplier hasn't exploded + further, since that number is quoted in RESULTS.md too.""" + data = _load() + graph, concepts = _arm(data, "graph"), _arm(data, "graph_concepts") + assert concepts["avg_latency"] < graph["avg_latency"] * 10 diff --git a/tests/test_retrieval.py b/tests/test_retrieval.py new file mode 100644 index 0000000..9fe8c1b --- /dev/null +++ b/tests/test_retrieval.py @@ -0,0 +1,113 @@ +import numpy as np + +from kgqa.retrieval import ChunkStore, GraphRetriever, PlainRetriever +from tests.conftest import FakeDB + + +def make_store(encoder): + texts = [ + "aspirin reduces heart attack risk in patients", + "statins lower cholesterol levels significantly", + "regular exercise improves mood and sleep", + ] + keys = ["1", "2", "3"] + ids = [f"Chunks/{k}_0" for k in keys] + embs = encoder.encode(texts, normalize_embeddings=True) + return ChunkStore(ids, keys, texts, np.asarray(embs)) + + +def test_chunkstore_search_ranks_relevant_first(fake_encoder): + store = make_store(fake_encoder) + idxs = store.search(fake_encoder.encode(["aspirin heart attack"]), k=3) + assert store.paper_keys[idxs[0]] == "1" + + +def test_plain_arm_naming(fake_encoder, fake_reranker): + assert PlainRetriever(make_store(fake_encoder), fake_encoder).name == "plain" + assert PlainRetriever(make_store(fake_encoder), fake_encoder, + reranker=fake_reranker).name == "plain_rr" + + +def test_plain_context_is_raw_chunks(fake_encoder): + store = make_store(fake_encoder) + r = PlainRetriever(store, fake_encoder, top_k_final=1) + ctx = r.retrieve("aspirin heart attack") + assert ctx.startswith("Abstract 1:") + assert "aspirin" in ctx + + +def test_graph_parent_expansion_uses_full_abstract(fake_encoder, fake_reranker): + store = make_store(fake_encoder) + db = FakeDB(abstracts={ + "1": "FULL ABSTRACT 1: aspirin trial methods results conclusion", + "2": "FULL ABSTRACT 2: statin trial", + "3": "FULL ABSTRACT 3: exercise study", + }) + r = GraphRetriever(store, fake_encoder, db, reranker=fake_reranker, top_k_final=1) + assert r.name == "graph" + ctx = r.retrieve("aspirin heart attack") + assert "=== STUDY 1 ===" in ctx + assert "FULL ABSTRACT 1" in ctx + + +def test_graph_concept_hop_adds_neighbour(fake_encoder, fake_reranker): + store = make_store(fake_encoder) + db = FakeDB( + abstracts={"1": "FULL ABSTRACT 1: aspirin", "2": "x", "3": "y"}, + neighbours=[("99", "NEIGHBOUR ABSTRACT via shared MeSH concept")], + ) + r = GraphRetriever(store, fake_encoder, db, reranker=fake_reranker, + use_concepts=True, top_k_final=1) + assert r.name == "graph_concepts" + ctx = r.retrieve("aspirin heart attack") + assert "NEIGHBOUR ABSTRACT" in ctx + assert ctx.count("=== STUDY") == 2 + + +def test_graph_context_has_no_question_leakage(fake_encoder, fake_reranker): + """The benchmark question/title must never appear in the graph context.""" + store = make_store(fake_encoder) + db = FakeDB(abstracts={"1": "FULL ABSTRACT 1: aspirin", "2": "x", "3": "y"}) + r = GraphRetriever(store, fake_encoder, db, reranker=fake_reranker, top_k_final=1) + question = "does aspirin reduce heart attack risk" + ctx = r.retrieve(question) + assert question not in ctx + assert "STUDY:" not in ctx # old leaky "=== STUDY: {title} ===" format is gone + + +def test_graph_degrades_to_raw_chunks_on_db_error(fake_encoder, fake_reranker): + class BrokenDB: + class aql: + @staticmethod + def execute(*a, **k): + raise RuntimeError("no connection") + store = make_store(fake_encoder) + r = GraphRetriever(store, fake_encoder, BrokenDB(), reranker=fake_reranker, top_k_final=1) + ctx = r.retrieve("aspirin heart attack") + assert "=== STUDY 1 ===" in ctx + assert "aspirin" in ctx + + +def test_chat_returns_answer_and_source_pubids(fake_encoder, fake_reranker, monkeypatch): + import kgqa.retrieval.base as base + monkeypatch.setattr(base, "call_ollama", + lambda *a, **k: "reasoning Yes, it does.") + store = make_store(fake_encoder) + db = FakeDB(abstracts={"1": "FULL ABS 1: aspirin", "2": "x", "3": "y"}) + r = GraphRetriever(store, fake_encoder, db, reranker=fake_reranker, top_k_final=1) + out = r.chat("does aspirin reduce heart attack risk") + assert set(out) >= {"answer", "sources", "context"} + assert out["sources"] == ["1"] # the retrieved paper's pubid + assert "Yes" in out["answer"] + + +def test_chunkstore_from_dataset_builds_corpus(monkeypatch, fake_encoder): + import kgqa.data as data + from kgqa.retrieval import ChunkStore + + monkeypatch.setattr(data, "iter_chunks", + lambda include_unlabeled=True: iter([("1", 0, "alpha"), ("2", 0, "beta")])) + store = ChunkStore.from_dataset(fake_encoder, include_unlabeled=False) + assert len(store) == 2 + assert store.paper_keys == ["1", "2"] + assert store.ids == ["Chunks/1_0", "Chunks/2_0"] diff --git a/tests/test_service.py b/tests/test_service.py new file mode 100644 index 0000000..a60847f --- /dev/null +++ b/tests/test_service.py @@ -0,0 +1,96 @@ +import numpy as np + +from kgqa.retrieval import ChunkStore, GraphRetriever +from tests.conftest import FakeDB + + +def make_retriever(encoder, reranker): + texts = [ + "aspirin reduces heart attack risk in patients", + "statins lower cholesterol levels significantly", + ] + keys = ["1", "2"] + ids = [f"Chunks/{k}_0" for k in keys] + embs = encoder.encode(texts, normalize_embeddings=True) + store = ChunkStore(ids, keys, texts, np.asarray(embs)) + db = FakeDB(abstracts={ + "1": "FULL ABSTRACT 1: aspirin trial methods results conclusion", + "2": "FULL ABSTRACT 2: statin trial", + }) + return GraphRetriever(store, encoder, db, reranker=reranker, top_k_final=1) + + +def test_answer_returns_answer_reasoning_path_and_sources(fake_encoder, fake_reranker, monkeypatch): + import kgqa.service as service + + retriever = make_retriever(fake_encoder, fake_reranker) + monkeypatch.setattr(service, "_get_retriever", lambda graph_id, use_concepts=False: retriever) + monkeypatch.setattr(service, "call_llm", lambda task, prompt, system="": "Yes, it does.") + + result = service.answer("does aspirin reduce heart attack risk", graph_id="demo") + + assert result["answer"] == "Yes, it does." + assert result["sources"] == ["1"] + assert any(step["kind"] == "seed_chunk" for step in result["reasoning_path"]) + assert any(step["kind"] == "parent_paper" for step in result["reasoning_path"]) + + +def test_answer_reasoning_path_includes_concept_neighbours(fake_encoder, fake_reranker, monkeypatch): + import kgqa.service as service + + texts = ["aspirin reduces heart attack risk in patients"] + embs = fake_encoder.encode(texts, normalize_embeddings=True) + store = ChunkStore(["Chunks/1_0"], ["1"], texts, np.asarray(embs)) + db = FakeDB( + abstracts={"1": "FULL ABSTRACT 1: aspirin"}, + neighbours=[("99", "NEIGHBOUR ABSTRACT via shared MeSH concept")], + ) + retriever = GraphRetriever(store, fake_encoder, db, reranker=fake_reranker, + use_concepts=True, top_k_final=1) + monkeypatch.setattr(service, "_get_retriever", lambda graph_id, use_concepts=False: retriever) + monkeypatch.setattr(service, "call_llm", lambda task, prompt, system="": "answer") + + result = service.answer("q", graph_id="demo", use_concepts=True) + + kinds = [step["kind"] for step in result["reasoning_path"]] + assert "concept_neighbour" in kinds + + +def test_shared_db_degrades_to_none_without_arango_configured(monkeypatch): + """Real code path (no mocked _get_retriever): demo mode must not crash + just because ARANGO_PASS is unset -- it should degrade, not raise.""" + import kgqa.service as service + + monkeypatch.delenv("ARANGO_PASS", raising=False) + service._DB_CACHE.clear() + + db = service._shared_db("demo") + + assert db is None + assert service._DB_CACHE["demo"] is None # cached, so the failure isn't retried + + +def test_get_retriever_builds_when_arango_unavailable(monkeypatch, fake_encoder, fake_reranker): + """answer()'s retriever must still build (and later degrade to raw chunks + inside gather_studies) rather than raising when there's no reachable graph.""" + import kgqa.service as service + + monkeypatch.delenv("ARANGO_PASS", raising=False) + service._DB_CACHE.clear() + service._STORE_CACHE.clear() + service._RETRIEVER_CACHE.clear() + monkeypatch.setattr(service, "_shared_encoder", lambda: fake_encoder) + monkeypatch.setattr(service, "_shared_reranker", lambda: fake_reranker) + monkeypatch.setattr( + service.ChunkStore, "from_dataset", + classmethod(lambda cls, encoder, include_unlabeled=True: ChunkStore( + ["Chunks/1_0"], ["1"], ["aspirin study"], + np.asarray(encoder.encode(["aspirin study"], normalize_embeddings=True)))), + ) + + retriever = service._get_retriever("demo") + + assert retriever.db is None + candidates = retriever._select("aspirin") + studies = retriever.gather_studies(candidates) # degrades instead of raising + assert studies == [(c.paper_key, c.text) for c in candidates]