From 363179495603f8f043914302dc5ec8a6eb8fc054 Mon Sep 17 00:00:00 2001 From: Chengbiao Jin Date: Tue, 14 Apr 2026 22:07:51 -0700 Subject: [PATCH 01/70] Add UI test --- graphrag-ui/package-lock.json | 10906 ++++++++++++++++++++++++++++++++ graphrag-ui/package.json | 6 +- 2 files changed, 10909 insertions(+), 3 deletions(-) create mode 100644 graphrag-ui/package-lock.json diff --git a/graphrag-ui/package-lock.json b/graphrag-ui/package-lock.json new file mode 100644 index 0000000..7def4e4 --- /dev/null +++ b/graphrag-ui/package-lock.json @@ -0,0 +1,10906 @@ +{ + "name": "tg-cbot-v5", + "version": "0.0.5", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tg-cbot-v5", + "version": "0.0.5", + "dependencies": { + "@hookform/resolvers": "^3.6.0", + "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dropdown-menu": "^2.0.6", + "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-popover": "^1.1.1", + "@radix-ui/react-radio-group": "^1.2.0", + "@radix-ui/react-select": "^2.1.1", + "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-tabs": "^1.1.0", + "@react-three/drei": "9.56.1", + "@react-three/fiber": "8.13.3", + "@tailwindcss/typography": "^0.5.18", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "i18next": "^23.11.5", + "install": "^0.13.0", + "lucide-react": "^0.390.0", + "npm": "^10.8.1", + "react": "^18.3.1", + "react-chatbot-kit": "^2.2.2", + "react-dom": "^18.3.1", + "react-hook-form": "^7.51.5", + "react-i18next": "^14.1.2", + "react-icons": "^5.2.1", + "react-markdown": "^9.0.1", + "react-router-dom": "^6.23.1", + "react-use-websocket": "^4.8.1", + "reagraph": "4.15.19", + "remark-gfm": "^4.0.0", + "tailwind-merge": "^2.3.0", + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" + }, + "devDependencies": { + "@playwright/test": "^1.59.1", + "@types/node": "^25.6.0", + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@typescript-eslint/eslint-plugin": "^7.5.0", + "@typescript-eslint/parser": "^7.5.0", + "@vitejs/plugin-react-swc": "^3.6.0", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^6.1.1", + "eslint-plugin-react-refresh": "^0.4.6", + "postcss": "^8.4.38", + "tailwindcss": "^3.4.18", + "typescript": "^5.2.2", + "vite": "^5.2.0" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@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/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@dimforge/rapier3d-compat": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", + "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", + "license": "Apache-2.0" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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/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/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/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/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", + "integrity": "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", + "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.5", + "@floating-ui/utils": "^0.2.11" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", + "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", + "license": "MIT" + }, + "node_modules/@hookform/resolvers": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.10.0.tgz", + "integrity": "sha512-79Dv+3mDF7i+2ajj7SkypSKHhl1cbln1OGavqrsF7p6mbUv11xpqpacPsGDCTRvCSjEEIez2ef1NveSVL3b0Ag==", + "license": "MIT", + "peerDependencies": { + "react-hook-form": "^7.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/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/@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/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mediapipe/tasks-vision": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.8.tgz", + "integrity": "sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==", + "license": "Apache-2.0" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@playwright/test": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", + "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@react-spring/animated": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", + "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", + "license": "MIT", + "dependencies": { + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", + "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", + "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==", + "license": "MIT" + }, + "node_modules/@react-spring/shared": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", + "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", + "license": "MIT", + "dependencies": { + "@react-spring/rafz": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/three": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.7.5.tgz", + "integrity": "sha512-RxIsCoQfUqOS3POmhVHa1wdWS0wyHAUway73uRLp3GAL5U2iYVNdnzQsep6M2NZ994BlW8TcKuMtQHUqOsy6WA==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.7.5", + "@react-spring/core": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "@react-three/fiber": ">=6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "three": ">=0.126" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", + "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==", + "license": "MIT" + }, + "node_modules/@react-three/drei": { + "version": "9.56.1", + "resolved": "https://registry.npmjs.org/@react-three/drei/-/drei-9.56.1.tgz", + "integrity": "sha512-xHQHMqqn4ww62YVDoXLazFhhrM5pkzoaA/2v5ytjbKjU9hP2iHos3odxGxQEKUS0WXwduziP6ScRkdSevpDFsQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@react-spring/three": "^9.3.1", + "@use-gesture/react": "^10.2.0", + "camera-controls": "^1.38.0", + "detect-gpu": "^5.0.8", + "glsl-noise": "^0.0.0", + "lodash.clamp": "^4.0.3", + "lodash.omit": "^4.5.0", + "lodash.pick": "^4.4.0", + "maath": "^0.5.2", + "meshline": "^3.1.6", + "react-composer": "^5.0.3", + "react-merge-refs": "^1.1.0", + "stats.js": "^0.17.0", + "suspend-react": "^0.0.8", + "three-mesh-bvh": "^0.5.22", + "three-stdlib": "^2.21.6", + "troika-three-text": "^0.47.1", + "utility-types": "^3.10.0", + "zustand": "^3.5.13" + }, + "peerDependencies": { + "@react-three/fiber": ">=8.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "three": ">=0.137" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber": { + "version": "8.13.3", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.13.3.tgz", + "integrity": "sha512-mCdTUB8D1kwlsOSxGhUg5nuGHt3HN3aNFc0s9I/N7ayk+nzT2ttLdn49c56nrHu+YK+SU1xnrxe6LqftZgIRmQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.1", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-gl": ">=11.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@react-three/fiber/node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz", + "integrity": "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz", + "integrity": "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz", + "integrity": "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz", + "integrity": "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz", + "integrity": "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz", + "integrity": "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz", + "integrity": "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz", + "integrity": "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz", + "integrity": "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz", + "integrity": "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz", + "integrity": "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz", + "integrity": "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz", + "integrity": "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz", + "integrity": "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz", + "integrity": "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz", + "integrity": "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz", + "integrity": "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1.tgz", + "integrity": "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz", + "integrity": "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz", + "integrity": "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz", + "integrity": "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1.tgz", + "integrity": "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz", + "integrity": "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.1.tgz", + "integrity": "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.1.tgz", + "integrity": "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@swc/core": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.24.tgz", + "integrity": "sha512-5Hj8aNasue7yusUt8LGCUe/AjM7RMAce8ZoyDyiFwx7Al+GbYKL+yE7g4sJk8vEr1dKIkTRARkNIJENc4CjkBQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.26" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.15.24", + "@swc/core-darwin-x64": "1.15.24", + "@swc/core-linux-arm-gnueabihf": "1.15.24", + "@swc/core-linux-arm64-gnu": "1.15.24", + "@swc/core-linux-arm64-musl": "1.15.24", + "@swc/core-linux-ppc64-gnu": "1.15.24", + "@swc/core-linux-s390x-gnu": "1.15.24", + "@swc/core-linux-x64-gnu": "1.15.24", + "@swc/core-linux-x64-musl": "1.15.24", + "@swc/core-win32-arm64-msvc": "1.15.24", + "@swc/core-win32-ia32-msvc": "1.15.24", + "@swc/core-win32-x64-msvc": "1.15.24" + }, + "peerDependencies": { + "@swc/helpers": ">=0.5.17" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.24.tgz", + "integrity": "sha512-uM5ZGfFXjtvtJ+fe448PVBEbn/CSxS3UAyLj3O9xOqKIWy3S6hPTXSPbszxkSsGDYKi+YFhzAsR4r/eXLxEQ0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.24.tgz", + "integrity": "sha512-fMIb/Zfn929pw25VMBhV7Ji2Dl+lCWtUPNdYJQYOke+00E5fcQ9ynxtP8+qhUo/HZc+mYQb1gJxwHM9vty+lXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.24.tgz", + "integrity": "sha512-vOkjsyjjxnoYx3hMEWcGxQrMgnNrRm6WAegBXrN8foHtDAR+zpdhpGF5a4lj1bNPgXAvmysjui8cM1ov/Clkaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.24.tgz", + "integrity": "sha512-h/oNu+upkXJ6Cicnq7YGVj9PkdfarLCdQa8l/FlHYvfv8CEiMaeeTnpLU7gSBH/rGxosM6Qkfa/J9mThGF9CLA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.24.tgz", + "integrity": "sha512-ZpF/pRe1guk6sKzQI9D1jAORtjTdNlyeXn9GDz8ophof/w2WhojRblvSDJaGe7rJjcPN8AaOkhwdRUh7q8oYIg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-ppc64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-ppc64-gnu/-/core-linux-ppc64-gnu-1.15.24.tgz", + "integrity": "sha512-QZEsZfisHTSJlmyChgDFNmKPb3W6Lhbfo/O76HhIngfEdnQNmukS38/VSe1feho+xkV5A5hETyCbx3sALBZKAQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-s390x-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-s390x-gnu/-/core-linux-s390x-gnu-1.15.24.tgz", + "integrity": "sha512-DLdJKVsJgglqQrJBuoUYNmzm3leI7kUZhLbZGHv42onfKsGf6JDS3+bzCUQfte/XOqDjh/tmmn1DR/CF/tCJFw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.24.tgz", + "integrity": "sha512-IpLYfposPA/XLxYOKpRfeccl1p5dDa3+okZDHHTchBkXEaVCnq5MADPmIWwIYj1tudt7hORsEHccG5no6IUQRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.24.tgz", + "integrity": "sha512-JHy3fMSc0t/EPWgo74+OK5TGr51aElnzqfUPaiRf2qJ/BfX5CUCfMiWVBuhI7qmVMBnk1jTRnL/xZnOSHDPLYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.24.tgz", + "integrity": "sha512-Txj+qUH1z2bUd1P3JvwByfjKFti3cptlAxhWgmunBUUxy/IW3CXLZ6l6Gk4liANadKkU71nIU1X30Z5vpMT3BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.24.tgz", + "integrity": "sha512-15D/nl3XwrhFpMv+MADFOiVwv3FvH9j8c6Rf8EXBT3Q5LoMh8YnDnSgPYqw1JzPnksvsBX6QPXLiPqmcR/Z4qQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.15.24", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.24.tgz", + "integrity": "sha512-PR0PlTlPra2JbaDphrOAzm6s0v9rA0F17YzB+XbWD95B4g2cWcZY9LAeTa4xll70VLw9Jr7xBrlohqlQmelMFQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@swc/types": { + "version": "0.1.26", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.26.tgz", + "integrity": "sha512-lyMwd7WGgG79RS7EERZV3T8wMdmPq3xwyg+1nmAM64kIhx5yl+juO2PYIHb7vTiPgPCj8LYjsNV2T5wiQHUEaw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.19.tgz", + "integrity": "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==", + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tweenjs/tween.js": { + "version": "23.1.3", + "resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-23.1.3.tgz", + "integrity": "sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/draco3d": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/draco3d/-/draco3d-1.4.10.tgz", + "integrity": "sha512-AX22jp8Y7wwaBgAixaSvkoG4M/+PlAcm3Qs4OW8yT9DM4xUpWKeFhLueTAyZF39pviAdcDdeJoACapiAceqNcw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.19.0" + } + }, + "node_modules/@types/offscreencanvas": { + "version": "2019.7.3", + "resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz", + "integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==", + "license": "MIT" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "devOptional": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@types/react-reconciler": { + "version": "0.26.7", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.26.7.tgz", + "integrity": "sha512-mBDYl8x+oyPX/VBb3E638N0B7xG+SPk/EAMcVPeexqus/5aTpTphQi0curhhshOqRrc9t6OPoJfEUkbymse/lQ==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/stats.js": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", + "integrity": "sha512-jIBvWWShCvlBqBNIZt0KAshWpvSjhkwkEu4ZUcASoAvhmrgAUI2t1dXrjSL4xXVLB4FznPrIsX3nKXFl/Dt4vA==", + "license": "MIT" + }, + "node_modules/@types/three": { + "version": "0.183.1", + "resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz", + "integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==", + "license": "MIT", + "dependencies": { + "@dimforge/rapier3d-compat": "~0.12.0", + "@tweenjs/tween.js": "~23.1.3", + "@types/stats.js": "*", + "@types/webxr": ">=0.5.17", + "@webgpu/types": "*", + "fflate": "~0.8.2", + "meshoptimizer": "~1.0.1" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/webxr": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.24.tgz", + "integrity": "sha512-h8fgEd/DpoS9CBrjEQXR+dIDraopAEfu4wYVNY2tEPwk60stPWhvZMf4Foo5FakuQ7HFZoa8WceaWFervK2Ovg==", + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, + "node_modules/@use-gesture/core": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/core/-/core-10.3.1.tgz", + "integrity": "sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==", + "license": "MIT" + }, + "node_modules/@use-gesture/react": { + "version": "10.3.1", + "resolved": "https://registry.npmjs.org/@use-gesture/react/-/react-10.3.1.tgz", + "integrity": "sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==", + "license": "MIT", + "dependencies": { + "@use-gesture/core": "10.3.1" + }, + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/@vitejs/plugin-react-swc": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.11.0.tgz", + "integrity": "sha512-YTJCGFdNMHCMfjODYtxRNVAYmTWQ1Lb8PulP/2/f/oEEtglw8oKxKIZmmRkyXrVrHfsKOaVkAc3NT9/dMutO5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.27", + "@swc/core": "^1.12.11" + }, + "peerDependencies": { + "vite": "^4 || ^5 || ^6 || ^7" + } + }, + "node_modules/@webgpu/types": { + "version": "0.1.69", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.69.tgz", + "integrity": "sha512-RPmm6kgRbI8e98zSD3RVACvnuktIja5+yLgDAkTmxLr90BEwdTXRQWNLF3ETTTyH/8mKhznZuN5AveXYFEsMGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@yomguithereal/helpers": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@yomguithereal/helpers/-/helpers-1.1.1.tgz", + "integrity": "sha512-UYvAq/XCA7xoh1juWDYsq3W0WywOB+pz8cgVnE1b45ZfdMhBvHDrgmSFG3jXeZSr2tMTYLGHFHON+ekG05Jebg==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "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.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.27", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", + "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001774", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.17", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.17.tgz", + "integrity": "sha512-HdrkN8eVG2CXxeifv/VdJ4A4RSra1DTW8dc/hdxzhGHN8QePs6gKaWM9pHPcpCoxYZJuOZ8drHmbdpLHjCYjLA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "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.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/camera-controls": { + "version": "1.38.2", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-1.38.2.tgz", + "integrity": "sha512-EfzbovxLssyWpJVG9uKcazSDDIEcd1hUsPhPF/OWWnICsKY9WbLY/2S4UPW73HHbvnVeR/Z9wsWaQKtANy/2Yg==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001787", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", + "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/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/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "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-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/ctrl-keys": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/ctrl-keys/-/ctrl-keys-1.0.6.tgz", + "integrity": "sha512-fENSKrbIfvX83uHxruP3S/9GizirvgT66vHhgKHOCTVHK+22Xpud/vttg5c5IifRl+6Gom/GjE+ZSXJKf0DMTA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-binarytree": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d3-binarytree/-/d3-binarytree-1.0.2.tgz", + "integrity": "sha512-cElUNH+sHu95L04m92pG73t2MEJXKu+GeKUN1TJkFsu93E5W8E9Sc3kHEGJKgenGvj19m6upSn2EunvMgMD2Yw==", + "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-force-3d": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/d3-force-3d/-/d3-force-3d-3.0.6.tgz", + "integrity": "sha512-4tsKHUPLOVkyfEffZo1v6sFHvGFwAIIjt/W8IThbp08DYAsXZck+2pSHEG5W1+gQgEvFLdZkYvmJAbRM2EzMnA==", + "license": "MIT", + "dependencies": { + "d3-binarytree": "1", + "d3-dispatch": "1 - 3", + "d3-octree": "1", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-octree": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/d3-octree/-/d3-octree-1.1.0.tgz", + "integrity": "sha512-F8gPlqpP+HwRPMO/8uOu5wjH110+6q4cgJvgJT6vlpy3BEaDIKlTZrgHKZSp/i1InRpVfh4puY/kvL6MxK930A==", + "license": "MIT" + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-gpu": { + "version": "5.0.70", + "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", + "integrity": "sha512-bqerEP1Ese6nt3rFkwPnGbsUF9a4q+gMmpTVVOEzoCyeCc+y7/RvJnQZJx1JwhgQI5Ntg0Kgat8Uu7XpBqnz1w==", + "license": "MIT", + "dependencies": { + "webgl-constants": "^1.1.1" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/draco3d": { + "version": "1.5.7", + "resolved": "https://registry.npmjs.org/draco3d/-/draco3d-1.5.7.tgz", + "integrity": "sha512-m6WCKt/erDXcw+70IJXnG7M3awwQPAsZvJGX5zY7beBqpELw6RDGkYVU0W43AFxye4pDZ5i2Lbyc/NNGqwjUVQ==", + "license": "Apache-2.0" + }, + "node_modules/electron-to-chromium": { + "version": "1.5.334", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.334.tgz", + "integrity": "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog==", + "dev": true, + "license": "ISC" + }, + "node_modules/ellipsize": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/ellipsize/-/ellipsize-0.5.1.tgz", + "integrity": "sha512-0jEAyuIRU6U8MN0S5yUqIrkK/AQWkChh642N3zQuGV57s9bsUWYLc0jJOoDIUkZ2sbEL3ySq8xfq71BvG4q3hw==", + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "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": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-6.1.1.tgz", + "integrity": "sha512-St9EKZzOAQF704nt2oJvAKZHjhrpg25ClQoaAlHmPZuajFldVLqRDW4VBNAS01NzeiQF0m0qhG1ZA807K6aVaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "zod": "^3.22.4 || ^4.0.0", + "zod-validation-error": "^3.0.3 || ^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" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/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/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.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/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "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==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.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==", + "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": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "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/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/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/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glodrei": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/glodrei/-/glodrei-0.0.1.tgz", + "integrity": "sha512-DMx6ElCSwh1pR4IyDS3LvyFwZHSCCKCqdqo8P1G7klQtqH6PcOjleduCDsHehDtyYQ1E4dzVeoEzHIL1DIxjag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.11.2", + "@mediapipe/tasks-vision": "0.10.8", + "@react-spring/three": "~9.6.1", + "@use-gesture/react": "^10.2.24", + "camera-controls": "^2.4.2", + "cross-env": "^7.0.3", + "detect-gpu": "^5.0.28", + "glsl-noise": "^0.0.0", + "maath": "^0.10.7", + "meshline": "^3.1.6", + "react-composer": "^5.0.3", + "react-merge-refs": "^1.1.0", + "stats-gl": "^2.0.0", + "stats.js": "^0.17.0", + "suspend-react": "^0.1.3", + "three-mesh-bvh": "^0.7.0", + "three-stdlib": "^2.29.4", + "troika-three-text": "^0.47.2", + "tunnel-rat": "^0.1.2", + "utility-types": "^3.10.0", + "uuid": "^9.0.1", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "@react-three/fiber": ">=8.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "three": ">=0.137" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/glodrei/node_modules/@react-spring/animated": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "license": "MIT", + "dependencies": { + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/glodrei/node_modules/@react-spring/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/glodrei/node_modules/@react-spring/rafz": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==", + "license": "MIT" + }, + "node_modules/glodrei/node_modules/@react-spring/shared": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "license": "MIT", + "dependencies": { + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/glodrei/node_modules/@react-spring/three": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.6.1.tgz", + "integrity": "sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "@react-three/fiber": ">=6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "three": ">=0.126" + } + }, + "node_modules/glodrei/node_modules/@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==", + "license": "MIT" + }, + "node_modules/glodrei/node_modules/camera-controls": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.10.1.tgz", + "integrity": "sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/glodrei/node_modules/maath": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.10.8.tgz", + "integrity": "sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.134.0", + "three": ">=0.134.0" + } + }, + "node_modules/glodrei/node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/glodrei/node_modules/three-mesh-bvh": { + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.7.6.tgz", + "integrity": "sha512-rCjsnxEqR9r1/C/lCqzGLS67NDty/S/eT6rAJfDvsanrIctTWdNoR4ZOGWewCB13h1QkVo2BpmC0wakj1+0m8A==", + "license": "MIT", + "peerDependencies": { + "three": ">= 0.151.0" + } + }, + "node_modules/glsl-noise": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/glsl-noise/-/glsl-noise-0.0.0.tgz", + "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", + "license": "MIT" + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/graphology": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/graphology/-/graphology-0.25.4.tgz", + "integrity": "sha512-33g0Ol9nkWdD6ulw687viS8YJQBxqG5LWII6FI6nul0pq6iM2t5EKquOTFDbyTblRB3O9I+7KX4xI8u5ffekAQ==", + "license": "MIT", + "dependencies": { + "events": "^3.3.0", + "obliterator": "^2.0.2" + }, + "peerDependencies": { + "graphology-types": ">=0.24.0" + } + }, + "node_modules/graphology-indices": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/graphology-indices/-/graphology-indices-0.17.0.tgz", + "integrity": "sha512-A7RXuKQvdqSWOpn7ZVQo4S33O0vCfPBnUSf7FwE0zNCasqwZVUaCXePuWo5HBpWw68KJcwObZDHpFk6HKH6MYQ==", + "license": "MIT", + "dependencies": { + "graphology-utils": "^2.4.2", + "mnemonist": "^0.39.0" + }, + "peerDependencies": { + "graphology-types": ">=0.20.0" + } + }, + "node_modules/graphology-layout": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/graphology-layout/-/graphology-layout-0.6.1.tgz", + "integrity": "sha512-m9aMvbd0uDPffUCFPng5ibRkb2pmfNvdKjQWeZrf71RS1aOoat5874+DcyNfMeCT4aQguKC7Lj9eCbqZj/h8Ag==", + "license": "MIT", + "dependencies": { + "graphology-utils": "^2.3.0", + "pandemonium": "^2.4.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-layout-forceatlas2": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/graphology-layout-forceatlas2/-/graphology-layout-forceatlas2-0.10.1.tgz", + "integrity": "sha512-ogzBeF1FvWzjkikrIFwxhlZXvD2+wlY54lqhsrWprcdPjopM2J9HoMweUmIgwaTvY4bUYVimpSsOdvDv1gPRFQ==", + "license": "MIT", + "dependencies": { + "graphology-utils": "^2.1.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-layout-noverlap": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/graphology-layout-noverlap/-/graphology-layout-noverlap-0.4.2.tgz", + "integrity": "sha512-13WwZSx96zim6l1dfZONcqLh3oqyRcjIBsqz2c2iJ3ohgs3605IDWjldH41Gnhh462xGB1j6VGmuGhZ2FKISXA==", + "license": "MIT", + "dependencies": { + "graphology-utils": "^2.3.0" + }, + "peerDependencies": { + "graphology-types": ">=0.19.0" + } + }, + "node_modules/graphology-metrics": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/graphology-metrics/-/graphology-metrics-2.4.0.tgz", + "integrity": "sha512-7WOfOP+mFLCaTJx55Qg4eY+211vr1/b3D/R3biz3SXGhAaCVcWYkfabnmO4O4WBNWANEHtVnFrGgJ0kj6MM6xw==", + "license": "MIT", + "dependencies": { + "graphology-indices": "^0.17.0", + "graphology-shortest-path": "^2.0.0", + "graphology-utils": "^2.4.4", + "mnemonist": "^0.39.0", + "pandemonium": "2.4.1" + }, + "peerDependencies": { + "graphology-types": ">=0.20.0" + } + }, + "node_modules/graphology-shortest-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/graphology-shortest-path/-/graphology-shortest-path-2.1.0.tgz", + "integrity": "sha512-KbT9CTkP/u72vGEJzyRr24xFC7usI9Es3LMmCPHGwQ1KTsoZjxwA9lMKxfU0syvT/w+7fZUdB/Hu2wWYcJBm6Q==", + "license": "MIT", + "dependencies": { + "@yomguithereal/helpers": "^1.1.1", + "graphology-indices": "^0.17.0", + "graphology-utils": "^2.4.3", + "mnemonist": "^0.39.0" + }, + "peerDependencies": { + "graphology-types": ">=0.20.0" + } + }, + "node_modules/graphology-types": { + "version": "0.24.8", + "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz", + "integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==", + "license": "MIT", + "peer": true + }, + "node_modules/graphology-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/graphology-utils/-/graphology-utils-2.5.2.tgz", + "integrity": "sha512-ckHg8MXrXJkOARk56ZaSCM1g1Wihe2d6iTmz1enGOz4W/l831MBCKSayeFQfowgF8wd+PQ4rlch/56Vs/VZLDQ==", + "license": "MIT", + "peerDependencies": { + "graphology-types": ">=0.23.0" + } + }, + "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/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hold-event": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/hold-event/-/hold-event-0.2.0.tgz", + "integrity": "sha512-rko5P1XgHzy4B0NR0xVHEpWPgj0i23f8Mf8qsOugd1CHvfLR0PyIyy+8TAQQA9v8qAa1OZ4XuCKk04rxmPGHNQ==", + "license": "MIT" + }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/i18next": { + "version": "23.16.8", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", + "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "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/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/install": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.13.0.tgz", + "integrity": "sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/its-fine": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz", + "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==", + "license": "MIT", + "dependencies": { + "@types/react-reconciler": "^0.28.0" + }, + "peerDependencies": { + "react": ">=18.0" + } + }, + "node_modules/its-fine/node_modules/@types/react-reconciler": { + "version": "0.28.9", + "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz", + "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "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/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/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/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "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.clamp": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/lodash.clamp/-/lodash.clamp-4.0.3.tgz", + "integrity": "sha512-HvzRFWjtcguTW7yd8NJBshuNaCa8aqNFtnswdT7f/cMd/1YKy5Zzoq4W/Oxvnx9l7aeY258uSdDfM793+eLsVg==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.omit": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-4.18.0.tgz", + "integrity": "sha512-hZXIupXdHtocTnvIJ2aCd2vxKYtxex6gbiGuPvgBRnFQO9yu3AtmDAbVuCXcSsQx3INo/1g71OktlFFA/ES8Xg==", + "license": "MIT" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "deprecated": "This package is deprecated. Use destructuring assignment syntax instead.", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/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/lucide-react": { + "version": "0.390.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.390.0.tgz", + "integrity": "sha512-APqbfEcVuHnZbiy3E97gYWLeBdkE4e6NbY6AuVETZDZVn/bQCHYUoHyxcUHyvRopfPOHhFUEvDyyQzHwM+S9/w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/maath": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/maath/-/maath-0.5.3.tgz", + "integrity": "sha512-ut63A4zTd9abtpi+sOHW1fPWPtAFrjK0E17eAthx1k93W/T2cWLKV5oaswyotJVDvvW1EXSdokAqhK5KOu0Qdw==", + "license": "MIT", + "peerDependencies": { + "@types/three": ">=0.144.0", + "three": ">=0.144.0" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/meshline": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/meshline/-/meshline-3.3.1.tgz", + "integrity": "sha512-/TQj+JdZkeSUOl5Mk2J7eLcYTLiQm2IDzmlSvYm7ov15anEcDJ92GHqqazxTSreeNgfnYu24kiEvvv0WlbCdFQ==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.137" + } + }, + "node_modules/meshoptimizer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-1.0.1.tgz", + "integrity": "sha512-Vix+QlA1YYT3FwmBBZ+49cE5y/b+pRrcXKqGpS5ouh33d3lSp2PoTpCw19E0cKDFWalembrHnIaZetf27a+W2g==", + "license": "MIT" + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mnemonist": { + "version": "0.39.8", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.39.8.tgz", + "integrity": "sha512-vyWo2K3fjrUw8YeeZ1zF0fy6Mu59RHokURlld8ymdUPjMlD9EC9ov1/YPqTgqRvUN9nTr3Gqfz29LYAmu0PHPQ==", + "license": "MIT", + "dependencies": { + "obliterator": "^2.0.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/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/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm": { + "version": "10.9.8", + "resolved": "https://registry.npmjs.org/npm/-/npm-10.9.8.tgz", + "integrity": "sha512-fYwb6ODSmHkqrJQQaCxY3M2lPf/mpgC7ik0HSzzIwG5CGtabRp4bNqikatvCoT42b5INQSqudVH0R7yVmC9hVg==", + "bundleDependencies": [ + "@isaacs/string-locale-compare", + "@npmcli/arborist", + "@npmcli/config", + "@npmcli/fs", + "@npmcli/map-workspaces", + "@npmcli/package-json", + "@npmcli/promise-spawn", + "@npmcli/redact", + "@npmcli/run-script", + "@sigstore/tuf", + "abbrev", + "archy", + "cacache", + "chalk", + "ci-info", + "cli-columns", + "fastest-levenshtein", + "fs-minipass", + "glob", + "graceful-fs", + "hosted-git-info", + "ini", + "init-package-json", + "is-cidr", + "json-parse-even-better-errors", + "libnpmaccess", + "libnpmdiff", + "libnpmexec", + "libnpmfund", + "libnpmhook", + "libnpmorg", + "libnpmpack", + "libnpmpublish", + "libnpmsearch", + "libnpmteam", + "libnpmversion", + "make-fetch-happen", + "minimatch", + "minipass", + "minipass-pipeline", + "ms", + "node-gyp", + "nopt", + "normalize-package-data", + "npm-audit-report", + "npm-install-checks", + "npm-package-arg", + "npm-pick-manifest", + "npm-profile", + "npm-registry-fetch", + "npm-user-validate", + "p-map", + "pacote", + "parse-conflict-json", + "proc-log", + "qrcode-terminal", + "read", + "semver", + "spdx-expression-parse", + "ssri", + "supports-color", + "tar", + "text-table", + "tiny-relative-date", + "treeverse", + "validate-npm-package-name", + "which", + "write-file-atomic" + ], + "license": "Artistic-2.0", + "workspaces": [ + "docs", + "smoke-tests", + "mock-globals", + "mock-registry", + "workspaces/*" + ], + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/arborist": "^8.0.5", + "@npmcli/config": "^9.0.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/map-workspaces": "^4.0.2", + "@npmcli/package-json": "^6.2.0", + "@npmcli/promise-spawn": "^8.0.3", + "@npmcli/redact": "^3.2.2", + "@npmcli/run-script": "^9.1.0", + "@sigstore/tuf": "^3.1.1", + "abbrev": "^3.0.1", + "archy": "~1.0.0", + "cacache": "^19.0.1", + "chalk": "^5.6.2", + "ci-info": "^4.4.0", + "cli-columns": "^4.0.0", + "fastest-levenshtein": "^1.0.16", + "fs-minipass": "^3.0.3", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "hosted-git-info": "^8.1.0", + "ini": "^5.0.0", + "init-package-json": "^7.0.2", + "is-cidr": "^5.1.1", + "json-parse-even-better-errors": "^4.0.0", + "libnpmaccess": "^9.0.0", + "libnpmdiff": "^7.0.5", + "libnpmexec": "^9.0.5", + "libnpmfund": "^6.0.5", + "libnpmhook": "^11.0.0", + "libnpmorg": "^7.0.0", + "libnpmpack": "^8.0.5", + "libnpmpublish": "^10.0.2", + "libnpmsearch": "^8.0.0", + "libnpmteam": "^7.0.0", + "libnpmversion": "^7.0.0", + "make-fetch-happen": "^14.0.3", + "minimatch": "^9.0.9", + "minipass": "^7.1.3", + "minipass-pipeline": "^1.2.4", + "ms": "^2.1.2", + "node-gyp": "^11.5.0", + "nopt": "^8.1.0", + "normalize-package-data": "^7.0.1", + "npm-audit-report": "^6.0.0", + "npm-install-checks": "^7.1.2", + "npm-package-arg": "^12.0.2", + "npm-pick-manifest": "^10.0.0", + "npm-profile": "^11.0.1", + "npm-registry-fetch": "^18.0.2", + "npm-user-validate": "^3.0.0", + "p-map": "^7.0.4", + "pacote": "^19.0.1", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "qrcode-terminal": "^0.12.0", + "read": "^4.1.0", + "semver": "^7.7.4", + "spdx-expression-parse": "^4.0.0", + "ssri": "^12.0.0", + "supports-color": "^9.4.0", + "tar": "^7.5.11", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "treeverse": "^3.0.0", + "validate-npm-package-name": "^6.0.2", + "which": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "bin": { + "npm": "bin/npm-cli.js", + "npx": "bin/npx-cli.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui": { + "version": "8.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/@isaacs/fs-minipass": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.4" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/npm/node_modules/@isaacs/string-locale-compare": { + "version": "1.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/@npmcli/agent": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "agent-base": "^7.1.0", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.1", + "lru-cache": "^10.0.1", + "socks-proxy-agent": "^8.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/arborist": { + "version": "8.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@isaacs/string-locale-compare": "^1.1.0", + "@npmcli/fs": "^4.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/metavuln-calculator": "^8.0.0", + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.1", + "@npmcli/query": "^4.0.0", + "@npmcli/redact": "^3.0.0", + "@npmcli/run-script": "^9.0.1", + "bin-links": "^5.0.0", + "cacache": "^19.0.1", + "common-ancestor-path": "^1.0.1", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "json-stringify-nice": "^1.1.4", + "lru-cache": "^10.2.2", + "minimatch": "^9.0.4", + "nopt": "^8.0.0", + "npm-install-checks": "^7.1.0", + "npm-package-arg": "^12.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.1", + "pacote": "^19.0.0", + "parse-conflict-json": "^4.0.0", + "proc-log": "^5.0.0", + "proggy": "^3.0.0", + "promise-all-reject-late": "^1.0.0", + "promise-call-limit": "^3.0.1", + "promise-retry": "^2.0.1", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "ssri": "^12.0.0", + "treeverse": "^3.0.0", + "walk-up-path": "^3.0.1" + }, + "bin": { + "arborist": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/config": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/map-workspaces": "^4.0.1", + "@npmcli/package-json": "^6.0.1", + "ci-info": "^4.0.0", + "ini": "^5.0.0", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/fs": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/git": { + "version": "6.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/promise-spawn": "^8.0.0", + "ini": "^5.0.0", + "lru-cache": "^10.0.1", + "npm-pick-manifest": "^10.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "semver": "^7.3.5", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/installed-package-contents": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-bundled": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "bin": { + "installed-package-contents": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/map-workspaces": { + "version": "4.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/name-from-folder": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "glob": "^10.2.2", + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator": { + "version": "8.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cacache": "^19.0.0", + "json-parse-even-better-errors": "^4.0.0", + "pacote": "^20.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/metavuln-calculator/node_modules/pacote": { + "version": "20.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^7.5.10" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/name-from-folder": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/node-gyp": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/package-json": { + "version": "6.2.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "glob": "^10.2.2", + "hosted-git-info": "^8.0.0", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.5.3", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/promise-spawn": { + "version": "8.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/query": { + "version": "4.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "postcss-selector-parser": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/redact": { + "version": "3.2.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@npmcli/run-script": { + "version": "9.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "inBundle": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/@sigstore/bundle": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/core": { + "version": "2.0.0", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/protobuf-specs": { + "version": "0.4.3", + "inBundle": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/sign": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "make-fetch-happen": "^14.0.2", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/tuf": { + "version": "3.1.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/protobuf-specs": "^0.4.1", + "tuf-js": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@sigstore/verify": { + "version": "2.1.1", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/@tufjs/canonical-json": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/abbrev": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/agent-base": { + "version": "7.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/ansi-styles": { + "version": "6.2.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/aproba": { + "version": "2.1.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/archy": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/bin-links": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cmd-shim": "^7.0.0", + "npm-normalize-package-bin": "^4.0.0", + "proc-log": "^5.0.0", + "read-cmd-shim": "^5.0.0", + "write-file-atomic": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/binary-extensions": { + "version": "2.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/brace-expansion": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/npm/node_modules/cacache": { + "version": "19.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/fs": "^4.0.0", + "fs-minipass": "^3.0.0", + "glob": "^10.2.2", + "lru-cache": "^10.0.1", + "minipass": "^7.0.3", + "minipass-collect": "^2.0.1", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "p-map": "^7.0.2", + "ssri": "^12.0.0", + "tar": "^7.4.3", + "unique-filename": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/chalk": { + "version": "5.6.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/npm/node_modules/chownr": { + "version": "3.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/ci-info": { + "version": "4.4.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/cidr-regex": { + "version": "4.1.3", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "ip-regex": "^5.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/cli-columns": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/npm/node_modules/cmd-shim": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/npm/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/common-ancestor-path": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/cross-spawn": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/cssesc": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/debug": { + "version": "4.4.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/diff": { + "version": "5.2.2", + "inBundle": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/npm/node_modules/eastasianwidth": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/encoding": { + "version": "0.1.13", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/npm/node_modules/env-paths": { + "version": "2.2.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/npm/node_modules/err-code": { + "version": "2.0.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/exponential-backoff": { + "version": "3.1.3", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/npm/node_modules/fastest-levenshtein": { + "version": "1.0.16", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/npm/node_modules/fdir": { + "version": "6.5.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/npm/node_modules/foreground-child": { + "version": "3.3.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/fs-minipass": { + "version": "3.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/glob": { + "version": "10.5.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/hosted-git-info": { + "version": "8.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^10.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/http-cache-semantics": { + "version": "4.2.0", + "inBundle": true, + "license": "BSD-2-Clause" + }, + "node_modules/npm/node_modules/http-proxy-agent": { + "version": "7.0.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/https-proxy-agent": { + "version": "7.0.6", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/iconv-lite": { + "version": "0.6.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm/node_modules/ignore-walk": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minimatch": "^9.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/imurmurhash": { + "version": "0.1.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/npm/node_modules/ini": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/init-package-json": { + "version": "7.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/package-json": "^6.0.0", + "npm-package-arg": "^12.0.0", + "promzard": "^2.0.0", + "read": "^4.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/ip-address": { + "version": "10.1.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/npm/node_modules/ip-regex": { + "version": "5.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/is-cidr": { + "version": "5.1.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "cidr-regex": "^4.1.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/npm/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/isexe": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/jackspeak": { + "version": "3.4.3", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/npm/node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/json-stringify-nice": { + "version": "1.1.4", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/jsonparse": { + "version": "1.3.1", + "engines": [ + "node >= 0.2.0" + ], + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff": { + "version": "6.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/just-diff-apply": { + "version": "5.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/libnpmaccess": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmdiff": { + "version": "7.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5", + "@npmcli/installed-package-contents": "^3.0.0", + "binary-extensions": "^2.3.0", + "diff": "^5.1.0", + "minimatch": "^9.0.4", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "tar": "^7.5.11" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmexec": { + "version": "9.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5", + "@npmcli/run-script": "^9.0.1", + "ci-info": "^4.0.0", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0", + "proc-log": "^5.0.0", + "read": "^4.0.0", + "read-package-json-fast": "^4.0.0", + "semver": "^7.3.7", + "walk-up-path": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmfund": { + "version": "6.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmhook": { + "version": "11.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmorg": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpack": { + "version": "8.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/arborist": "^8.0.5", + "@npmcli/run-script": "^9.0.1", + "npm-package-arg": "^12.0.0", + "pacote": "^19.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmpublish": { + "version": "10.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ci-info": "^4.0.0", + "normalize-package-data": "^7.0.0", + "npm-package-arg": "^12.0.0", + "npm-registry-fetch": "^18.0.1", + "proc-log": "^5.0.0", + "semver": "^7.3.7", + "sigstore": "^3.0.0", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmsearch": { + "version": "8.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmteam": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "aproba": "^2.0.0", + "npm-registry-fetch": "^18.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/libnpmversion": { + "version": "7.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.1", + "@npmcli/run-script": "^9.0.1", + "json-parse-even-better-errors": "^4.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.7" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/lru-cache": { + "version": "10.4.3", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/make-fetch-happen": { + "version": "14.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/agent": "^3.0.0", + "cacache": "^19.0.1", + "http-cache-semantics": "^4.1.1", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^1.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "ssri": "^12.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/minimatch": { + "version": "9.0.9", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/minipass": { + "version": "7.1.3", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-collect": { + "version": "2.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/npm/node_modules/minipass-fetch": { + "version": "4.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.0.3", + "minipass-sized": "^1.0.3", + "minizlib": "^3.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" + } + }, + "node_modules/npm/node_modules/minipass-flush": { + "version": "1.0.5", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-flush/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minipass-pipeline": { + "version": "1.2.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-pipeline/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minipass-sized": { + "version": "1.0.3", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/minipass-sized/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/minizlib": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "minipass": "^7.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/npm/node_modules/ms": { + "version": "2.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/mute-stream": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/negotiator": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm/node_modules/node-gyp": { + "version": "11.5.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.0", + "exponential-backoff": "^3.1.1", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^14.0.3", + "nopt": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "tar": "^7.4.3", + "tinyglobby": "^0.2.12", + "which": "^5.0.0" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/nopt": { + "version": "8.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "abbrev": "^3.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/normalize-package-data": { + "version": "7.0.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^8.0.0", + "semver": "^7.3.5", + "validate-npm-package-license": "^3.0.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-audit-report": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-bundled": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-install-checks": { + "version": "7.1.2", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "semver": "^7.1.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-package-arg": { + "version": "12.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "hosted-git-info": "^8.0.0", + "proc-log": "^5.0.0", + "semver": "^7.3.5", + "validate-npm-package-name": "^6.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-packlist": { + "version": "9.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "ignore-walk": "^7.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-pick-manifest": { + "version": "10.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-install-checks": "^7.1.0", + "npm-normalize-package-bin": "^4.0.0", + "npm-package-arg": "^12.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-profile": { + "version": "11.0.1", + "inBundle": true, + "license": "ISC", + "dependencies": { + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-registry-fetch": { + "version": "18.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/redact": "^3.0.0", + "jsonparse": "^1.3.1", + "make-fetch-happen": "^14.0.0", + "minipass": "^7.0.2", + "minipass-fetch": "^4.0.0", + "minizlib": "^3.0.1", + "npm-package-arg": "^12.0.0", + "proc-log": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/npm-user-validate": { + "version": "3.0.0", + "inBundle": true, + "license": "BSD-2-Clause", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/p-map": { + "version": "7.0.4", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/package-json-from-dist": { + "version": "1.0.1", + "inBundle": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/npm/node_modules/pacote": { + "version": "19.0.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "@npmcli/git": "^6.0.0", + "@npmcli/installed-package-contents": "^3.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "@npmcli/run-script": "^9.0.0", + "cacache": "^19.0.0", + "fs-minipass": "^3.0.0", + "minipass": "^7.0.2", + "npm-package-arg": "^12.0.0", + "npm-packlist": "^9.0.0", + "npm-pick-manifest": "^10.0.0", + "npm-registry-fetch": "^18.0.0", + "proc-log": "^5.0.0", + "promise-retry": "^2.0.1", + "sigstore": "^3.0.0", + "ssri": "^12.0.0", + "tar": "^7.5.10" + }, + "bin": { + "pacote": "bin/index.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/parse-conflict-json": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "just-diff": "^6.0.0", + "just-diff-apply": "^5.2.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/path-key": { + "version": "3.1.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/path-scurry": { + "version": "1.11.1", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/picomatch": { + "version": "4.0.3", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/npm/node_modules/postcss-selector-parser": { + "version": "7.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/npm/node_modules/proc-log": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/proggy": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/promise-all-reject-late": { + "version": "1.0.1", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-call-limit": { + "version": "3.0.2", + "inBundle": true, + "license": "ISC", + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/promise-retry": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/promzard": { + "version": "2.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "read": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/qrcode-terminal": { + "version": "0.12.0", + "inBundle": true, + "bin": { + "qrcode-terminal": "bin/qrcode-terminal.js" + } + }, + "node_modules/npm/node_modules/read": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "mute-stream": "^2.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-cmd-shim": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/read-package-json-fast": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/retry": { + "version": "0.12.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/npm/node_modules/safer-buffer": { + "version": "2.1.2", + "inBundle": true, + "license": "MIT", + "optional": true + }, + "node_modules/npm/node_modules/semver": { + "version": "7.7.4", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/npm/node_modules/shebang-command": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/shebang-regex": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/signal-exit": { + "version": "4.1.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/npm/node_modules/sigstore": { + "version": "3.1.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@sigstore/bundle": "^3.1.0", + "@sigstore/core": "^2.0.0", + "@sigstore/protobuf-specs": "^0.4.0", + "@sigstore/sign": "^3.1.0", + "@sigstore/tuf": "^3.1.0", + "@sigstore/verify": "^2.1.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/smart-buffer": { + "version": "4.2.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks": { + "version": "2.8.7", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/npm/node_modules/socks-proxy-agent": { + "version": "8.0.5", + "inBundle": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/npm/node_modules/spdx-correct": { + "version": "3.2.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-correct/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-exceptions": { + "version": "2.5.0", + "inBundle": true, + "license": "CC-BY-3.0" + }, + "node_modules/npm/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/spdx-license-ids": { + "version": "3.0.23", + "inBundle": true, + "license": "CC0-1.0" + }, + "node_modules/npm/node_modules/ssri": { + "version": "12.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/npm/node_modules/supports-color": { + "version": "9.4.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/npm/node_modules/tar": { + "version": "7.5.11", + "inBundle": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/fs-minipass": "^4.0.0", + "chownr": "^3.0.0", + "minipass": "^7.1.2", + "minizlib": "^3.1.0", + "yallist": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/text-table": { + "version": "0.2.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tiny-relative-date": { + "version": "1.3.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/tinyglobby": { + "version": "0.2.15", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/npm/node_modules/treeverse": { + "version": "3.0.0", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/npm/node_modules/tuf-js": { + "version": "3.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/models": "3.0.1", + "debug": "^4.4.1", + "make-fetch-happen": "^14.0.3" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/tuf-js/node_modules/@tufjs/models": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@tufjs/canonical-json": "2.0.0", + "minimatch": "^9.0.5" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-filename": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "unique-slug": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/unique-slug": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/util-deprecate": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/validate-npm-package-license": { + "version": "3.0.4", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-license/node_modules/spdx-expression-parse": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/npm/node_modules/validate-npm-package-name": { + "version": "6.0.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/walk-up-path": { + "version": "3.0.1", + "inBundle": true, + "license": "ISC" + }, + "node_modules/npm/node_modules/which": { + "version": "5.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/which/node_modules/isexe": { + "version": "3.1.5", + "inBundle": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/npm/node_modules/wrap-ansi": { + "version": "8.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.2.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "9.2.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { + "version": "5.1.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/npm/node_modules/write-file-atomic": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm/node_modules/yallist": { + "version": "5.0.0", + "inBundle": true, + "license": "BlueOak-1.0.0", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/obliterator": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-2.0.5.tgz", + "integrity": "sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==", + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "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/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/pandemonium": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/pandemonium/-/pandemonium-2.4.1.tgz", + "integrity": "sha512-wRqjisUyiUfXowgm7MFH2rwJzKIr20rca5FsHXCMNm1W5YPP1hCtrZfgmQ62kP7OZ7Xt+cR858aB28lu5NX55g==", + "license": "MIT", + "dependencies": { + "mnemonist": "^0.39.2" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "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-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", + "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.59.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.59.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", + "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/postcss": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", + "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-nested/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/potpack": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/potpack/-/potpack-1.0.2.tgz", + "integrity": "sha512-choctRBIV9EMT9WGAZHn3V7t0Z2pMQyl0EZE6pFc/6ml3ssw7Dlf/oAOvFwjm1HVsqfQN8GfeFyJ+d8tRzqueQ==", + "license": "ISC" + }, + "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==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-chatbot-kit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/react-chatbot-kit/-/react-chatbot-kit-2.2.2.tgz", + "integrity": "sha512-8p/i0KkzkhoyG2XsL6Pb6f72k9j7GYNAc5SOa4f9OZwbCD3Q34uEruNPc06qa1wZHKfT6aFna19PA2plFuO2NA==", + "license": "MIT", + "dependencies": { + "react-conditionally-render": "^1.0.2" + } + }, + "node_modules/react-composer": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/react-composer/-/react-composer-5.0.3.tgz", + "integrity": "sha512-1uWd07EME6XZvMfapwZmc7NgCZqDemcvicRi3wMJzXsQLvZ3L7fTHVyPy1bZdnWXM4iPjYuNE+uJ41MLKeTtnA==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.6.0" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-conditionally-render": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/react-conditionally-render/-/react-conditionally-render-1.0.2.tgz", + "integrity": "sha512-CtjIgaLHVDSgHis3gv/PT/8EnD6GPUL8PrhUjh7DP6S5Y3p56dGu7y2nVg6pYv1kv+fGznRhRmX3assr/vRw3A==", + "license": "ISC" + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-dom/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/react-hook-form": { + "version": "7.72.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.1.tgz", + "integrity": "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-i18next": { + "version": "14.1.3", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-14.1.3.tgz", + "integrity": "sha512-wZnpfunU6UIAiJ+bxwOiTmBOAaB14ha97MjOEnLGac2RJ+h/maIYXZuTHlmyqQVX1UVHmU1YDTQ5vxLmwfXTjw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.9", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/react-markdown": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz", + "integrity": "sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-merge-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/react-merge-refs/-/react-merge-refs-1.1.0.tgz", + "integrity": "sha512-alTKsjEL0dKH/ru1Iyn7vliS2QRcBp9zZPGoWxUOvRGWPUYgjo+V01is7p04It6KhgrzhJGnIj9GgX8W4bZoCQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/react-reconciler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.27.0.tgz", + "integrity": "sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.21.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^18.0.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-use-gesture": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/react-use-gesture/-/react-use-gesture-9.1.3.tgz", + "integrity": "sha512-CdqA2SmS/fj3kkS2W8ZU8wjTbVBAIwDWaRprX7OKaj7HlGwBasGEFggmk5qNklknqk9zK/h8D355bEJFTpqEMg==", + "deprecated": "This package is no longer maintained. Please use @use-gesture/react instead", + "license": "MIT", + "peerDependencies": { + "react": ">= 16.8.0" + } + }, + "node_modules/react-use-measure": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/react-use-measure/-/react-use-measure-2.1.7.tgz", + "integrity": "sha512-KrvcAo13I/60HpwGO5jpW7E9DfusKyLPLvuHlUyP5zqnmAPhNc6qTRjUQrdTADl0lpPpDVU2/Gg51UlOGHXbdg==", + "license": "MIT", + "peerDependencies": { + "react": ">=16.13", + "react-dom": ">=16.13" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-use-websocket": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/react-use-websocket/-/react-use-websocket-4.13.0.tgz", + "integrity": "sha512-anMuVoV//g2N76Wxqvqjjo1X48r9Np3y1/gMl7arX84tAPXdy5R7sB5lO5hvCzQRYjqXwV8XMAiEBOUbyrZFrw==", + "license": "MIT" + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reagraph": { + "version": "4.15.19", + "resolved": "https://registry.npmjs.org/reagraph/-/reagraph-4.15.19.tgz", + "integrity": "sha512-acM2agUYyNKyKLzKhnEoMNbBc58KxpBQ5wzIqYvsoVa3Se2weuB8npVfdjJZV9AxW9BaSaeu90NwCrcO3XATTg==", + "license": "Apache-2.0", + "dependencies": { + "@react-spring/three": "9.6.1", + "@react-three/fiber": "8.13.5", + "camera-controls": "^2.8.3", + "classnames": "^2.5.1", + "d3-array": "^3.2.4", + "d3-force-3d": "^3.0.3", + "d3-hierarchy": "^3.1.2", + "d3-scale": "^4.0.2", + "ellipsize": "^0.5.1", + "glodrei": "^0.0.1", + "graphology": "^0.25.4", + "graphology-layout": "^0.6.1", + "graphology-layout-forceatlas2": "^0.10.1", + "graphology-layout-noverlap": "^0.4.2", + "graphology-metrics": "^2.1.0", + "graphology-shortest-path": "^2.0.2", + "hold-event": "^0.2.0", + "react-use-gesture": "^9.1.3", + "reakeys": "^2.0.0", + "three": "^0.154.0", + "three-stdlib": "^2.23.13", + "zustand": "4.3.9" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/reagraph/node_modules/@react-spring/animated": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.6.1.tgz", + "integrity": "sha512-ls/rJBrAqiAYozjLo5EPPLLOb1LM0lNVQcXODTC1SMtS6DbuBCPaKco5svFUQFMP2dso3O+qcC4k9FsKc0KxMQ==", + "license": "MIT", + "dependencies": { + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/reagraph/node_modules/@react-spring/core": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.6.1.tgz", + "integrity": "sha512-3HAAinAyCPessyQNNXe5W0OHzRfa8Yo5P748paPcmMowZ/4sMfaZ2ZB6e5x5khQI8NusOHj8nquoutd6FRY5WQ==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/rafz": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/reagraph/node_modules/@react-spring/rafz": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.6.1.tgz", + "integrity": "sha512-v6qbgNRpztJFFfSE3e2W1Uz+g8KnIBs6SmzCzcVVF61GdGfGOuBrbjIcp+nUz301awVmREKi4eMQb2Ab2gGgyQ==", + "license": "MIT" + }, + "node_modules/reagraph/node_modules/@react-spring/shared": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.6.1.tgz", + "integrity": "sha512-PBFBXabxFEuF8enNLkVqMC9h5uLRBo6GQhRMQT/nRTnemVENimgRd+0ZT4yFnAQ0AxWNiJfX3qux+bW2LbG6Bw==", + "license": "MIT", + "dependencies": { + "@react-spring/rafz": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/reagraph/node_modules/@react-spring/three": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/three/-/three-9.6.1.tgz", + "integrity": "sha512-Tyw2YhZPKJAX3t2FcqvpLRb71CyTe1GvT3V+i+xJzfALgpk10uPGdGaQQ5Xrzmok1340DAeg2pR/MCfaW7b8AA==", + "license": "MIT", + "dependencies": { + "@react-spring/animated": "~9.6.1", + "@react-spring/core": "~9.6.1", + "@react-spring/shared": "~9.6.1", + "@react-spring/types": "~9.6.1" + }, + "peerDependencies": { + "@react-three/fiber": ">=6.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "three": ">=0.126" + } + }, + "node_modules/reagraph/node_modules/@react-spring/types": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.6.1.tgz", + "integrity": "sha512-POu8Mk0hIU3lRXB3bGIGe4VHIwwDsQyoD1F394OK7STTiX9w4dG3cTLljjYswkQN+hDSHRrj4O36kuVa7KPU8Q==", + "license": "MIT" + }, + "node_modules/reagraph/node_modules/@react-three/fiber": { + "version": "8.13.5", + "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-8.13.5.tgz", + "integrity": "sha512-x9QdsaB/Wm/6NGvRXQahPPWfn2dQce7Fg3C2r00NNzyDdqRKw32YavL+WEqjZOOd0nvFpzv7FtaKc+VCOTR59w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.17.8", + "@types/react-reconciler": "^0.26.7", + "its-fine": "^1.0.6", + "react-reconciler": "^0.27.0", + "react-use-measure": "^2.1.1", + "scheduler": "^0.21.0", + "suspend-react": "^0.1.3", + "zustand": "^3.7.1" + }, + "peerDependencies": { + "expo": ">=43.0", + "expo-asset": ">=8.4", + "expo-gl": ">=11.0", + "react": ">=18.0", + "react-dom": ">=18.0", + "react-native": ">=0.64", + "three": ">=0.133" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + }, + "expo-asset": { + "optional": true + }, + "expo-gl": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/reagraph/node_modules/@react-three/fiber/node_modules/zustand": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/reagraph/node_modules/camera-controls": { + "version": "2.10.1", + "resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-2.10.1.tgz", + "integrity": "sha512-KnaKdcvkBJ1Irbrzl8XD6WtZltkRjp869Jx8c0ujs9K+9WD+1D7ryBsCiVqJYUqt6i/HR5FxT7RLASieUD+Q5w==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.126.1" + } + }, + "node_modules/reagraph/node_modules/suspend-react": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.1.3.tgz", + "integrity": "sha512-aqldKgX9aZqpoDp3e8/BZ8Dm7x1pJl+qI3ZKxDN0i/IQTWUwBx/ManmlVJ3wowqbno6c2bmiIfs+Um6LbsjJyQ==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/reagraph/node_modules/three": { + "version": "0.154.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.154.0.tgz", + "integrity": "sha512-Uzz8C/5GesJzv8i+Y2prEMYUwodwZySPcNhuJUdsVMH2Yn4Nm8qlbQe6qRN5fOhg55XB0WiLfTPBxVHxpE60ug==", + "license": "MIT" + }, + "node_modules/reagraph/node_modules/zustand": { + "version": "4.3.9", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.3.9.tgz", + "integrity": "sha512-Tat5r8jOMG1Vcsj8uldMyqYKC5IZvQif8zetmLHs9WoZlntTHmIoNM8TpLRY31ExncuUvUOXehd0kvahkuHjDw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "immer": ">=9.0", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/reakeys": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/reakeys/-/reakeys-2.0.6.tgz", + "integrity": "sha512-dmZPhOwU3NuLjy61CLqf3dGEhhetx4Du7m/DlX1eqZrBKcKrDqpR0O1tHyYMB95KVdhVRjrfcuFFawI7EqGyxQ==", + "license": "Apache-2.0", + "dependencies": { + "ctrl-keys": "^1.0.6" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.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/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.60.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.1.tgz", + "integrity": "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.1", + "@rollup/rollup-android-arm64": "4.60.1", + "@rollup/rollup-darwin-arm64": "4.60.1", + "@rollup/rollup-darwin-x64": "4.60.1", + "@rollup/rollup-freebsd-arm64": "4.60.1", + "@rollup/rollup-freebsd-x64": "4.60.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", + "@rollup/rollup-linux-arm-musleabihf": "4.60.1", + "@rollup/rollup-linux-arm64-gnu": "4.60.1", + "@rollup/rollup-linux-arm64-musl": "4.60.1", + "@rollup/rollup-linux-loong64-gnu": "4.60.1", + "@rollup/rollup-linux-loong64-musl": "4.60.1", + "@rollup/rollup-linux-ppc64-gnu": "4.60.1", + "@rollup/rollup-linux-ppc64-musl": "4.60.1", + "@rollup/rollup-linux-riscv64-gnu": "4.60.1", + "@rollup/rollup-linux-riscv64-musl": "4.60.1", + "@rollup/rollup-linux-s390x-gnu": "4.60.1", + "@rollup/rollup-linux-x64-gnu": "4.60.1", + "@rollup/rollup-linux-x64-musl": "4.60.1", + "@rollup/rollup-openbsd-x64": "4.60.1", + "@rollup/rollup-openharmony-arm64": "4.60.1", + "@rollup/rollup-win32-arm64-msvc": "4.60.1", + "@rollup/rollup-win32-ia32-msvc": "4.60.1", + "@rollup/rollup-win32-x64-gnu": "4.60.1", + "@rollup/rollup-win32-x64-msvc": "4.60.1", + "fsevents": "~2.3.2" + } + }, + "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==", + "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/scheduler": { + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", + "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/stats-gl": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/stats-gl/-/stats-gl-2.4.2.tgz", + "integrity": "sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==", + "license": "MIT", + "dependencies": { + "@types/three": "*", + "three": "^0.170.0" + }, + "peerDependencies": { + "@types/three": "*", + "three": "*" + } + }, + "node_modules/stats-gl/node_modules/three": { + "version": "0.170.0", + "resolved": "https://registry.npmjs.org/three/-/three-0.170.0.tgz", + "integrity": "sha512-FQK+LEpYc0fBD+J8g6oSEyyNzjp+Q7Ks1C568WWaoMRLW+TkNNWmenWeGgJjV105Gd+p/2ql1ZcjYvNiPZBhuQ==", + "license": "MIT" + }, + "node_modules/stats.js": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/stats.js/-/stats.js-0.17.0.tgz", + "integrity": "sha512-hNKz8phvYLPEcRkeG1rsGmV5ChMjKDAWU7/OJJdDErPBNChQXxCo3WZurGpnWc6gZhAzEPFad1aVgyOANH1sMw==", + "license": "MIT" + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-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/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/suspend-react": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/suspend-react/-/suspend-react-0.0.8.tgz", + "integrity": "sha512-ZC3r8Hu1y0dIThzsGw0RLZplnX9yXwfItcvaIzJc2VQVi8TGyGDlu92syMB5ulybfvGLHAI5Ghzlk23UBPF8xg==", + "license": "MIT", + "peerDependencies": { + "react": ">=17.0" + } + }, + "node_modules/tailwind-merge": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss/node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/three": { + "version": "0.183.2", + "resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz", + "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==", + "license": "MIT", + "peer": true + }, + "node_modules/three-mesh-bvh": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/three-mesh-bvh/-/three-mesh-bvh-0.5.24.tgz", + "integrity": "sha512-VTIgfjz8aFoPKTQoMIQQv9jJD4ybFRZuKKE1/kqy78FQcuHQ0+iIWv7C5cSb2inlvs7bNMVY3yRx3RXGZfrvzQ==", + "license": "MIT", + "peerDependencies": { + "three": ">= 0.123.0" + } + }, + "node_modules/three-stdlib": { + "version": "2.36.1", + "resolved": "https://registry.npmjs.org/three-stdlib/-/three-stdlib-2.36.1.tgz", + "integrity": "sha512-XyGQrFmNQ5O/IoKm556ftwKsBg11TIb301MB5dWNicziQBEs2g3gtOYIf7pFiLa0zI2gUwhtCjv9fmjnxKZ1Cg==", + "license": "MIT", + "dependencies": { + "@types/draco3d": "^1.4.0", + "@types/offscreencanvas": "^2019.6.4", + "@types/webxr": "^0.5.2", + "draco3d": "^1.4.1", + "fflate": "^0.6.9", + "potpack": "^1.0.1" + }, + "peerDependencies": { + "three": ">=0.128.0" + } + }, + "node_modules/three-stdlib/node_modules/fflate": { + "version": "0.6.10", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz", + "integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "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==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/troika-three-text": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-text/-/troika-three-text-0.47.2.tgz", + "integrity": "sha512-qylT0F+U7xGs+/PEf3ujBdJMYWbn0Qci0kLqI5BJG2kW1wdg4T1XSxneypnF05DxFqJhEzuaOR9S2SjiyknMng==", + "license": "MIT", + "dependencies": { + "bidi-js": "^1.0.2", + "troika-three-utils": "^0.47.2", + "troika-worker-utils": "^0.47.2", + "webgl-sdf-generator": "1.1.1" + }, + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-three-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-three-utils/-/troika-three-utils-0.47.2.tgz", + "integrity": "sha512-/28plhCxfKtH7MSxEGx8e3b/OXU5A0xlwl+Sbdp0H8FXUHKZDoksduEKmjQayXYtxAyuUiCRunYIv/8Vi7aiyg==", + "license": "MIT", + "peerDependencies": { + "three": ">=0.125.0" + } + }, + "node_modules/troika-worker-utils": { + "version": "0.47.2", + "resolved": "https://registry.npmjs.org/troika-worker-utils/-/troika-worker-utils-0.47.2.tgz", + "integrity": "sha512-mzss4MeyzUkYBppn4x5cdAqrhBHFEuVmMMgLMTyFV23x6GvQMyo+/R5E5Lsbrt7WSt5RfvewjcwD1DChRTA9lA==", + "license": "MIT" + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "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/tunnel-rat": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tunnel-rat/-/tunnel-rat-0.1.2.tgz", + "integrity": "sha512-lR5VHmkPhzdhrM092lI2nACsLO4QubF0/yoOhzX7c+wIpbN1GjHNzCc91QlpxBi+cnx8vVJ+Ur6vL5cEoQPFpQ==", + "license": "MIT", + "dependencies": { + "zustand": "^4.3.2" + } + }, + "node_modules/tunnel-rat/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/tunnel-rat/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 + } + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/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/undici-types": { + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "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-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utility-types": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", + "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/webgl-constants": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-constants/-/webgl-constants-1.1.1.tgz", + "integrity": "sha512-LkBXKjU5r9vAW7Gcu3T5u+5cvSvh5WwINdr0C+9jpzVB41cjQAP5ePArDtk/WHYdVj0GefCgM73BA7FlIiNtdg==" + }, + "node_modules/webgl-sdf-generator": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/webgl-sdf-generator/-/webgl-sdf-generator-1.1.1.tgz", + "integrity": "sha512-9Z0JcMTFxeE+b2x1LJTdnaT8rT8aEp7MVxkNwoycNmJWwPdzoXzMh0BjJSh/AEFP+KPYZUli814h8bJZFIZ2jA==", + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "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": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "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": "3.7.2", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-3.7.2.tgz", + "integrity": "sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==", + "license": "MIT", + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/graphrag-ui/package.json b/graphrag-ui/package.json index 4ef113e..12cca39 100755 --- a/graphrag-ui/package.json +++ b/graphrag-ui/package.json @@ -19,6 +19,9 @@ "@radix-ui/react-select": "^2.1.1", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-tabs": "^1.1.0", + "@react-three/drei": "9.56.1", + "@react-three/fiber": "8.13.3", + "@tailwindcss/typography": "^0.5.18", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "i18next": "^23.11.5", @@ -35,12 +38,9 @@ "react-router-dom": "^6.23.1", "react-use-websocket": "^4.8.1", "reagraph": "4.15.19", - "@react-three/fiber": "8.13.3", - "@react-three/drei": "9.56.1", "remark-gfm": "^4.0.0", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", - "@tailwindcss/typography": "^0.5.18", "zod": "^3.23.8" }, "devDependencies": { From d22fca4984f46686078deb4d1937c67c2db9196a Mon Sep 17 00:00:00 2001 From: Chengbiao Jin Date: Tue, 21 Apr 2026 22:21:21 -0700 Subject: [PATCH 02/70] Update version number --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 3a3cd8c..88c5fb8 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.3.1 +1.4.0 From 51aeeb0c2760668e5e5012bec988aed6c75a96cb Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Tue, 14 Apr 2026 16:09:25 +0530 Subject: [PATCH 03/70] feat: Add Trace Logs UI for agent execution observability --- graphrag-ui/src/actions/ActionProvider.tsx | 8 +- .../src/components/CustomChatMessage.tsx | 14 + graphrag-ui/src/main.tsx | 5 + graphrag-ui/src/pages/TraceLogs.tsx | 697 ++++++++++++++++++ graphrag/app/agent/agent.py | 22 +- 5 files changed, 742 insertions(+), 4 deletions(-) create mode 100644 graphrag-ui/src/pages/TraceLogs.tsx diff --git a/graphrag-ui/src/actions/ActionProvider.tsx b/graphrag-ui/src/actions/ActionProvider.tsx index 58fc7aa..4bda231 100644 --- a/graphrag-ui/src/actions/ActionProvider.tsx +++ b/graphrag-ui/src/actions/ActionProvider.tsx @@ -1,4 +1,4 @@ -import React, {useState, useCallback, useEffect, useContext} from 'react'; +import React, {useState, useRef, useCallback, useEffect, useContext} from 'react'; import {createClientMessage} from 'react-chatbot-kit'; import useWebSocket, {ReadyState} from 'react-use-websocket'; import Loader from '../components/Loader'; @@ -81,6 +81,7 @@ const ActionProvider: React.FC = ({ }) => { const selectedGraph = useContext(SelectedGraphContext); const selectedRagPattern = useContext(RagPatternContext); + const lastUserQueryRef = useRef(""); const WS_URL = "/ui/" + selectedGraph + "/chat" + "?rag_pattern=" + selectedRagPattern; const [messageHistory, setMessageHistory] = useState[]>( [], @@ -205,6 +206,7 @@ const ActionProvider: React.FC = ({ }; const defaultQuestions = (msg: string) => { + lastUserQueryRef.current = msg; const clientMessage = createClientMessage(msg, { delay: 300, }); @@ -213,6 +215,7 @@ const ActionProvider: React.FC = ({ }; const queryGraphragWs = (msg) => { + lastUserQueryRef.current = msg; const queryGraphragWsTest = (msg: string) => { sendMessage(msg); }; @@ -269,6 +272,9 @@ const ActionProvider: React.FC = ({ return; // Don't create a bot message for conversation ID } + // Attach the user query so the trace page can display it + messageData.userQuery = lastUserQueryRef.current; + // Handle regular bot messages const botMessage = createChatBotMessage(messageData); setState((prev) => { diff --git a/graphrag-ui/src/components/CustomChatMessage.tsx b/graphrag-ui/src/components/CustomChatMessage.tsx index 0aef2ea..285ac8f 100755 --- a/graphrag-ui/src/components/CustomChatMessage.tsx +++ b/graphrag-ui/src/components/CustomChatMessage.tsx @@ -10,6 +10,8 @@ import { } from "@/components/ui/dialog" import { ImEnlarge2 } from "react-icons/im"; import { IoIosCloseCircleOutline } from "react-icons/io"; +import { LuActivity } from "react-icons/lu"; +import { useNavigate } from "react-router-dom"; import { Interactions } from "./Interact"; import { KnowledgeGraphPro } from "./graphs/KnowledgeGraphPro"; import { KnowledgeTablPro } from "./tables/KnowledgeTablePro"; @@ -127,6 +129,7 @@ const AuthenticatedImage: FC<{ src: string; alt: string }> = ({ src, alt }) => { export const CustomChatMessage: FC = ({ message, }) => { + const navigate = useNavigate(); const [showResult, setShowResult] = useState(false); const [showGraphVis, setShowGraphVis] = useState(false); const [showTableVis, setShowTableVis] = useState(false); @@ -191,6 +194,17 @@ export const CustomChatMessage: FC = ({ showTable={handleShowTable} showGraph={handleShowGraph} /> + {(message.response_type !== "progress" && (message.query_sources?.result || message.query_sources?.reasoning)) && ( + + )} {showGraphVis ? ( diff --git a/graphrag-ui/src/main.tsx b/graphrag-ui/src/main.tsx index 70a14d3..2f6c599 100755 --- a/graphrag-ui/src/main.tsx +++ b/graphrag-ui/src/main.tsx @@ -4,6 +4,7 @@ import "./index.css"; import { Outlet, RouterProvider, createBrowserRouter, Navigate } from "react-router-dom"; import Chat from "./pages/Chat"; import ChatDialog from "./pages/ChatDialog.tsx"; +import TraceLogs from "./pages/TraceLogs.tsx"; import SetupLayout from "./pages/setup/SetupLayout.tsx"; import KGAdmin from "./pages/setup/KGAdmin.tsx"; import IngestGraph from "./pages/setup/IngestGraph.tsx"; @@ -56,6 +57,10 @@ const router = createBrowserRouter([ path: "/preferences", element: , }, + { + path: "/trace", + element: , + }, { path: "/setup", element: , diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx new file mode 100644 index 0000000..033a726 --- /dev/null +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -0,0 +1,697 @@ +import { FC, useState, useMemo } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"; +import { + LuArrowLeft, + LuChevronDown, + LuChevronUp, + LuCopy, + LuDownload, + LuClock, + LuWrench, + LuBookOpen, + LuActivity, +} from "react-icons/lu"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +interface TraceLogEntry { + id: number; + type: "tool_call" | "tool_result" | "citation"; + timestamp: string; + label: string; + detail?: string; + durationMs?: number; + content?: string; + step?: number; +} + +interface ToolCallEntry { + id: number; + name: string; + timestamp: string; + durationMs: number; + input?: string; + output?: string; +} + +interface CitationEntry { + id: number; + source: string; + cited: boolean; + text: string; +} + +interface TimelineStep { + step: number; + name: string; + durationMs: number; +} + +interface TraceData { + originalQuery: string; + conversationContext: string[]; + status: "completed" | "in_progress" | "failed"; + sessionId: string; + timing: { + totalDuration: number; + toolExecution: number; + llmThinking: number; + startTime: string; + endTime: string; + }; + logs: TraceLogEntry[]; + toolCalls: ToolCallEntry[]; + citations: CitationEntry[]; + timeline: TimelineStep[]; + finalResponse: string; +} + +// ─── Helpers ────────────────────────────────────────────────────────────────── + +function formatDuration(seconds: number): string { + if (seconds < 1) return `${(seconds * 1000).toFixed(0)}ms`; + return `${seconds.toFixed(2)}s`; +} + +function safeJson(obj: any, maxLen = 2000): string { + if (obj == null) return "N/A"; + if (typeof obj === "string") { + try { + const parsed = JSON.parse(obj); + const pretty = JSON.stringify(parsed, null, 2); + return pretty.length > maxLen ? pretty.slice(0, maxLen) + "\n…truncated" : pretty; + } catch { + return obj.length > maxLen ? obj.slice(0, maxLen) + "\n…truncated" : obj; + } + } + try { + const s = JSON.stringify(obj, null, 2); + return s.length > maxLen ? s.slice(0, maxLen) + "\n…truncated" : s; + } catch { + return String(obj); + } +} + +function buildTraceFromMessage(message: any, userQuery?: string): TraceData { + const now = new Date(); + const sessionTs = now.toISOString().replace(/[-:T]/g, "").slice(0, 15); + const sessionId = `chat_${sessionTs}`; + + const query = userQuery || message?.originalQuery || message?.query || "N/A"; + const qs = message?.query_sources || {}; + const responseType = message?.response_type || ""; + const totalResponseTime = message?.response_time || 0; + const ts = now.toLocaleTimeString(); + + // ── Tool Calls ────────────────────────────────────────────────────────── + // query_sources.agent_steps = array of { node, duration_s } from the + // actual LangGraph agent execution (collected in agent.py) + const toolCalls: ToolCallEntry[] = []; + const agentSteps: { node: string; duration_s: number }[] = + qs.agent_steps || []; + + if (agentSteps.length > 0) { + agentSteps.forEach((step: { node: string; duration_s: number }, i: number) => { + toolCalls.push({ + id: i + 1, + name: step.node, + timestamp: ts, + durationMs: Math.round(step.duration_s * 1000), + input: "", + output: "", + }); + }); + } + + // ── Citations ─────────────────────────────────────────────────────────── + const rawReasoning = qs.reasoning; + const finalRetrieval = + typeof qs.result === "object" && qs.result?.final_retrieval + ? qs.result.final_retrieval + : null; + const citations: CitationEntry[] = []; + + if (rawReasoning && Array.isArray(rawReasoning)) { + rawReasoning.forEach((src: any, i: number) => { + if (src == null) return; + const raw = typeof src === "string" ? src : String(src); + const cited = raw.startsWith("* "); + const chunkName = raw.replace(/^\*\s*/, ""); + + let chunkText = ""; + if (finalRetrieval && finalRetrieval[chunkName]) { + const val = finalRetrieval[chunkName]; + chunkText = Array.isArray(val) ? val.join("\n\n") : String(val); + } + + citations.push({ + id: i + 1, + source: chunkName, + cited, + text: chunkText, + }); + }); + } + + // ── Logs ──────────────────────────────────────────────────────────────── + const logs: TraceLogEntry[] = []; + let logId = 0; + toolCalls.forEach((tc) => { + logs.push({ + id: logId++, + type: "tool_call", + timestamp: tc.timestamp, + label: `${tc.name} - Input`, + content: tc.input, + }); + logs.push({ + id: logId++, + type: "tool_result", + timestamp: tc.timestamp, + label: `${tc.name} - Result`, + content: tc.output, + }); + }); + + // ── Timeline ──────────────────────────────────────────────────────────── + const timeline: TimelineStep[] = toolCalls.map((tc, i) => ({ + step: i + 1, + name: tc.name, + durationMs: tc.durationMs, + })); + + const totalToolSec = agentSteps.reduce( + (sum: number, s: { duration_s: number }) => sum + s.duration_s, + 0 + ); + const llmThinking = Math.max(0, totalResponseTime - totalToolSec); + const endTime = new Date(now.getTime() + totalResponseTime * 1000); + + return { + originalQuery: query, + conversationContext: [`user: ${query}`], + status: "completed", + sessionId, + timing: { + totalDuration: totalResponseTime, + toolExecution: totalToolSec, + llmThinking, + startTime: now.toLocaleTimeString(), + endTime: endTime.toLocaleTimeString(), + }, + logs, + toolCalls, + citations, + timeline, + finalResponse: message?.content || "", + }; +} + +// ─── Sub-components ─────────────────────────────────────────────────────────── + +const StatusBadge: FC<{ status: string }> = ({ status }) => { + const color = + status === "completed" + ? "bg-emerald-500" + : status === "in_progress" + ? "bg-blue-500" + : "bg-red-500"; + return ( + + {status} + + ); +}; + +const TimingRow: FC<{ + items: { value: string; label: string; color: string }[]; +}> = ({ items }) => ( +
+ {items.map((item, i) => ( +
+ + {item.value} + + + {item.label} + +
+ ))} +
+); + +const ExpandableRow: FC<{ + children: React.ReactNode; + content?: string; + defaultOpen?: boolean; +}> = ({ children, content, defaultOpen = false }) => { + const [open, setOpen] = useState(defaultOpen); + return ( +
+
setOpen((p) => !p)} + > +
+ {children} +
+
+ {content && ( + + )} + {open ? ( + + ) : ( + + )} +
+
+ {open && content && ( +
+
{content}
+
+ )} +
+ ); +}; + +// ─── Tab Panels ─────────────────────────────────────────────────────────────── + +const LogsPanel: FC<{ trace: TraceData }> = ({ trace }) => { + const [collapsed, setCollapsed] = useState(false); + const toolCount = trace.logs.filter( + (l) => l.type === "tool_call" || l.type === "tool_result" + ).length; + const citationCount = trace.citations.length; + const totalEvents = trace.logs.length; + + return ( +
+
+
+ + {trace.logs.length} log entries ({totalEvents} events) + + + Tools ({toolCount}) + + + Citations ({citationCount}) + +
+ +
+ +
+ {trace.logs.map((log) => { + const isToolCall = log.type === "tool_call"; + const isToolResult = log.type === "tool_result"; + + const dotColor = isToolResult + ? "bg-emerald-500" + : "bg-blue-500"; + const iconBg = isToolResult + ? "text-emerald-600 dark:text-emerald-400" + : "text-blue-600 dark:text-blue-400"; + const typeLabel = isToolCall + ? "Tool Call" + : "Tool Result"; + + return ( +
+
+
+
+
+
+ + + {isToolCall && } + {isToolResult && "✓ "} + {typeLabel} + + + {log.timestamp} + + + {log.label} + + {log.durationMs && ( + + ({log.durationMs}ms) + + )} + +
+
+ ); + })} +
+
+ ); +}; + +const ToolCallExpandable: FC<{ tc: ToolCallEntry }> = ({ tc }) => { + const [open, setOpen] = useState(false); + return ( +
+
setOpen((p) => !p)} + > +
+ + {tc.id} + + {tc.name} + {tc.timestamp} +
+
+ {tc.durationMs > 0 && ( + + {tc.durationMs}ms + + )} + {open ? ( + + ) : ( + + )} +
+
+ {open && ( +
+
+

+ Input +

+
+              {tc.input || "N/A"}
+            
+
+
+

+ Result +

+
+              {tc.output || "N/A"}
+            
+
+
+ )} +
+ ); +}; + +const ToolCallsPanel: FC<{ trace: TraceData }> = ({ trace }) => ( +
+ {trace.toolCalls.map((tc) => ( + + ))} +
+); + + +const CitationRow: FC<{ c: CitationEntry }> = ({ c }) => { + const [open, setOpen] = useState(false); + return ( +
+
setOpen((p) => !p)} + > +
+ + + [{c.source}] + + {c.cited && ( + + Cited + + )} +
+
+ {open ? ( + + ) : ( + + )} +
+
+ {open && ( +
+ {c.text || "No content retrieved for this chunk."} +
+ )} +
+ ); +}; + +const CitationsPanel: FC<{ trace: TraceData }> = ({ trace }) => ( +
+ {trace.citations.length === 0 ? ( +

+ No citations available for this trace. +

+ ) : ( + trace.citations.map((c) => ) + )} +
+); + +const TimelinePanel: FC<{ trace: TraceData }> = ({ trace }) => ( +
+ {trace.timeline.map((item, i) => ( +
+
+ Step {item.step} +
+
+ + {item.durationMs}ms + + {item.name} +
+
+ ))} +
+); + +// ─── Main Page ──────────────────────────────────────────────────────────────── + +const TraceLogs: FC = () => { + const location = useLocation(); + const navigate = useNavigate(); + const message = location.state?.message; + const userQuery = location.state?.userQuery; + + const trace = useMemo( + () => buildTraceFromMessage(message, userQuery), + [message, userQuery] + ); + + const handleBack = () => { + navigate(-1); + }; + + const handleDownload = () => { + const blob = new Blob([JSON.stringify(trace, null, 2)], { + type: "application/json", + }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `trace_${trace.sessionId}.json`; + a.click(); + URL.revokeObjectURL(url); + }; + + return ( +
+ {/* Header */} +
+
+
+ +

Trace Logs

+
+
+ + + Session: {trace.sessionId} + + +
+
+
+ +
+ {/* Original Query */} +
+

Original Query

+
+ {trace.originalQuery} +
+
+ + {/* Conversation Context */} +
+

Conversation Context

+
+ {trace.conversationContext.map((line, i) => ( +

+ {line} +

+ ))} +
+
+ + {/* Timing Overview */} +
+

Timing Overview

+ + {/* Timeline bar */} +
+
+
+
+
+
+ Start + {trace.timing.startTime} + {trace.timing.endTime} +
+
+
+ + {/* Tabs */} + + + + + Logs + + + Tool Calls + + {trace.toolCalls.length} + + + + Citations + + {trace.citations.length} + + + + Timeline + + + + + + + + + + + + + + + + + + {/* Final Response */} + {trace.finalResponse && ( +
+

Final Response

+
+ + {trace.finalResponse} + +
+
+ )} +
+
+ ); +}; + +export default TraceLogs; diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index 49b8552..735565c 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -129,14 +129,30 @@ def question_for_agent( logger.error(f"Failed to serialize input_data to JSON: {e}") raise ValueError("Invalid input data format. Unable to convert to JSON.") + agent_steps = [] + step_start = time.time() + for output in self.agent.stream({"question": input_data["input"], "conversation": input_data["conversation"]}): for key, value in output.items(): - # logger.info(f"testing steps {key}: {value}") - LogWriter.info(f"request_id={req_id_cv.get()} executed node {key}") + step_end = time.time() + step_duration = round(step_end - step_start, 3) + agent_steps.append({ + "node": key, + "duration_s": step_duration, + }) + LogWriter.info( + f"request_id={req_id_cv.get()} executed node {key} ({step_duration}s)" + ) + step_start = step_end + + answer = value["answer"] + if answer.query_sources is None: + answer.query_sources = {} + answer.query_sources["agent_steps"] = agent_steps LogWriter.info(f"request_id={req_id_cv.get()} EXIT question_for_agent") - return value["answer"] + return answer except Exception as e: metrics.llm_query_error_total.labels(self.model_name).inc() LogWriter.error(f"request_id={req_id_cv.get()} FAILURE question_for_agent") From d5b1ed2b8c1c5a32b7d4ca552a386e62f7b2f210 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Tue, 14 Apr 2026 17:02:19 +0530 Subject: [PATCH 04/70] fix: avoid intermediate variable in agent.py return --- graphrag/app/agent/agent.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index 735565c..ddb6a2e 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -146,13 +146,12 @@ def question_for_agent( ) step_start = step_end - answer = value["answer"] - if answer.query_sources is None: - answer.query_sources = {} - answer.query_sources["agent_steps"] = agent_steps + if value["answer"].query_sources is None: + value["answer"].query_sources = {} + value["answer"].query_sources["agent_steps"] = agent_steps LogWriter.info(f"request_id={req_id_cv.get()} EXIT question_for_agent") - return answer + return value["answer"] except Exception as e: metrics.llm_query_error_total.labels(self.model_name).inc() LogWriter.error(f"request_id={req_id_cv.get()} FAILURE question_for_agent") From 025f0b9aa9ea2d47df002f9cf205add3c484a484 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Tue, 14 Apr 2026 18:23:08 +0530 Subject: [PATCH 05/70] feat: capture node input/output in agent_steps and display in Trace Logs --- graphrag-ui/src/pages/TraceLogs.tsx | 11 ++++------- graphrag/app/agent/agent.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx index 033a726..bc4a597 100644 --- a/graphrag-ui/src/pages/TraceLogs.tsx +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -102,26 +102,23 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { const query = userQuery || message?.originalQuery || message?.query || "N/A"; const qs = message?.query_sources || {}; - const responseType = message?.response_type || ""; const totalResponseTime = message?.response_time || 0; const ts = now.toLocaleTimeString(); // ── Tool Calls ────────────────────────────────────────────────────────── - // query_sources.agent_steps = array of { node, duration_s } from the - // actual LangGraph agent execution (collected in agent.py) const toolCalls: ToolCallEntry[] = []; - const agentSteps: { node: string; duration_s: number }[] = + const agentSteps: { node: string; duration_s: number; input?: string; output?: string }[] = qs.agent_steps || []; if (agentSteps.length > 0) { - agentSteps.forEach((step: { node: string; duration_s: number }, i: number) => { + agentSteps.forEach((step, i: number) => { toolCalls.push({ id: i + 1, name: step.node, timestamp: ts, durationMs: Math.round(step.duration_s * 1000), - input: "", - output: "", + input: safeJson(step.input), + output: safeJson(step.output), }); }); } diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index ddb6a2e..313861a 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -131,16 +131,28 @@ def question_for_agent( agent_steps = [] step_start = time.time() + prev_output = input_data["input"] for output in self.agent.stream({"question": input_data["input"], "conversation": input_data["conversation"]}): for key, value in output.items(): step_end = time.time() step_duration = round(step_end - step_start, 3) + + def _safe_serialize(obj, max_len=3000): + try: + s = json.dumps(obj, default=str) + except Exception: + s = str(obj) + return s[:max_len] if len(s) > max_len else s + agent_steps.append({ "node": key, "duration_s": step_duration, + "input": _safe_serialize(prev_output), + "output": _safe_serialize(value), }) + prev_output = value LogWriter.info( f"request_id={req_id_cv.get()} executed node {key} ({step_duration}s)" ) From c741963020e62f04202469f4bbd1357fa4e61873 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Tue, 14 Apr 2026 19:08:50 +0530 Subject: [PATCH 06/70] fix: move _safe_serialize above loop, remove unused LuClock import --- graphrag-ui/src/pages/TraceLogs.tsx | 1 - graphrag/app/agent/agent.py | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx index bc4a597..a3ffe34 100644 --- a/graphrag-ui/src/pages/TraceLogs.tsx +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -7,7 +7,6 @@ import { LuChevronUp, LuCopy, LuDownload, - LuClock, LuWrench, LuBookOpen, LuActivity, diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index 313861a..c8a67a1 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -129,6 +129,13 @@ def question_for_agent( logger.error(f"Failed to serialize input_data to JSON: {e}") raise ValueError("Invalid input data format. Unable to convert to JSON.") + def _safe_serialize(obj, max_len=3000): + try: + s = json.dumps(obj, default=str) + except Exception: + s = str(obj) + return s[:max_len] if len(s) > max_len else s + agent_steps = [] step_start = time.time() prev_output = input_data["input"] @@ -139,13 +146,6 @@ def question_for_agent( step_end = time.time() step_duration = round(step_end - step_start, 3) - def _safe_serialize(obj, max_len=3000): - try: - s = json.dumps(obj, default=str) - except Exception: - s = str(obj) - return s[:max_len] if len(s) > max_len else s - agent_steps.append({ "node": key, "duration_s": step_duration, From 0d1cef7787cf17239129dd62030856b1067f875e Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Fri, 17 Apr 2026 11:39:36 +0530 Subject: [PATCH 07/70] feat: role-gated View Trace, remove truncation, fix routing labels --- .../src/components/CustomChatMessage.tsx | 17 +- graphrag-ui/src/components/Interact.tsx | 31 +++- graphrag-ui/src/pages/TraceLogs.tsx | 169 ++++++++++-------- graphrag/app/agent/agent.py | 64 ++++++- 4 files changed, 180 insertions(+), 101 deletions(-) diff --git a/graphrag-ui/src/components/CustomChatMessage.tsx b/graphrag-ui/src/components/CustomChatMessage.tsx index 285ac8f..1830018 100755 --- a/graphrag-ui/src/components/CustomChatMessage.tsx +++ b/graphrag-ui/src/components/CustomChatMessage.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/dialog" import { ImEnlarge2 } from "react-icons/im"; import { IoIosCloseCircleOutline } from "react-icons/io"; -import { LuActivity } from "react-icons/lu"; import { useNavigate } from "react-router-dom"; import { Interactions } from "./Interact"; import { KnowledgeGraphPro } from "./graphs/KnowledgeGraphPro"; @@ -193,18 +192,12 @@ export const CustomChatMessage: FC = ({ showExplain={handleShowExplain} showTable={handleShowTable} showGraph={handleShowGraph} + onViewTrace={() => { + navigate(`/trace/${message.messageId || message.message_id || ""}`, { + state: { message, userQuery: message.userQuery || "" }, + }); + }} /> - {(message.response_type !== "progress" && (message.query_sources?.result || message.query_sources?.reasoning)) && ( - - )}
{showGraphVis ? ( diff --git a/graphrag-ui/src/components/Interact.tsx b/graphrag-ui/src/components/Interact.tsx index e1426f0..9f259f4 100644 --- a/graphrag-ui/src/components/Interact.tsx +++ b/graphrag-ui/src/components/Interact.tsx @@ -10,7 +10,8 @@ import { PiArrowsCounterClockwiseFill } from "react-icons/pi"; import { Feedback, Message } from "@/actions/ActionProvider"; import { PiGraph } from "react-icons/pi"; import { FaTable } from "react-icons/fa"; -import { LuInfo } from "react-icons/lu"; +import { LuInfo, LuActivity } from "react-icons/lu"; +import { useRoles } from "@/hooks/useRoles"; const GRAPHRAG_URL = ""; interface Interactions { @@ -18,6 +19,7 @@ interface Interactions { showExplain: () => boolean; showTable: () => boolean; showGraph: () => boolean; + onViewTrace?: () => void; } export const Interactions: FC = ({ @@ -25,8 +27,11 @@ export const Interactions: FC = ({ showExplain, showTable, showGraph, + onViewTrace, }: Interactions) => { const [feedback, setFeedback] = useState(Feedback.NoFeedback); + const { isSuperuser, isGlobalDesigner, isGraphAdmin } = useRoles(); + const canViewTrace = isSuperuser || isGlobalDesigner || isGraphAdmin; const sendFeedback = async (action: Feedback, message: Message) => { const creds = sessionStorage.getItem("creds"); @@ -90,13 +95,23 @@ export const Interactions: FC = ({
*/} -
showExplain()} - > - - Explain -
+ {canViewTrace ? ( +
onViewTrace?.()} + > + + View Trace +
+ ) : ( +
showExplain()} + > + + Explain +
+ )}
maxLen ? pretty.slice(0, maxLen) + "\n…truncated" : pretty; + return JSON.stringify(JSON.parse(obj), null, 2); } catch { - return obj.length > maxLen ? obj.slice(0, maxLen) + "\n…truncated" : obj; + return obj; } } try { - const s = JSON.stringify(obj, null, 2); - return s.length > maxLen ? s.slice(0, maxLen) + "\n…truncated" : s; + return JSON.stringify(obj, null, 2); } catch { return String(obj); } } +const NODE_LABELS: Record = { + entry: "Entry", + supportai: "SupportAI", + map_question_to_schema: "Map Question to Schema", + generate_function: "Generate Function", + generate_cypher: "Generate Cypher", + generate_answer: "Generate Answer", + lookup_history: "Lookup History", + merge_history_context: "Merge History Context", + rewrite_question: "Rewrite Question", + apologize: "Apologize", + greet: "Greet", +}; + function buildTraceFromMessage(message: any, userQuery?: string): TraceData { const now = new Date(); const sessionTs = now.toISOString().replace(/[-:T]/g, "").slice(0, 15); @@ -113,7 +124,7 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { agentSteps.forEach((step, i: number) => { toolCalls.push({ id: i + 1, - name: step.node, + name: NODE_LABELS[step.node] || step.node, timestamp: ts, durationMs: Math.round(step.duration_s * 1000), input: safeJson(step.input), @@ -160,14 +171,15 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { id: logId++, type: "tool_call", timestamp: tc.timestamp, - label: `${tc.name} - Input`, + label: `${tc.name} — Input`, content: tc.input, + durationMs: tc.durationMs, }); logs.push({ id: logId++, - type: "tool_result", + type: "citation", timestamp: tc.timestamp, - label: `${tc.name} - Result`, + label: `${tc.name} — Output`, content: tc.output, }); }); @@ -283,7 +295,7 @@ const ExpandableRow: FC<{
{open && content && (
-
{content}
+
{content}
)}
@@ -294,24 +306,19 @@ const ExpandableRow: FC<{ const LogsPanel: FC<{ trace: TraceData }> = ({ trace }) => { const [collapsed, setCollapsed] = useState(false); - const toolCount = trace.logs.filter( - (l) => l.type === "tool_call" || l.type === "tool_result" - ).length; - const citationCount = trace.citations.length; - const totalEvents = trace.logs.length; return (
- {trace.logs.length} log entries ({totalEvents} events) + {trace.logs.length} agent steps - Tools ({toolCount}) + Nodes ({trace.toolCalls.length}) - Citations ({citationCount}) + Citations ({trace.citations.length})
- {trace.logs.map((log) => { - const isToolCall = log.type === "tool_call"; - const isToolResult = log.type === "tool_result"; - - const dotColor = isToolResult - ? "bg-emerald-500" - : "bg-blue-500"; - const iconBg = isToolResult - ? "text-emerald-600 dark:text-emerald-400" - : "text-blue-600 dark:text-blue-400"; - const typeLabel = isToolCall - ? "Tool Call" - : "Tool Result"; - - return ( -
-
-
-
-
-
- - - {isToolCall && } - {isToolResult && "✓ "} - {typeLabel} - - - {log.timestamp} - - - {log.label} + {trace.logs.map((log) => ( +
+
+
+
+
+
+ + + + Node + + + {log.timestamp} + + + {log.label} + + {log.durationMs != null && log.durationMs > 0 && ( + + ({log.durationMs}ms) - {log.durationMs && ( - - ({log.durationMs}ms) - - )} - -
+ )} +
- ); - })} +
+ ))}
); @@ -408,15 +399,15 @@ const ToolCallExpandable: FC<{ tc: ToolCallEntry }> = ({ tc }) => {

Input

-
+            
               {tc.input || "N/A"}
             

- Result + Output

-
+            
               {tc.output || "N/A"}
             
@@ -512,8 +503,34 @@ const TimelinePanel: FC<{ trace: TraceData }> = ({ trace }) => ( const TraceLogs: FC = () => { const location = useLocation(); const navigate = useNavigate(); - const message = location.state?.message; - const userQuery = location.state?.userQuery; + const { messageId } = useParams<{ messageId: string }>(); + + const stateMessage = location.state?.message; + const stateUserQuery = location.state?.userQuery; + + const [apiData, setApiData] = useState(null); + const [loading, setLoading] = useState(!stateMessage); + + useEffect(() => { + if (stateMessage || !messageId) return; + setLoading(true); + fetch(`/ui/trace/${messageId}`) + .then((res) => { + if (!res.ok) throw new Error("Not found"); + return res.json(); + }) + .then((data) => setApiData(data)) + .catch(() => setApiData(null)) + .finally(() => setLoading(false)); + }, [messageId, stateMessage]); + + const message = stateMessage || (apiData ? { + content: apiData.natural_language_response, + response_time: apiData.response_time, + response_type: apiData.response_type, + query_sources: apiData.query_sources, + } : null); + const userQuery = stateUserQuery || apiData?.user_query; const trace = useMemo( () => buildTraceFromMessage(message, userQuery), @@ -536,6 +553,14 @@ const TraceLogs: FC = () => { URL.revokeObjectURL(url); }; + if (loading) { + return ( +
+

Loading trace data...

+
+ ); + } + return (
{/* Header */} diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index c8a67a1..e64b389 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -129,35 +129,81 @@ def question_for_agent( logger.error(f"Failed to serialize input_data to JSON: {e}") raise ValueError("Invalid input data format. Unable to convert to JSON.") - def _safe_serialize(obj, max_len=3000): + def _safe(obj): try: - s = json.dumps(obj, default=str) + return json.dumps(obj, default=str) except Exception: - s = str(obj) - return s[:max_len] if len(s) > max_len else s + return str(obj) + + def _node_output(node, state): + """Extract the meaningful output that this node produced.""" + _LOOKUP_LABELS = {"inquiryai": "db_search", "supportai": "vector_search"} + lookup = state.get("lookup_source", "") + lookup = _LOOKUP_LABELS.get(lookup, lookup) + + if node == "entry": + return "" + elif node == "map_question_to_schema": + return _safe({"schema_mapping": str(state.get("schema_mapping", ""))}) + elif node == "generate_function": + ctx = state.get("context", {}) + return _safe({ + "context": ctx if isinstance(ctx, dict) else str(ctx), + "lookup_source": lookup, + }) + elif node == "generate_cypher": + ctx = state.get("context", {}) + return _safe({ + "cypher": ctx.get("cypher", "") if isinstance(ctx, dict) else "", + "reasoning": ctx.get("reasoning", "") if isinstance(ctx, dict) else "", + "result": ctx.get("result", "") if isinstance(ctx, dict) else "", + "lookup_source": lookup, + }) + elif node == "supportai": + ctx = state.get("context", {}) + return _safe({ + "context": ctx if isinstance(ctx, dict) else str(ctx), + "lookup_source": lookup, + }) + elif node == "generate_answer": + ans = state.get("answer") + return _safe({ + "natural_language_response": getattr(ans, "natural_language_response", "") if ans else "", + "answered_question": getattr(ans, "answered_question", False) if ans else False, + "response_type": getattr(ans, "response_type", "") if ans else "", + }) + elif node in ("greet", "apologize"): + ans = state.get("answer") + return getattr(ans, "natural_language_response", "") if ans else "" + return _safe(state) agent_steps = [] step_start = time.time() - prev_output = input_data["input"] + prev_state = {"question": input_data["input"], "conversation": input_data["conversation"]} for output in self.agent.stream({"question": input_data["input"], "conversation": input_data["conversation"]}): for key, value in output.items(): step_end = time.time() step_duration = round(step_end - step_start, 3) - agent_steps.append({ "node": key, "duration_s": step_duration, - "input": _safe_serialize(prev_output), - "output": _safe_serialize(value), + "input": _safe(prev_state), + "output": _node_output(key, value), }) - prev_output = value + prev_state = value LogWriter.info( f"request_id={req_id_cv.get()} executed node {key} ({step_duration}s)" ) step_start = step_end + # Backfill entry with routing decision + if len(agent_steps) >= 2 and agent_steps[0]["node"] == "entry": + next_node = agent_steps[1]["node"] + _ROUTE_LABELS = {"supportai": "vector_search", "map_question_to_schema": "db_search", "lookup_history": "history_lookup"} + agent_steps[0]["output"] = _safe({"routing_decision": _ROUTE_LABELS.get(next_node, next_node)}) + if value["answer"].query_sources is None: value["answer"].query_sources = {} value["answer"].query_sources["agent_steps"] = agent_steps From 043591ded48ee23ae1ca27a268d171936f8d24fe Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Fri, 17 Apr 2026 14:51:52 +0530 Subject: [PATCH 08/70] feat: add trace_logs volume mount in docker-compose --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 97a0952..449939b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -18,6 +18,7 @@ services: USE_CYPHER: "true" volumes: - ./configs/:/code/configs + - ./trace_logs/:/code/trace_logs graphrag-ecc: image: tigergraph/graphrag-ecc:latest From de18fbb1bc4420a8bf1821d9970cc7b9a67bfa88 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Fri, 17 Apr 2026 23:44:48 +0530 Subject: [PATCH 09/70] feat: add trace log save/fetch endpoints in ui.py and nginx /trace route --- docs/tutorials/configs/nginx.conf | 8 +++++ graphrag/app/routers/ui.py | 58 +++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/docs/tutorials/configs/nginx.conf b/docs/tutorials/configs/nginx.conf index 975d8a0..121922c 100644 --- a/docs/tutorials/configs/nginx.conf +++ b/docs/tutorials/configs/nginx.conf @@ -29,6 +29,14 @@ server { proxy_pass http://graphrag-ui:3000/; } + location /trace { + proxy_pass http://graphrag-ui:3000; + } + + location /trace/ { + proxy_pass http://graphrag-ui:3000; + } + location ~^/ui/.*/chat$ { proxy_pass http://graphrag:8000; proxy_http_version 1.1; diff --git a/graphrag/app/routers/ui.py b/graphrag/app/routers/ui.py index 400435d..432eb3a 100644 --- a/graphrag/app/routers/ui.py +++ b/graphrag/app/routers/ui.py @@ -70,6 +70,28 @@ logger = logging.getLogger(__name__) +TRACE_LOGS_DIR = os.environ.get("TRACE_LOGS_DIR", "/code/trace_logs") + +def _save_trace_log(message_id: str, conversation_id: str, user_query: str, resp: GraphRAGResponse, elapsed: float): + try: + os.makedirs(TRACE_LOGS_DIR, exist_ok=True) + trace_data = { + "message_id": message_id, + "conversation_id": conversation_id, + "user_query": user_query, + "response_time": elapsed, + "response_type": resp.response_type, + "answered_question": resp.answered_question, + "query_sources": resp.query_sources, + "natural_language_response": resp.natural_language_response, + "timestamp": time.time(), + } + filepath = os.path.join(TRACE_LOGS_DIR, f"{message_id}.json") + with open(filepath, "w") as f: + json.dump(trace_data, f, default=str) + except Exception: + logger.warning(f"Failed to save trace log for message {message_id}", exc_info=True) + # Validated graph name path parameter — rejects path traversal characters ValidGraphName = Annotated[str, Path(pattern=r"^[A-Za-z_][A-Za-z0-9_]*$")] @@ -338,6 +360,40 @@ def add_feedback( return {"message": "feedback saved", "message_id": message.message_id} +@router.get(route_prefix + "/trace/{message_id}") +def get_trace_log(message_id: str): + filepath = os.path.join(TRACE_LOGS_DIR, f"{message_id}.json") + if not os.path.exists(filepath): + raise HTTPException(status_code=404, detail="Trace log not found") + with open(filepath, "r") as f: + return json.load(f) + + +@router.get(route_prefix + "/traces/{conversation_id}") +def list_trace_logs(conversation_id: str): + if not os.path.isdir(TRACE_LOGS_DIR): + return [] + traces = [] + for filename in os.listdir(TRACE_LOGS_DIR): + if not filename.endswith(".json"): + continue + filepath = os.path.join(TRACE_LOGS_DIR, filename) + try: + with open(filepath, "r") as f: + data = json.load(f) + if data.get("conversation_id") == conversation_id: + traces.append({ + "message_id": data.get("message_id"), + "user_query": data.get("user_query"), + "response_time": data.get("response_time"), + "timestamp": data.get("timestamp"), + }) + except Exception: + continue + traces.sort(key=lambda t: t.get("timestamp", 0)) + return traces + + @router.post(route_prefix + "/{graphname}/create_graph") def create_graph( graphname: ValidGraphName, @@ -1074,6 +1130,7 @@ async def graph_query( query_sources=resp.query_sources, ) await write_message_to_history(message, auth) + _save_trace_log(message.message_id, convo_id, data, resp, elapsed) prev_id = message.message_id # reply @@ -1200,6 +1257,7 @@ async def chat( query_sources=resp.query_sources, ) await write_message_to_history(message, usr_auth) + _save_trace_log(message.message_id, convo_id, data, resp, elapsed) prev_id = message.message_id # reply From 7d48f0eb8fdbf2c4d6c389d411e13504063aff8d Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Sat, 18 Apr 2026 00:23:50 +0530 Subject: [PATCH 10/70] fix: update trace route to /trace/:messageId in main.tsx --- graphrag-ui/src/main.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphrag-ui/src/main.tsx b/graphrag-ui/src/main.tsx index 2f6c599..79def9b 100755 --- a/graphrag-ui/src/main.tsx +++ b/graphrag-ui/src/main.tsx @@ -58,7 +58,7 @@ const router = createBrowserRouter([ element: , }, { - path: "/trace", + path: "/trace/:messageId", element: , }, { From 71bb9240c62b285daac4e349a4cf0f0388267df9 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Tue, 21 Apr 2026 15:59:05 +0530 Subject: [PATCH 11/70] fix: show all durations in seconds in TraceLogs --- graphrag-ui/src/pages/TraceLogs.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx index 2dfc851..4b43936 100644 --- a/graphrag-ui/src/pages/TraceLogs.tsx +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -71,7 +71,6 @@ interface TraceData { // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDuration(seconds: number): string { - if (seconds < 1) return `${(seconds * 1000).toFixed(0)}ms`; return `${seconds.toFixed(2)}s`; } @@ -353,7 +352,7 @@ const LogsPanel: FC<{ trace: TraceData }> = ({ trace }) => { {log.durationMs != null && log.durationMs > 0 && ( - ({log.durationMs}ms) + ({formatDuration(log.durationMs / 1000)}) )} @@ -383,7 +382,7 @@ const ToolCallExpandable: FC<{ tc: ToolCallEntry }> = ({ tc }) => {
{tc.durationMs > 0 && ( - {tc.durationMs}ms + {formatDuration(tc.durationMs / 1000)} )} {open ? ( @@ -489,7 +488,7 @@ const TimelinePanel: FC<{ trace: TraceData }> = ({ trace }) => (
- {item.durationMs}ms + {formatDuration(item.durationMs / 1000)} {item.name}
From 96f5770e0915225bb3995f7a7989a7ca27daa8ad Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Mon, 27 Apr 2026 20:06:48 +0530 Subject: [PATCH 12/70] feat: add LLM token usage tracking per node and Token Overview tab --- common/llm_services/base_llm.py | 28 ++ .../src/components/CustomChatMessage.tsx | 8 +- graphrag-ui/src/main.tsx | 1 + graphrag-ui/src/pages/TraceLogs.tsx | 253 +++++++++++++++++- graphrag/app/agent/agent.py | 41 ++- 5 files changed, 321 insertions(+), 10 deletions(-) diff --git a/common/llm_services/base_llm.py b/common/llm_services/base_llm.py index ba1c770..005fab1 100644 --- a/common/llm_services/base_llm.py +++ b/common/llm_services/base_llm.py @@ -23,6 +23,32 @@ logger = logging.getLogger(__name__) +# Per-request collector for LLM usage so callers (e.g. agent trace logs) can +# aggregate token usage without breaking the existing return signatures. +# It's a context-local list the agent resets before each node executes. +import contextvars as _contextvars + +_usage_collector: _contextvars.ContextVar = _contextvars.ContextVar( + "llm_usage_collector", default=None +) + + +def start_usage_collection(): + """Begin collecting LLM usage for the current context (per node).""" + _usage_collector.set([]) + + +def get_collected_usage(): + """Return the usage entries collected since the last start (or None).""" + return _usage_collector.get() + + +def _record_usage(caller_name: str, usage_data: dict): + bucket = _usage_collector.get() + if bucket is not None: + bucket.append({"caller_name": caller_name, **usage_data}) + + class LLM_Model: """Base LLM_Model Class @@ -95,6 +121,7 @@ def invoke_with_parser( usage_data["total_tokens"] = cb.total_tokens usage_data["cost"] = cb.total_cost logger.info(f"{caller_name} usage: {usage_data}") + _record_usage(caller_name, usage_data) raw_text = raw_output.content if hasattr(raw_output, "content") else str(raw_output) @@ -131,6 +158,7 @@ async def ainvoke_with_parser( usage_data["total_tokens"] = cb.total_tokens usage_data["cost"] = cb.total_cost logger.info(f"{caller_name} usage: {usage_data}") + _record_usage(caller_name, usage_data) raw_text = raw_output.content if hasattr(raw_output, "content") else str(raw_output) diff --git a/graphrag-ui/src/components/CustomChatMessage.tsx b/graphrag-ui/src/components/CustomChatMessage.tsx index 1830018..14ccf87 100755 --- a/graphrag-ui/src/components/CustomChatMessage.tsx +++ b/graphrag-ui/src/components/CustomChatMessage.tsx @@ -10,7 +10,6 @@ import { } from "@/components/ui/dialog" import { ImEnlarge2 } from "react-icons/im"; import { IoIosCloseCircleOutline } from "react-icons/io"; -import { useNavigate } from "react-router-dom"; import { Interactions } from "./Interact"; import { KnowledgeGraphPro } from "./graphs/KnowledgeGraphPro"; import { KnowledgeTablPro } from "./tables/KnowledgeTablePro"; @@ -128,7 +127,6 @@ const AuthenticatedImage: FC<{ src: string; alt: string }> = ({ src, alt }) => { export const CustomChatMessage: FC = ({ message, }) => { - const navigate = useNavigate(); const [showResult, setShowResult] = useState(false); const [showGraphVis, setShowGraphVis] = useState(false); const [showTableVis, setShowTableVis] = useState(false); @@ -193,9 +191,9 @@ export const CustomChatMessage: FC = ({ showTable={handleShowTable} showGraph={handleShowGraph} onViewTrace={() => { - navigate(`/trace/${message.messageId || message.message_id || ""}`, { - state: { message, userQuery: message.userQuery || "" }, - }); + const messageId = message.messageId || message.message_id || ""; + // No noopener — browser copies sessionStorage to the new tab automatically. + window.open(`/trace/${messageId}`, "_blank"); }} />
diff --git a/graphrag-ui/src/main.tsx b/graphrag-ui/src/main.tsx index 79def9b..1788c05 100755 --- a/graphrag-ui/src/main.tsx +++ b/graphrag-ui/src/main.tsx @@ -26,6 +26,7 @@ const RequireAuth = ({ children }: { children: any }) => { return children; }; + const Layout = () => { useIdleTimeout(); return ( diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx index 4b43936..566cc4a 100644 --- a/graphrag-ui/src/pages/TraceLogs.tsx +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -10,6 +10,8 @@ import { LuWrench, LuBookOpen, LuActivity, + LuCoins, + LuInfo, } from "react-icons/lu"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -27,6 +29,17 @@ interface TraceLogEntry { step?: number; } +interface TokenUsage { + input_tokens: number; + output_tokens: number; + total_tokens: number; + cost: number; +} + +interface LlmCall extends TokenUsage { + caller_name: string; +} + interface ToolCallEntry { id: number; name: string; @@ -34,6 +47,7 @@ interface ToolCallEntry { durationMs: number; input?: string; output?: string; + usage?: TokenUsage & { calls?: LlmCall[] }; } interface CitationEntry { @@ -65,12 +79,14 @@ interface TraceData { toolCalls: ToolCallEntry[]; citations: CitationEntry[]; timeline: TimelineStep[]; + tokenUsage: TokenUsage; finalResponse: string; } // ─── Helpers ────────────────────────────────────────────────────────────────── function formatDuration(seconds: number): string { + if (seconds < 0.01) return `${Math.round(seconds * 1000)}ms`; return `${seconds.toFixed(2)}s`; } @@ -116,8 +132,13 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { // ── Tool Calls ────────────────────────────────────────────────────────── const toolCalls: ToolCallEntry[] = []; - const agentSteps: { node: string; duration_s: number; input?: string; output?: string }[] = - qs.agent_steps || []; + const agentSteps: { + node: string; + duration_s: number; + input?: string; + output?: string; + usage?: TokenUsage & { calls?: LlmCall[] }; + }[] = qs.agent_steps || []; if (agentSteps.length > 0) { agentSteps.forEach((step, i: number) => { @@ -128,6 +149,7 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { durationMs: Math.round(step.duration_s * 1000), input: safeJson(step.input), output: safeJson(step.output), + usage: step.usage, }); }); } @@ -197,6 +219,22 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { const llmThinking = Math.max(0, totalResponseTime - totalToolSec); const endTime = new Date(now.getTime() + totalResponseTime * 1000); + // ── Token usage totals ───────────────────────────────────────────────── + const serverTotal = qs.token_usage as TokenUsage | undefined; + const tokenUsage: TokenUsage = serverTotal || agentSteps.reduce( + (acc, s) => { + const u = s.usage; + if (!u) return acc; + return { + input_tokens: acc.input_tokens + (u.input_tokens || 0), + output_tokens: acc.output_tokens + (u.output_tokens || 0), + total_tokens: acc.total_tokens + (u.total_tokens || 0), + cost: acc.cost + (u.cost || 0), + }; + }, + { input_tokens: 0, output_tokens: 0, total_tokens: 0, cost: 0 } as TokenUsage + ); + return { originalQuery: query, conversationContext: [`user: ${query}`], @@ -213,10 +251,21 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { toolCalls, citations, timeline, + tokenUsage, finalResponse: message?.content || "", }; } +function formatCost(cost: number): string { + if (!cost) return "$0.00"; + if (cost < 0.01) return `$${cost.toFixed(6)}`; + return `$${cost.toFixed(4)}`; +} + +function formatNumber(n: number): string { + return (n || 0).toLocaleString(); +} + // ─── Sub-components ─────────────────────────────────────────────────────────── const StatusBadge: FC<{ status: string }> = ({ status }) => { @@ -380,6 +429,14 @@ const ToolCallExpandable: FC<{ tc: ToolCallEntry }> = ({ tc }) => { {tc.timestamp}
+ {tc.usage && tc.usage.total_tokens > 0 && ( + + {formatNumber(tc.usage.total_tokens)} tokens + + )} {tc.durationMs > 0 && ( {formatDuration(tc.durationMs / 1000)} @@ -394,6 +451,37 @@ const ToolCallExpandable: FC<{ tc: ToolCallEntry }> = ({ tc }) => {
{open && (
+ {tc.usage && tc.usage.total_tokens > 0 && ( +
+

+ LLM Usage +

+
+
+
Input
+
{formatNumber(tc.usage.input_tokens)}
+
+
+
Output
+
{formatNumber(tc.usage.output_tokens)}
+
+
+
Total
+
{formatNumber(tc.usage.total_tokens)}
+
+
+
Cost
+
{formatCost(tc.usage.cost)}
+
+
+ {tc.usage.calls && tc.usage.calls.length > 0 && ( +
+ {tc.usage.calls.length} LLM call{tc.usage.calls.length !== 1 ? "s" : ""}:{" "} + {tc.usage.calls.map((c) => c.caller_name).join(", ")} +
+ )} +
+ )}

Input @@ -497,6 +585,142 @@ const TimelinePanel: FC<{ trace: TraceData }> = ({ trace }) => (

); +const TokenOverviewPanel: FC<{ trace: TraceData }> = ({ trace }) => { + const usage = trace.tokenUsage; + const nodesWithUsage = trace.toolCalls.filter( + (tc) => tc.usage && tc.usage.total_tokens > 0 + ); + + return ( +
+ {/* Totals */} +
+
+
+ Input Tokens +
+
+ {formatNumber(usage.input_tokens)} +
+
+
+
+ Output Tokens +
+
+ {formatNumber(usage.output_tokens)} +
+
+
+
+ Total Tokens +
+
+ {formatNumber(usage.total_tokens)} +
+
+
+
+ Est. Cost + + + + Cost is estimated based on the model's published per-token pricing. Actual billing may differ. + + + +
+
+ {formatCost(usage.cost)} +
+
estimated
+
+
+ + {/* Per-node breakdown */} +
+
+

Usage by Node

+
+ {nodesWithUsage.length === 0 ? ( +

+ No LLM usage recorded for this trace. +

+ ) : ( +
+ + + + + + + + + + + + + {nodesWithUsage.map((tc) => ( + + + + + + + + + ))} + + + + + + + + + + +
NodeInputOutputTotal + + Est. Cost + + + + Cost is estimated based on the model's published per-token pricing. Actual billing may differ. + + + + LLM Calls
{tc.name} + {formatNumber(tc.usage!.input_tokens)} + + {formatNumber(tc.usage!.output_tokens)} + + {formatNumber(tc.usage!.total_tokens)} + + {formatCost(tc.usage!.cost)} + + {tc.usage!.calls && tc.usage!.calls.length > 0 + ? tc.usage!.calls.map((c) => c.caller_name).join(", ") + : "—"} +
Total + {formatNumber(usage.input_tokens)} + + {formatNumber(usage.output_tokens)} + + {formatNumber(usage.total_tokens)} + + + {formatCost(usage.cost)} + + +
+
+ )} +
+
+ ); +}; + // ─── Main Page ──────────────────────────────────────────────────────────────── const TraceLogs: FC = () => { @@ -537,7 +761,13 @@ const TraceLogs: FC = () => { ); const handleBack = () => { - navigate(-1); + // Trace opens in a new tab — closing it returns the user to the chat tab. + // If the tab cannot be closed (e.g. opened via direct link), fall back to navigate. + if (window.opener || window.history.length <= 1) { + window.close(); + } else { + navigate(-1); + } }; const handleDownload = () => { @@ -571,7 +801,7 @@ const TraceLogs: FC = () => { className="flex items-center gap-1 text-sm text-blue-600 dark:text-blue-400 hover:underline mb-1" > - Back to Chat + Close & Back to Chat

Trace Logs

@@ -682,6 +912,18 @@ const TraceLogs: FC = () => { > Timeline + + + Token Overview + {trace.tokenUsage.total_tokens > 0 && ( + + {formatNumber(trace.tokenUsage.total_tokens)} + + )} + @@ -696,6 +938,9 @@ const TraceLogs: FC = () => { + + + {/* Final Response */} diff --git a/graphrag/app/agent/agent.py b/graphrag/app/agent/agent.py index e64b389..611f03c 100644 --- a/graphrag/app/agent/agent.py +++ b/graphrag/app/agent/agent.py @@ -11,7 +11,7 @@ from common.config import embedding_service, embedding_store, llm_config, get_completion_config, get_chat_config, get_llm_service from common.embeddings.base_embedding_store import EmbeddingStore from common.embeddings.embedding_services import EmbeddingModel -from common.llm_services.base_llm import LLM_Model +from common.llm_services.base_llm import LLM_Model, start_usage_collection, get_collected_usage from common.logs.log import req_id_cv from common.logs.logwriter import LogWriter from common.metrics.prometheus_metrics import metrics @@ -181,17 +181,47 @@ def _node_output(node, state): step_start = time.time() prev_state = {"question": input_data["input"], "conversation": input_data["conversation"]} + # Start collecting LLM usage so we can attribute tokens/cost per node. + start_usage_collection() + for output in self.agent.stream({"question": input_data["input"], "conversation": input_data["conversation"]}): for key, value in output.items(): step_end = time.time() step_duration = round(step_end - step_start, 3) + + # Grab usage accumulated during this node and reset for next node. + node_usage = get_collected_usage() or [] + input_tokens = sum(int(u.get("input_tokens", 0) or 0) for u in node_usage) + output_tokens = sum(int(u.get("output_tokens", 0) or 0) for u in node_usage) + total_tokens = sum(int(u.get("total_tokens", 0) or 0) for u in node_usage) + cost = sum(float(u.get("cost", 0) or 0) for u in node_usage) + agent_steps.append({ "node": key, "duration_s": step_duration, "input": _safe(prev_state), "output": _node_output(key, value), + "usage": { + "input_tokens": input_tokens, + "output_tokens": output_tokens, + "total_tokens": total_tokens, + "cost": cost, + "calls": [ + { + "caller_name": u.get("caller_name"), + "input_tokens": u.get("input_tokens", 0), + "output_tokens": u.get("output_tokens", 0), + "total_tokens": u.get("total_tokens", 0), + "cost": u.get("cost", 0), + } + for u in node_usage + ], + }, }) + # Reset the collector for the next node. + start_usage_collection() + prev_state = value LogWriter.info( f"request_id={req_id_cv.get()} executed node {key} ({step_duration}s)" @@ -208,6 +238,15 @@ def _node_output(node, state): value["answer"].query_sources = {} value["answer"].query_sources["agent_steps"] = agent_steps + # Aggregate total LLM usage across all nodes for the Token Overview UI. + total_usage = { + "input_tokens": sum(int(s.get("usage", {}).get("input_tokens", 0) or 0) for s in agent_steps), + "output_tokens": sum(int(s.get("usage", {}).get("output_tokens", 0) or 0) for s in agent_steps), + "total_tokens": sum(int(s.get("usage", {}).get("total_tokens", 0) or 0) for s in agent_steps), + "cost": sum(float(s.get("usage", {}).get("cost", 0) or 0) for s in agent_steps), + } + value["answer"].query_sources["token_usage"] = total_usage + LogWriter.info(f"request_id={req_id_cv.get()} EXIT question_for_agent") return value["answer"] except Exception as e: From 41abc99cc5af3a54a172350eb00a91428de9ce73 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Mon, 27 Apr 2026 23:13:24 +0530 Subject: [PATCH 13/70] fix: deduplicate LLM caller names in Token Overview table --- graphrag-ui/src/pages/TraceLogs.tsx | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx index 566cc4a..cd82095 100644 --- a/graphrag-ui/src/pages/TraceLogs.tsx +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -266,6 +266,17 @@ function formatNumber(n: number): string { return (n || 0).toLocaleString(); } +function formatCallerNames(calls: { caller_name: string }[]): string { + if (!calls || calls.length === 0) return "—"; + const counts: Record = {}; + calls.forEach((c) => { + counts[c.caller_name] = (counts[c.caller_name] || 0) + 1; + }); + return Object.entries(counts) + .map(([name, count]) => (count > 1 ? `${name} ×${count}` : name)) + .join(", "); +} + // ─── Sub-components ─────────────────────────────────────────────────────────── const StatusBadge: FC<{ status: string }> = ({ status }) => { @@ -474,12 +485,12 @@ const ToolCallExpandable: FC<{ tc: ToolCallEntry }> = ({ tc }) => {
{formatCost(tc.usage.cost)}
- {tc.usage.calls && tc.usage.calls.length > 0 && ( -
- {tc.usage.calls.length} LLM call{tc.usage.calls.length !== 1 ? "s" : ""}:{" "} - {tc.usage.calls.map((c) => c.caller_name).join(", ")} -
- )} + {tc.usage.calls && tc.usage.calls.length > 0 && ( +
+ {tc.usage.calls.length} LLM call{tc.usage.calls.length !== 1 ? "s" : ""}:{" "} + {formatCallerNames(tc.usage.calls)} +
+ )}
)}
@@ -687,7 +698,7 @@ const TokenOverviewPanel: FC<{ trace: TraceData }> = ({ trace }) => { {tc.usage!.calls && tc.usage!.calls.length > 0 - ? tc.usage!.calls.map((c) => c.caller_name).join(", ") + ? formatCallerNames(tc.usage!.calls) : "—"} From b2210ff7bde030daed68671b2c70142f586a74e1 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Wed, 29 Apr 2026 18:55:04 +0530 Subject: [PATCH 14/70] fix: add auth to trace endpoint, fix browser auth dialog, async file IO, remove unused endpoint --- .../src/components/CustomChatMessage.tsx | 4 ++- graphrag-ui/src/pages/TraceLogs.tsx | 26 +++++++++------ graphrag/app/routers/ui.py | 33 ++++--------------- 3 files changed, 25 insertions(+), 38 deletions(-) diff --git a/graphrag-ui/src/components/CustomChatMessage.tsx b/graphrag-ui/src/components/CustomChatMessage.tsx index 14ccf87..07e0d5e 100755 --- a/graphrag-ui/src/components/CustomChatMessage.tsx +++ b/graphrag-ui/src/components/CustomChatMessage.tsx @@ -192,7 +192,9 @@ export const CustomChatMessage: FC = ({ showGraph={handleShowGraph} onViewTrace={() => { const messageId = message.messageId || message.message_id || ""; - // No noopener — browser copies sessionStorage to the new tab automatically. + // Store message in sessionStorage so the new tab reads it directly + // without needing an authenticated API fetch (which triggers browser auth dialog). + sessionStorage.setItem(`trace_msg_${messageId}`, JSON.stringify(message)); window.open(`/trace/${messageId}`, "_blank"); }} /> diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx index cd82095..07b25ab 100644 --- a/graphrag-ui/src/pages/TraceLogs.tsx +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -742,13 +742,23 @@ const TraceLogs: FC = () => { const stateMessage = location.state?.message; const stateUserQuery = location.state?.userQuery; + // Check sessionStorage for message stored by the opener tab before API fetch. + const sessionKey = messageId ? `trace_msg_${messageId}` : null; + const sessionRaw = sessionKey ? sessionStorage.getItem(sessionKey) : null; + const sessionMessage = sessionRaw ? JSON.parse(sessionRaw) : null; + + const resolvedMessage = stateMessage || sessionMessage; + const [apiData, setApiData] = useState(null); - const [loading, setLoading] = useState(!stateMessage); + const [loading, setLoading] = useState(!resolvedMessage); useEffect(() => { - if (stateMessage || !messageId) return; + if (resolvedMessage || !messageId) return; setLoading(true); - fetch(`/ui/trace/${messageId}`) + const creds = sessionStorage.getItem("creds"); + fetch(`/ui/trace/${messageId}`, { + headers: { Authorization: `Basic ${creds}` }, + }) .then((res) => { if (!res.ok) throw new Error("Not found"); return res.json(); @@ -756,15 +766,15 @@ const TraceLogs: FC = () => { .then((data) => setApiData(data)) .catch(() => setApiData(null)) .finally(() => setLoading(false)); - }, [messageId, stateMessage]); + }, [messageId, resolvedMessage]); - const message = stateMessage || (apiData ? { + const message = resolvedMessage || (apiData ? { content: apiData.natural_language_response, response_time: apiData.response_time, response_type: apiData.response_type, query_sources: apiData.query_sources, } : null); - const userQuery = stateUserQuery || apiData?.user_query; + const userQuery = stateUserQuery || sessionMessage?.userQuery || apiData?.user_query; const trace = useMemo( () => buildTraceFromMessage(message, userQuery), @@ -817,10 +827,6 @@ const TraceLogs: FC = () => {

Trace Logs

- - - Session: {trace.sessionId} -
From 352d58a187b3990c715786742d219020ca31c751 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Thu, 30 Apr 2026 16:21:14 +0530 Subject: [PATCH 16/70] fix(GML-2086): preserve all rows for headerless Excel sheets --- common/utils/text_extractors.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index 4ba4c0f..891acdf 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -631,10 +631,20 @@ def extract_text_from_file(file_path, graphname=None): xl = pd.ExcelFile(file_path) sheet_texts = [] for sheet_name in xl.sheet_names: - df = xl.parse(sheet_name) + # Always read with header=None so no data row is silently + # consumed as column names for headerless spreadsheets. + df = xl.parse(sheet_name, header=None) if df.empty: continue df = df.fillna('') + # Detect header row: first row is all non-empty strings with + # no purely numeric values → treat as column names. + first_row = df.iloc[0] + if all(isinstance(v, str) and v.strip() for v in first_row): + df.columns = first_row.tolist() + df = df.iloc[1:].reset_index(drop=True) + else: + df.columns = [f"Column {i + 1}" for i in range(len(df.columns))] sheet_md = df.to_markdown(index=False) sheet_texts.append(f"## Sheet: {sheet_name}\n\n{sheet_md}") return "\n\n".join(sheet_texts) if sheet_texts else "[Excel file is empty or contains no data]" From c8230805d4faba90294b6ee625f8916448502b7e Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Thu, 30 Apr 2026 16:33:39 +0530 Subject: [PATCH 17/70] fix(GML-2086): align supported_extensions dict with get_supported_extensions --- common/utils/text_extractors.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index 891acdf..957387d 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -137,6 +137,8 @@ def __init__(self): '.xml': 'application/xml', '.jpeg': 'image/jpeg', '.jpg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', '.jsonl': 'application/x-jsonlines' } @@ -687,7 +689,7 @@ def get_doc_type_from_extension(extension): def get_supported_extensions(): """Get list of supported file extensions.""" - return {'.txt', '.md', '.html', '.htm', '.csv', '.json', '.pdf', '.docx', '.xml', '.jpeg', '.jpg', '.png', '.gif', '.xlsx', '.xls'} + return {'.txt', '.md', '.html', '.htm', '.csv', '.json', '.pdf', '.docx', '.doc', '.xml', '.jpeg', '.jpg', '.png', '.gif', '.xlsx', '.xls', '.jsonl'} def is_supported_file(file_path): """Check if a file is supported for text extraction.""" From f75a9d2ef2547f0773ce32a04d09c4222f90ac0a Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Thu, 30 Apr 2026 16:37:26 +0530 Subject: [PATCH 18/70] fix(GML-2086): handle non-UTF-8 encodings in CSV extraction --- common/utils/text_extractors.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index 957387d..82442ba 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -613,9 +613,22 @@ def extract_text_from_file(file_path, graphname=None): if extension in ['.txt', '.md']: with open(file_path, 'r', encoding='utf-8') as f: return f.read().strip() - elif extension in ['.html', '.htm', '.csv']: + elif extension in ['.html', '.htm']: with open(file_path, 'r', encoding='utf-8') as f: return f.read().strip() + elif extension == '.csv': + raw = file_path.read_bytes() + # utf-8-sig handles UTF-8 with BOM (common Excel CSV export) + try: + return raw.decode('utf-8-sig').strip() + except UnicodeDecodeError: + pass + # Fall back to chardet detection + import chardet + detected = chardet.detect(raw) + encoding = detected.get('encoding') if detected.get('confidence', 0) >= 0.5 else None + # latin-1 as final fallback — never raises DecodeError + return raw.decode(encoding or 'latin-1').strip() elif extension == '.json': with open(file_path, 'r', encoding='utf-8') as f: data = json.load(f) From 58b6d3281baa7c9f63a29585cf0cf39a932c3959 Mon Sep 17 00:00:00 2001 From: Prins Kumar Date: Mon, 4 May 2026 19:23:59 +0530 Subject: [PATCH 19/70] fix: remove chunk text from citations in trace logs UI and backend JSON --- graphrag-ui/src/pages/TraceLogs.tsx | 68 ++++++++--------------------- graphrag/app/routers/ui.py | 11 ++++- 2 files changed, 28 insertions(+), 51 deletions(-) diff --git a/graphrag-ui/src/pages/TraceLogs.tsx b/graphrag-ui/src/pages/TraceLogs.tsx index 07b25ab..bddf861 100644 --- a/graphrag-ui/src/pages/TraceLogs.tsx +++ b/graphrag-ui/src/pages/TraceLogs.tsx @@ -156,10 +156,6 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { // ── Citations ─────────────────────────────────────────────────────────── const rawReasoning = qs.reasoning; - const finalRetrieval = - typeof qs.result === "object" && qs.result?.final_retrieval - ? qs.result.final_retrieval - : null; const citations: CitationEntry[] = []; if (rawReasoning && Array.isArray(rawReasoning)) { @@ -169,17 +165,11 @@ function buildTraceFromMessage(message: any, userQuery?: string): TraceData { const cited = raw.startsWith("* "); const chunkName = raw.replace(/^\*\s*/, ""); - let chunkText = ""; - if (finalRetrieval && finalRetrieval[chunkName]) { - const val = finalRetrieval[chunkName]; - chunkText = Array.isArray(val) ? val.join("\n\n") : String(val); - } - citations.push({ id: i + 1, source: chunkName, cited, - text: chunkText, + text: "", }); }); } @@ -524,47 +514,25 @@ const ToolCallsPanel: FC<{ trace: TraceData }> = ({ trace }) => ( ); -const CitationRow: FC<{ c: CitationEntry }> = ({ c }) => { - const [open, setOpen] = useState(false); - return ( -
-
setOpen((p) => !p)} - > -
- - - [{c.source}] - - {c.cited && ( - - Cited - - )} -
-
- {open ? ( - - ) : ( - - )} -
-
- {open && ( -
- {c.text || "No content retrieved for this chunk."} -
+const CitationRow: FC<{ c: CitationEntry }> = ({ c }) => ( +
+
+ + [{c.source}] + {c.cited && ( + + Cited + )}
- ); -}; +
+); const CitationsPanel: FC<{ trace: TraceData }> = ({ trace }) => (
diff --git a/graphrag/app/routers/ui.py b/graphrag/app/routers/ui.py index 5ddfb17..79dff2a 100644 --- a/graphrag/app/routers/ui.py +++ b/graphrag/app/routers/ui.py @@ -75,6 +75,15 @@ def _save_trace_log(message_id: str, conversation_id: str, user_query: str, resp: GraphRAGResponse, elapsed: float): try: os.makedirs(TRACE_LOGS_DIR, exist_ok=True) + + # Strip chunk text from query_sources to keep trace files small. + # final_retrieval contains the full text of every retrieved chunk. + query_sources = dict(resp.query_sources) if resp.query_sources else {} + result = query_sources.get("result") + if isinstance(result, dict) and "final_retrieval" in result: + result = {k: v for k, v in result.items() if k != "final_retrieval"} + query_sources = {**query_sources, "result": result} + trace_data = { "message_id": message_id, "conversation_id": conversation_id, @@ -82,7 +91,7 @@ def _save_trace_log(message_id: str, conversation_id: str, user_query: str, resp "response_time": elapsed, "response_type": resp.response_type, "answered_question": resp.answered_question, - "query_sources": resp.query_sources, + "query_sources": query_sources, "natural_language_response": resp.natural_language_response, "timestamp": time.time(), } From ae872a590b2bec2ecc4504568dbac0d66aeac4a3 Mon Sep 17 00:00:00 2001 From: Chengbiao Jin Date: Sat, 2 May 2026 12:22:44 -0700 Subject: [PATCH 20/70] v1.4.0: schema-aware ingest, prompt centralization, faster startup Schema-aware ingest - Add Initialize Knowledge Graph flow with optional schema generation from samples or pasted GSQL, reviewable in a form-mode editor - Populate declared domain vertex / edge instances during extraction; opt-in strict mode drops non-schema extractions - Add typed-relationship metadata layer (EntityType / RelationshipType) carrying per-type definitions used by retrievers and the chat agent; auto-fills from extracted types when no domain schema is declared - Surface domain vertex type labels in retrieval LLM context - Thread schema definitions into LLM prompts for query generation and extraction; permissive parser accepts UNDIRECTED edges - Add E2E test covering the schema-aware initialization and ingest path Prompt management - Centralize all customizable prompts as in-code defaults - Make schema-extraction prompt customizable via the /prompts API - Lift prompt_path to global llm_config scope - Validate and escape user-customized prompts server-side - Add E2E test covering the customizable-prompt round-trip Startup / runtime - Async embedding-store initialization - Parallel image description with concurrency knob - Skip redundant GDS install when already present - Auto-default Bedrock max_tokens per model family so large prompts do not truncate - Surface TigerGraph version mismatch as a clear startup error - Embedding-store status check returns HTTP 503 on failure Reliability - Detect real GSQL failures in schema-change apply --- .github/workflows/cloud-build-deploy-ci.yaml | 9 + .github/workflows/cloud-build-nightly.yaml | 9 + .github/workflows/onprem-build-nightly.yaml | 9 + .github/workflows/onprem-build.yaml | 9 + CHANGELOG.md | 33 + README.md | 120 +- common/config.py | 121 +- common/db/schema_extraction.py | 115 ++ common/db/schema_utils.py | 1259 +++++++++++++++++ .../embeddings/tigergraph_embedding_store.py | 53 +- .../LLMEntityRelationshipExtractor.py | 103 ++ common/gsql/supportai/SupportAI_Schema.gsql | 5 +- .../create_entity_type_relationships.gsql | 20 - .../retrievers/Content_Similarity_Search.gsql | 6 + .../Content_Similarity_Vector_Search.gsql | 6 + .../retrievers/GraphRAG_Hybrid_Search.gsql | 12 +- .../GraphRAG_Hybrid_Search_Display.gsql | 8 +- .../GraphRAG_Hybrid_Vector_Search.gsql | 8 +- common/llm_services/aws_bedrock_service.py | 80 +- common/llm_services/base_llm.py | 442 ++++-- .../chatbot_response.txt | 17 - .../community_summarization.txt | 11 - .../entity_relationship_extraction.txt | 24 - .../generate_cypher.txt | 85 -- .../generate_function.txt | 27 - .../graphrag_scoring.txt | 7 - .../map_question_to_schema.txt | 14 - .../aws_bedrock_titan/generate_function.txt | 14 - .../map_question_to_schema.txt | 19 - .../entity_relationship_extraction.txt | 24 - .../generate_function.txt | 29 - .../map_question_to_schema.txt | 17 - .../prompts/custom/aml/chatbot_response.txt | 29 - .../custom/aml/community_summarization.txt | 11 - .../aml/entity_relationship_extraction.txt | 24 - common/prompts/custom/aml/generate_cypher.txt | 85 -- .../prompts/custom/aml/generate_function.txt | 27 - .../prompts/custom/aml/graphrag_scoring.txt | 7 - .../custom/aml/map_question_to_schema.txt | 14 - .../community_summarization.txt | 11 - .../entity_relationship_extraction.txt | 24 - .../gcp_vertexai_palm/generate_function.txt | 33 - .../map_question_to_schema.txt | 14 - .../google_gemini/chatbot_response.txt | 17 - .../google_gemini/community_summarization.txt | 11 - .../entity_relationship_extraction.txt | 24 - .../prompts/google_gemini/generate_cypher.txt | 84 -- .../google_gemini/generate_function.txt | 24 - .../google_gemini/graphrag_scoring.txt | 7 - .../google_gemini/map_question_to_schema.txt | 15 - .../google_gemini/question_expansion.txt | 6 - .../prompts/llama_70b/generate_function.txt | 24 - .../llama_70b/map_question_to_schema.txt | 15 - .../prompts/openai_gpt4/chatbot_response.txt | 17 - .../openai_gpt4/community_summarization.txt | 11 - .../entity_relationship_extraction.txt | 24 - .../prompts/openai_gpt4/generate_cypher.txt | 85 -- .../prompts/openai_gpt4/generate_function.txt | 27 - .../prompts/openai_gpt4/graphrag_scoring.txt | 7 - .../openai_gpt4/map_question_to_schema.txt | 15 - .../openai_gpt4/question_expansion.txt | 6 - common/utils/image_data_extractor.py | 3 + common/utils/prompt_validation.py | 135 ++ common/utils/text_extractors.py | 155 +- ecc/app/ecc_util.py | 4 +- ecc/app/eventual_consistency_checker.py | 22 +- ecc/app/graphrag/community_summarizer.py | 2 +- ecc/app/graphrag/graph_rag.py | 13 +- ecc/app/graphrag/util.py | 129 +- ecc/app/graphrag/workers.py | 365 ++++- ecc/app/main.py | 17 +- ecc/app/supportai/supportai_init.py | 4 +- ecc/app/supportai/util.py | 4 +- ecc/app/supportai/workers.py | 32 +- graphrag-ui/src/components/SideMenu.tsx | 7 +- graphrag-ui/src/pages/Setup.tsx | 11 +- .../src/pages/setup/GraphRAGConfig.tsx | 58 + graphrag-ui/src/pages/setup/KGAdmin.tsx | 1085 +++++++++++++- graphrag-ui/src/utils/safeJson.ts | 18 + graphrag/app/agent/agent.py | 6 +- graphrag/app/routers/inquiryai.py | 53 +- graphrag/app/routers/supportai.py | 37 +- graphrag/app/routers/ui.py | 502 +++++-- graphrag/app/supportai/supportai_ingest.py | 63 +- graphrag/app/tools/generate_cypher.py | 22 +- graphrag/app/tools/generate_gsql.py | 22 +- graphrag/tests/conftest.py | 24 +- .../tests/test_e2e_prompt_customization.py | 300 ++++ .../tests/test_e2e_schema_aware_ingest.py | 692 +++++++++ .../test_llm_entity_relationship_extractor.py | 143 ++ graphrag/tests/test_prompt_validation.py | 184 +++ graphrag/tests/test_schema_extraction.py | 200 +++ graphrag/tests/test_schema_utils.py | 1133 +++++++++++++++ 93 files changed, 7304 insertions(+), 1554 deletions(-) create mode 100644 common/db/schema_extraction.py create mode 100644 common/db/schema_utils.py delete mode 100644 common/gsql/supportai/create_entity_type_relationships.gsql delete mode 100644 common/prompts/aws_bedrock_claude3haiku/chatbot_response.txt delete mode 100644 common/prompts/aws_bedrock_claude3haiku/community_summarization.txt delete mode 100644 common/prompts/aws_bedrock_claude3haiku/entity_relationship_extraction.txt delete mode 100644 common/prompts/aws_bedrock_claude3haiku/generate_cypher.txt delete mode 100644 common/prompts/aws_bedrock_claude3haiku/generate_function.txt delete mode 100644 common/prompts/aws_bedrock_claude3haiku/graphrag_scoring.txt delete mode 100644 common/prompts/aws_bedrock_claude3haiku/map_question_to_schema.txt delete mode 100644 common/prompts/aws_bedrock_titan/generate_function.txt delete mode 100644 common/prompts/aws_bedrock_titan/map_question_to_schema.txt delete mode 100644 common/prompts/azure_open_ai_gpt35_turbo_instruct/entity_relationship_extraction.txt delete mode 100644 common/prompts/azure_open_ai_gpt35_turbo_instruct/generate_function.txt delete mode 100644 common/prompts/azure_open_ai_gpt35_turbo_instruct/map_question_to_schema.txt delete mode 100644 common/prompts/custom/aml/chatbot_response.txt delete mode 100644 common/prompts/custom/aml/community_summarization.txt delete mode 100644 common/prompts/custom/aml/entity_relationship_extraction.txt delete mode 100644 common/prompts/custom/aml/generate_cypher.txt delete mode 100644 common/prompts/custom/aml/generate_function.txt delete mode 100644 common/prompts/custom/aml/graphrag_scoring.txt delete mode 100644 common/prompts/custom/aml/map_question_to_schema.txt delete mode 100644 common/prompts/gcp_vertexai_palm/community_summarization.txt delete mode 100644 common/prompts/gcp_vertexai_palm/entity_relationship_extraction.txt delete mode 100644 common/prompts/gcp_vertexai_palm/generate_function.txt delete mode 100644 common/prompts/gcp_vertexai_palm/map_question_to_schema.txt delete mode 100644 common/prompts/google_gemini/chatbot_response.txt delete mode 100644 common/prompts/google_gemini/community_summarization.txt delete mode 100644 common/prompts/google_gemini/entity_relationship_extraction.txt delete mode 100644 common/prompts/google_gemini/generate_cypher.txt delete mode 100644 common/prompts/google_gemini/generate_function.txt delete mode 100644 common/prompts/google_gemini/graphrag_scoring.txt delete mode 100644 common/prompts/google_gemini/map_question_to_schema.txt delete mode 100644 common/prompts/google_gemini/question_expansion.txt delete mode 100644 common/prompts/llama_70b/generate_function.txt delete mode 100644 common/prompts/llama_70b/map_question_to_schema.txt delete mode 100644 common/prompts/openai_gpt4/chatbot_response.txt delete mode 100644 common/prompts/openai_gpt4/community_summarization.txt delete mode 100644 common/prompts/openai_gpt4/entity_relationship_extraction.txt delete mode 100644 common/prompts/openai_gpt4/generate_cypher.txt delete mode 100644 common/prompts/openai_gpt4/generate_function.txt delete mode 100644 common/prompts/openai_gpt4/graphrag_scoring.txt delete mode 100644 common/prompts/openai_gpt4/map_question_to_schema.txt delete mode 100644 common/prompts/openai_gpt4/question_expansion.txt create mode 100644 common/utils/prompt_validation.py create mode 100644 graphrag-ui/src/utils/safeJson.ts create mode 100644 graphrag/tests/test_e2e_prompt_customization.py create mode 100644 graphrag/tests/test_e2e_schema_aware_ingest.py create mode 100644 graphrag/tests/test_llm_entity_relationship_extractor.py create mode 100644 graphrag/tests/test_prompt_validation.py create mode 100644 graphrag/tests/test_schema_extraction.py create mode 100644 graphrag/tests/test_schema_utils.py diff --git a/.github/workflows/cloud-build-deploy-ci.yaml b/.github/workflows/cloud-build-deploy-ci.yaml index 30bc588..9c5e9b0 100644 --- a/.github/workflows/cloud-build-deploy-ci.yaml +++ b/.github/workflows/cloud-build-deploy-ci.yaml @@ -3,6 +3,15 @@ name: GraphRAG Build Cloud on: push: branches: [ "cloud-main" ] + paths-ignore: + - '**.md' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.gitattributes' + - '.github/ISSUE_TEMPLATE/**' + - '.github/PULL_REQUEST_TEMPLATE*' + - '.github/CODEOWNERS' workflow_dispatch: env: diff --git a/.github/workflows/cloud-build-nightly.yaml b/.github/workflows/cloud-build-nightly.yaml index e7f5356..86f89a8 100644 --- a/.github/workflows/cloud-build-nightly.yaml +++ b/.github/workflows/cloud-build-nightly.yaml @@ -3,6 +3,15 @@ name: GraphRAG Build Cloud Nightly on: push: branches: [ "cloud-dev" ] + paths-ignore: + - '**.md' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.gitattributes' + - '.github/ISSUE_TEMPLATE/**' + - '.github/PULL_REQUEST_TEMPLATE*' + - '.github/CODEOWNERS' workflow_dispatch: env: diff --git a/.github/workflows/onprem-build-nightly.yaml b/.github/workflows/onprem-build-nightly.yaml index 29ef4c8..92b7c3c 100644 --- a/.github/workflows/onprem-build-nightly.yaml +++ b/.github/workflows/onprem-build-nightly.yaml @@ -3,6 +3,15 @@ name: GraphRAG Build Nightly on: push: branches: [ "dev" ] + paths-ignore: + - '**.md' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.gitattributes' + - '.github/ISSUE_TEMPLATE/**' + - '.github/PULL_REQUEST_TEMPLATE*' + - '.github/CODEOWNERS' workflow_dispatch: env: diff --git a/.github/workflows/onprem-build.yaml b/.github/workflows/onprem-build.yaml index 04b7456..9fb26a4 100644 --- a/.github/workflows/onprem-build.yaml +++ b/.github/workflows/onprem-build.yaml @@ -3,6 +3,15 @@ name: GraphRAG Build On-Prem on: push: branches: [ "main" ] + paths-ignore: + - '**.md' + - 'docs/**' + - 'LICENSE' + - '.gitignore' + - '.gitattributes' + - '.github/ISSUE_TEMPLATE/**' + - '.github/PULL_REQUEST_TEMPLATE*' + - '.github/CODEOWNERS' workflow_dispatch: env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b34570..5d89e53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,38 @@ # Changelog +## [1.4.0] + +### Added +- **Schema-aware initialization** at *Initialize Knowledge Graph* time, with three modes: skip schema, generate a draft from sample documents, or paste a GSQL schema. Drafts are reviewed in a form-mode editor before being applied as a single atomic schema-change job that never drops existing types. +- **Schema-aware extraction**: when an extracted entity or relationship matches a declared domain type or pair, ECC populates the domain vertex / edge directly. A configurable strict mode drops non-schema extractions instead of falling back to the raw `Entity` layer. +- **Typed-relationship metadata layer** (`EntityType` / `RelationshipType` vertices linked by `IS_HEAD_OF` / `HAS_TAIL`) carrying type names and human-readable definitions; available to retrievers and to the chat agent. The layer auto-fills from extracted free-text types when no domain types are declared (with case / suffix / plural deduplication), and is restricted to declared types only when a domain schema exists. +- **Customizable schema-extraction prompt** in the `/prompts` API alongside the existing chatbot, entity-relationship, community-summarization, and query-generation prompts. Per-graph overrides supported. +- **Schema definitions threaded into LLM prompts** for query generation (Cypher / GSQL) and entity-relationship extraction, so the model sees per-type descriptions alongside the schema rep. +- **JSONL caching shared between schema extraction and ingest** — files uploaded for schema extraction are reused by the ingest flow without re-conversion. +- **Parallel image description** during PDF processing (default 8 workers, env-overridable). +- **Async embedding-store initialization** — service startup no longer blocks on the TigerGraph connection; status surfaces as `initializing` / `ok` / `error`. + +### Changed +- **All customizable prompts now ship as in-code defaults**, packaged inside the LLM service. Provider prompt directories are kept (empty) for backward compatibility; per-graph and global overrides still win when present. +- **`prompt_path` is a top-level `llm_config` field**, applied across LLM-prompted services automatically. Per-service `prompt_path` entries are still honored on disk but no longer needed. +- **Permissive schema parser** accepts both `DIRECTED` and `UNDIRECTED` edges and rejects names that collide with GraphRAG structural types or GSQL keywords. +- **Server-side prompt validation**: `/prompts` POST rejects edits missing required placeholders and auto-escapes stray `{token}` occurrences in user content. +- **`apply_proposal` reports a real failure** when the GSQL server returns a known error marker, instead of falsely reporting success. +- **TigerGraph embedding store skips redundant GDS install** when the `gds.vector` package is already installed, eliminating multi-minute catalog-lock stalls on container restart. +- **TigerGraph version mismatch** raises a clear `ValueError` at ECC startup instead of leaving the embedding store undefined. +- **`check_embedding_store_status()`** in the inquiryai / supportai routers raises HTTP 503 instead of swallowing the exception. +- **Bedrock `max_tokens` is auto-defaulted** per model family (Claude 3.x = 4096, Sonnet 3.5+ / 4.x = 8192, Titan / Cohere / Llama at their published caps), so schema extraction and other large-output prompts no longer truncate at the langchain-aws built-in 1024 default. Explicit `model_kwargs.max_tokens` and the existing `token_limit` config field both override the auto-default. +- **Hybrid / similarity retrievers surface domain vertex types** in the LLM context with a `: ` label, so type-aware questions (e.g. "which companies …") receive properly grounded answers. + +### Removed +- **`RELATIONSHIP_TYPE` edge** between `EntityType` vertices — superseded by `IS_HEAD_OF` + `HAS_TAIL` through `RelationshipType`. + +### Configuration +- New `graphrag_config` keys: `schema_max_sample_files` (default 5), `schema_max_total_mb` (default 50), `strict_mode` (default false). +- New env var: `PDF_IMAGE_CONCURRENCY` (default 8). + +> Implementation-level details for v1.4.0 (parser internals, endpoint contracts, dialog state machine, prompt-resolution chain, schema-aware ECC worker logic, etc.) live in `dev/plans/graphrag/v1.4.0_implementation_notes.md`. + ## [1.3.1] ### Changed diff --git a/README.md b/README.md index e49317b..94d4cbd 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,14 @@ - [Ollama](#ollama) - [Hugging Face](#hugging-face) - [Groq](#groq) +- [Tuning Guideline](#tuning-guideline) + - [Tune in the right order](#1-tune-in-the-right-order) + - [Chunking](#2-chunking--get-the-granularity-right) + - [Extraction](#3-extraction--make-the-graph-clean-before-tuning-retrieval) + - [Retrieval](#4-retrieval--match-context-size-to-the-question) + - [Prompts](#5-prompts--last-resort-biggest-leverage-when-the-rest-is-right) + - [Performance / cost knobs](#6-performance--cost-knobs) + - [A working tuning loop](#7-a-working-tuning-loop) - [Customization and Extensibility](#customization-and-extensibility) - [Test Your Code Changes](#test-your-code-changes) - [Testing with Pytest](#testing-with-pytest) @@ -55,8 +63,10 @@ --- ## Releases -* **2/28/2026**: GraphRAG v1.2.0 released. Added Admin UI for graph initialization, document ingestion, and knowledge graph rebuild, along with many other improvements and bug fixes. See [release notes](https://github.com/tigergraph/graphrag/releases/tag/v1.2.0) for details. -* **9/22/2025**: GraphRAG is available now officially v1.1 (v1.1.0). AWS Bedrock support is completed with BDA integration for multimodal document ingestion. See [release notes](https://github.com/tigergraph/graphrag/releases/tag/v1.1.0) for details. +* **GraphRAG v1.4.0** (in progress): Schema-aware initialization. The *Initialize Knowledge Graph* dialog accepts an optional pasted GSQL schema; the backend parses, diffs, and applies domain types as a single atomic schema-change job. Type definitions captured at init time flow through to the entity-extraction prompt and the query-routing schema rep. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.4.0) when published. +* **4/10/2026**: GraphRAG v1.3.0 released. Added an admin configuration UI with role-based access and per-graph chatbot LLM override, along with many other improvements and bug fixes. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.3.0) for details. +* **2/28/2026**: GraphRAG v1.2.0 released. Added Admin UI for graph initialization, document ingestion, and knowledge graph rebuild, along with many other improvements and bug fixes. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.2.0) for details. +* **9/22/2025**: GraphRAG is available now officially v1.1 (v1.1.0). AWS Bedrock support is completed with BDA integration for multimodal document ingestion. See [Release Notes](https://github.com/tigergraph/graphrag/releases/tag/v1.1.0) for details. * **6/18/2025**: GraphRAG is available now officially v1.0 (v1.0.0). TigerGraph database is the only graph and vector storagge supported. Please see [Release Notes](https://docs.tigergraph.com/tg-graphrag/current/release-notes/) for details. @@ -878,6 +888,112 @@ Example configuration for a model on Hugging Face with a serverless endpoint is --- +## Tuning Guideline + +GraphRAG answer quality, latency, and LLM cost are sensitive to a small set of parameters and prompts. This section is a high-level strategy — adjust *one knob at a time*, run the same set of evaluation questions before and after each change, and keep what helps. Detailed parameter descriptions live in [GraphRAG configuration](#graphrag-configuration). + +### 1. Tune in the right order + +A common mistake is tuning retrieval and prompts before the underlying graph is good. Work bottom-up: + +1. **Chunking** — fix how the source documents are split. +2. **Extraction** — fix what entities / relationships are pulled from each chunk. +3. **Retrieval** — pick the right context for each question. +4. **Response prompts** — shape the final answer. + +A bad answer at step 4 is rarely fixed by editing the response prompt; usually it's caused by step 1, 2, or 3. + +### 2. Chunking — get the granularity right + +| Symptom | Likely cause | Tweak | +| --- | --- | --- | +| Answers cite irrelevant facts from elsewhere in the same chunk | chunks too large | drop `chunk_size` (`character` / `markdown` / `html` / `recursive` chunkers); raise `threshold` (`semantic`) so it splits more aggressively | +| Answers miss context that's clearly in the source | chunks too small or no overlap | raise `chunk_size`; bump `overlap_size` (default 1/8 of `chunk_size`); lower `threshold` (`semantic`) | +| Tables / figures get fragmented | wrong chunker for the source | use `markdown` for markdown / docs converted to markdown; use `html` for HTML pages with structure; use `regex` with a custom `pattern` for structured logs | +| Cross-section reasoning fails | no overlap | increase `overlap_size` to ~25% of `chunk_size` | + +Default starting point for prose: `chunker: "semantic"`, `threshold: 0.95`, `chunker_config.method: "percentile"`. Move to `markdown` chunker with `chunk_size: 2048` and `overlap_size: 256` if your source is markdown-heavy and table integrity matters. + +### 3. Extraction — make the graph clean before tuning retrieval + +The extraction prompt drives what becomes a vertex / edge. Two failure modes show up: + +- **Document-structure noise** — the graph fills up with layout artifacts (page numbers, section headers, table captions, chart labels) instead of domain entities. This crushes downstream retrieval because the LLM has to wade through structural junk. +- **Generic abstractions** — over-merged or under-specified buckets (e.g. an "entity" or "record" type that swallows everything) instead of the concrete domain types you actually care about. For example, in a financial corpus you want `Company`, `Fund`, `Account`, `Person`, `Filing`, `Risk` — not a single `record` bin. + +Today's primary lever is the **entity-extraction prompt**: + +- **Customize the prompt for your domain** via Settings → *Customize Prompts* → entity extraction. Tell the LLM explicitly what counts and what doesn't. For a financial domain: *"Extract concrete real-world entities (companies, people, funds, accounts, filings, transactions, risks). Ignore document layout (page numbers, headers/footers, tables, captions, figures, navigation menus)."* +- **Add 1–2 short domain examples** in the prompt. Even one well-chosen exemplar (an extracted entity with type and definition) dramatically improves consistency across chunks. +- **List the canonical edge verbs you want.** Encourage `PUBLISHES`, `OWNS`, `ISSUES`, `MANAGES`, `REPORTS_ON` in the relationship-extraction prompt rather than letting the LLM emit ad-hoc nominal phrases. + +If extraction quality is still poor after iterating on the prompt, the next-best option today is to clear the graph's domain types and re-ingest with the improved prompt — schema growth is currently driven entirely by what extraction produces. (A schema-aware initialization flow that lets you supply a curated schema up front is on the roadmap.) + +### 4. Retrieval — match context size to the question + +Three knobs interact: `top_k`, `num_hops`, `num_seen_min`. Also `chunk_only` / `doc_only` and (for community search) `community_level` / `with_chunk`. + +| Question style | Recommended start | Reasoning | +| --- | --- | --- | +| *"What is X?"* (specific lookup) | `top_k=3`, `num_hops=1`, `num_seen_min=1` | Tight neighborhood, few seeds. | +| *"How are X and Y related?"* (relational) | `top_k=5`, `num_hops=2`, `num_seen_min=1` | Need to traverse between concepts. | +| *"Summarize the report"* (broad) | `top_k=8`, `num_hops=2`, `num_seen_min=2` | More seeds, filter loose connections. | +| *"Compare A across multiple sections"* (multi-hop reasoning) | `top_k=8`, `num_hops=3`, `num_seen_min=2` | Wide traversal, but tighten the filter. | +| *"List all X"* (aggregation) | use *Community Search* with `community_level: 1–2` | Broader summaries, not chunk-level retrieval. | + +Heuristics: + +- If the answer is **vague or hallucinated**, you don't have enough context: raise `top_k` first, then `num_hops`. +- If the answer is **drowning in irrelevant detail**, you have too much: drop `top_k`, raise `num_seen_min`, or set `chunk_only: true`. +- If the answer **misses things across sections**, raise `num_hops` (1 → 2 → 3). Each extra hop multiplies result size, so don't go past 3 without strong evidence. +- If the answer **cites whole documents but loses chunk-level detail**, set `doc_only: false` and `chunk_only: true`. +- For broad-survey questions, prefer `community_search` over hybrid; tune `community_level` (lower = more granular communities, higher = broader summaries). + +Each tweak should be made **alone** — moving `top_k` and `num_hops` together makes it impossible to tell which one helped. + +### 5. Prompts — last resort, biggest leverage when the rest is right + +Customize prompts via the UI: *Settings → Customize Prompts*. The four customizable prompt groups (UI labels and underlying ids): + +- **Entity Relationships** (`entity_relationship`) — combined entity- and relationship-extraction prompt; controls what becomes a vertex / edge. Tune for noise suppression, domain specificity, and verb-form edge names (e.g. `PUBLISHES`, `OWNS`, `MANAGES` instead of nominal phrases). See §3. +- **Schema Instructions** (`query_generation`) — instructions used when generating GSQL / Cypher and when filtering the schema for a structured query. Tune if your domain has unusual type names that aren't matching user phrasing, or if generated queries miss obvious joins. +- **Community Summarization** (`community_summarization`) — how community summaries are produced during knowledge-graph build. Tune for length / tone and to bias summaries toward domain-specific framing. +- **Chatbot Responses** (`chatbot_response`) — the final answer template. Keep it short; the LLM responds best to clear constraints (*"answer in ≤3 sentences, cite the doc id"*). + +When customizing: + +- **Always start from the system default** (don't write from scratch). +- **Keep examples short and domain-relevant.** +- **Test with the same evaluation set** before and after — a prompt change that fixes one question often regresses another. +- **Know where overrides live.** The runtime resolves prompt files in this order: + 1. Graph-scoped: `configs/graph_configs//prompts/.txt` — created when you edit prompts with a specific graph selected. Highest priority. + 2. Global override: `configs/prompts/.txt` — created when you edit prompts globally and the bundled provider default path is read-only. + 3. Provider default (bundled): `./common/prompts//.txt` — selected by the `prompt_path` field in the LLM config. Shipped with the deployment. +- **Version-control the override directories** so they survive container rebuilds and travel with the deployment. +- **Delete custom prompt overrides** if you suspect they're stale; the system falls back to the next layer cleanly. + +### 6. Performance / cost knobs + +- **`default_concurrency`** drives all internal semaphores. ECC uses 2× this value for ingest workers; the chatbot uses 1×. Raise it to speed up ingestion of large corpora; lower it if you're hitting LLM rate limits or seeing socket exhaustion. +- **`reuse_embedding: true`** skips re-embedding identical text — major saving on re-ingest of unchanged documents. +- **Choose `llm_model` thoughtfully** — entity / relationship extraction tolerates cheaper / faster models (Haiku, Nova-lite, Flash); response synthesis benefits from stronger ones (Sonnet, GPT-4-class). The `multimodal_service` is independent — set it to a vision-capable model only when you actually ingest images. +- **`load_batch_size`** and **`upsert_delay`** control ingestion pressure on TigerGraph. Defaults are fine for most loads; lower the batch size if you see write timeouts. + +### 7. A working tuning loop + +1. Define **5–10 representative evaluation questions** with expected answers (or at least the docs that should ground them). +2. Establish a **baseline** — run all questions, save answers + retrieved chunks. +3. **Change one parameter** (or one prompt). Re-run. +4. Diff the answers. Keep the change only if it improves more questions than it regresses. +5. **Iterate in order** — chunking → extraction → retrieval → prompts. Don't skip ahead. +6. **Save the winning config** to `configs/server_config.json` and document the rationale in your team's runbook. + +The chatbot UI's *Explain* panel (which lists the chunks fed into the answer) is the fastest debugging tool — most quality issues become obvious by reading the chunks the system actually retrieved. + +[Go back to top](#top) + +--- + ## Customization and Extensibility TigerGraph GraphRAG is designed to be easily extensible. The service can be configured to use different LLM providers, different graph schemas, and different LangChain tools. The service can also be extended to use different embedding services, different LLM generation services, and different LangChain tools. For more information on how to extend the service, see the [Developer Guide](./docs/DeveloperGuide.md). diff --git a/common/config.py b/common/config.py index 3dc3be1..3216c60 100644 --- a/common/config.py +++ b/common/config.py @@ -166,6 +166,17 @@ def resolve_llm_services(llm_cfg: dict) -> dict: if svc_key in cfg and "region_name" not in cfg[svc_key]: cfg[svc_key]["region_name"] = top_region + # Inject top-level prompt_path into LLM-prompted service configs + # if missing. The UI never lets users set per-service prompt_paths; + # in practice they are always identical to completion's. + # ``embedding_service`` is excluded — embedding models never load + # prompt files (their class hierarchy has no prompt-property machinery). + top_prompt_path = cfg.get("prompt_path") + if top_prompt_path: + for svc_key in ["completion_service", "multimodal_service", "chat_service"]: + if svc_key in cfg and "prompt_path" not in cfg[svc_key]: + cfg[svc_key]["prompt_path"] = top_prompt_path + completion = cfg.get("completion_service", {}) # Resolve embedding: inherit provider-level config from completion @@ -366,6 +377,15 @@ def get_graphrag_config(graphname=None): if svc_key in llm_config and "region_name" not in llm_config[svc_key]: llm_config[svc_key]["region_name"] = llm_config["region_name"] +# Inject top-level prompt_path into LLM-prompted service configs if +# missing. Embedding service is excluded — embedding models never load +# prompt files. Per-service entries on disk are accepted for backward +# compat but never written by the UI. +if "prompt_path" in llm_config: + for svc_key in ["completion_service", "multimodal_service", "chat_service"]: + if svc_key in llm_config and "prompt_path" not in llm_config[svc_key]: + llm_config[svc_key]["prompt_path"] = llm_config["prompt_path"] + _comp = llm_config.get("completion_service") if _comp is None: raise Exception("completion_service is not found in llm_config") @@ -441,6 +461,15 @@ def get_graphrag_config(graphname=None): else: raise Exception("Embedding service not implemented") +def get_embedding_service(): + """Return the current embedding service instance. + + Use this instead of importing ``embedding_service`` directly so + consumers always read the latest instance after a config reload. + """ + return embedding_service + + def get_llm_service(service_config: dict) -> LLM_Model: """ Instantiate an LLM provider from a flat service config dict. @@ -474,25 +503,73 @@ def get_llm_service(service_config: dict) -> LLM_Model: raise Exception(f"LLM service '{service_name}' not supported") -if os.getenv("INIT_EMBED_STORE", "true") == "true": - conn = TigerGraphConnection( - host=db_config.get("hostname", "http://tigergraph"), - username=db_config.get("username", "tigergraph"), - password=db_config.get("password", "tigergraph"), - gsPort=db_config.get("gsPort", "14240"), - restppPort=db_config.get("restppPort", "9000"), - graphname=db_config.get("graphname", ""), - apiToken=db_config.get("apiToken", ""), - ) - if not db_config.get("apiToken") and db_config.get("getToken"): - conn.getToken() +# Module-level ``embedding_store`` is kept for back-compat with direct +# importers (`from common.config import embedding_store`); it's +# populated by the background-init thread below. New callers should +# prefer the ``get_embedding_store(timeout)`` getter so they fail +# fast (or wait briefly) instead of seeing a ``None`` mid-startup. +embedding_store = None +_embedding_store_ready = threading.Event() +service_status["embedding_store"] = { + "status": "initializing", + "error": "Embedding store is still initializing", +} - embedding_store = TigerGraphEmbeddingStore( - conn, - embedding_service, - support_ai_instance=True, - ) - service_status["embedding_store"] = {"status": "ok", "error": None} + +def _init_embedding_store(): + """Background thread target. Builds the embedding store without + blocking module import — TigerGraph may be slow on first connect, + and we don't want app startup to wait on it. + """ + global embedding_store + try: + conn = TigerGraphConnection( + host=db_config.get("hostname", "http://tigergraph"), + username=db_config.get("username", "tigergraph"), + password=db_config.get("password", "tigergraph"), + gsPort=db_config.get("gsPort", "14240"), + restppPort=db_config.get("restppPort", "9000"), + graphname=db_config.get("graphname", ""), + apiToken=db_config.get("apiToken", ""), + ) + if not db_config.get("apiToken") and db_config.get("getToken"): + conn.getToken() + + embedding_store = TigerGraphEmbeddingStore( + conn, + embedding_service, + support_ai_instance=True, + ) + service_status["embedding_store"] = {"status": "ok", "error": None} + except Exception as e: + service_status["embedding_store"] = {"status": "error", "error": str(e)} + logger.error(f"Failed to initialize embedding store: {e}") + finally: + _embedding_store_ready.set() + + +def get_embedding_store(timeout: float = 0): + """Return the embedding store if ready. + + Args: + timeout: Seconds to wait for initialization. Default 0 + (non-blocking — raises immediately if still initializing). + + Raises: + RuntimeError: if not yet ready, timed out, or initialization failed. + """ + if not _embedding_store_ready.wait(timeout=timeout): + raise RuntimeError( + "Embedding store is still initializing. Please try again shortly." + ) + if embedding_store is None: + error = service_status.get("embedding_store", {}).get("error", "Unknown error") + raise RuntimeError(f"Embedding store failed to initialize: {error}") + return embedding_store + + +if os.getenv("INIT_EMBED_STORE", "true") == "true": + threading.Thread(target=_init_embedding_store, daemon=True).start() def reload_llm_config(new_llm_config: dict = None): @@ -550,6 +627,14 @@ def reload_llm_config(new_llm_config: dict = None): if svc_key in new_llm_config and "region_name" not in new_llm_config[svc_key]: new_llm_config[svc_key]["region_name"] = new_llm_config["region_name"] + # Inject top-level prompt_path into LLM-prompted service configs + # if missing. Embedding service is excluded — embedding models + # never load prompt files. + if "prompt_path" in new_llm_config: + for svc_key in ["completion_service", "multimodal_service", "chat_service"]: + if svc_key in new_llm_config and "prompt_path" not in new_llm_config[svc_key]: + new_llm_config[svc_key]["prompt_path"] = new_llm_config["prompt_path"] + new_completion_config = new_llm_config.get("completion_service") new_embedding_config = new_llm_config.get("embedding_service") diff --git a/common/db/schema_extraction.py b/common/db/schema_extraction.py new file mode 100644 index 0000000..b71794e --- /dev/null +++ b/common/db/schema_extraction.py @@ -0,0 +1,115 @@ +# Copyright (c) 2024-2026 TigerGraph, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +"""Schema-extraction over sample documents (Phase 1, sample-doc path). + +The endpoint accepts up to N representative documents, this module +turns them into a single concatenated markdown blob and asks the LLM +to emit ``VERTEX`` / ``DIRECTED EDGE`` / ``UNDIRECTED EDGE`` +statements (the same GSQL form the *paste* path accepts), so both +sources funnel through ``schema_utils.parse_gsql_schema``. + +Prompt loading is delegated to +``common.llm_services.base_llm.LLM_Model.schema_extraction_prompt`` — +the same per-graph-override → provider-default resolution used by every +other customizable prompt. The prompt itself lives at +``/schema_extraction.txt`` with a per-graph override at +``configs/graph_configs//prompts/schema_extraction.txt``. +""" + +from __future__ import annotations + +import logging +from typing import Iterable, List + +from langchain.prompts import PromptTemplate +from langchain_core.output_parsers import StrOutputParser + +from common.db.schema_utils import ( + GRAPHRAG_STRUCTURAL_EDGE_TYPES, + GRAPHRAG_STRUCTURAL_VERTEX_TYPES, + get_gsql_reserved_words, +) + +logger = logging.getLogger(__name__) + + +def _build_prompt(llm_service) -> PromptTemplate: + """Wrap *llm_service*'s ``schema_extraction_prompt`` text in a + ``PromptTemplate`` with the three required input variables. + """ + template_str = llm_service.schema_extraction_prompt + return PromptTemplate( + template=template_str, + input_variables=["samples", "structural_types", "tg_keywords"], + ) + + +def concatenate_samples( + samples: Iterable[dict], + max_chars: int, +) -> str: + """Concatenate sample-doc markdown into a single blob, with each + document preceded by an ``# `` heading. Truncates at + *max_chars* total characters; truncation is logged. + + *samples* is an iterable of ``{"doc_id": str, "content": str}`` + dicts (the same shape ``extract_text_from_file_with_images_as_docs`` + returns). + """ + parts: List[str] = [] + total = 0 + for s in samples: + doc_id = s.get("doc_id", "doc") + content = s.get("content", "") or "" + header = f"\n\n# {doc_id}\n\n" + budget = max_chars - total + if budget <= 0: + logger.warning("Sample doc budget exhausted; later files truncated.") + break + chunk = (header + content)[:budget] + parts.append(chunk) + total += len(chunk) + return "".join(parts).lstrip() + + +def extract_schema_gsql( + llm_service, + samples: Iterable[dict], + max_chars: int = 200_000, +) -> str: + """Run the schema-extraction prompt against *llm_service*. Returns + the raw GSQL string the model produced (caller passes it to + ``schema_utils.parse_gsql_schema``). + + *llm_service* must expose ``schema_extraction_prompt`` (from + :class:`common.llm_services.base_llm.LLM_Model`) and the standard + ``invoke_with_parser(prompt, parser, inputs, caller_name)`` entry + point. Per-graph prompt overrides are picked up automatically by + ``schema_extraction_prompt``'s resolution chain. + """ + prompt = _build_prompt(llm_service) + samples_blob = concatenate_samples(samples, max_chars=max_chars) + structural_types = ", ".join( + sorted(GRAPHRAG_STRUCTURAL_VERTEX_TYPES | GRAPHRAG_STRUCTURAL_EDGE_TYPES) + ) + tg_keywords = ", ".join(sorted(get_gsql_reserved_words())) + + raw = llm_service.invoke_with_parser( + prompt, + StrOutputParser(), + { + "samples": samples_blob, + "structural_types": structural_types, + "tg_keywords": tg_keywords, + }, + caller_name="schema_extraction", + ) + if isinstance(raw, str): + return raw.strip() + return str(raw).strip() diff --git a/common/db/schema_utils.py b/common/db/schema_utils.py new file mode 100644 index 0000000..ce4a782 --- /dev/null +++ b/common/db/schema_utils.py @@ -0,0 +1,1259 @@ +# Copyright (c) 2024-2026 TigerGraph, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +""" +Schema proposal and persistence for the schema-aware initialize_graph flow. + +A "schema proposal" is the user-supplied (or LLM-derived) **domain** schema +the graph should adopt at init time, expressed as a small Python dict: + + { + "vertices": [ + {"name": "Company", "description": "..."}, + {"name": "Report", "description": "..."}, + ... + ], + "edges": [ + {"name": "PUBLISHES", + "description": "...", + "pairs": [("Company", "Report"), ("Company", "Filing")]}, + ... + ], + "domain_label": "Corporate Governance", # optional + } + +This module provides: + +* :data:`GRAPHRAG_STRUCTURAL_VERTEX_TYPES` / :data:`GRAPHRAG_STRUCTURAL_EDGE_TYPES` + — the GraphRAG-internal types that the user must not redefine. +* :func:`parse_gsql_schema` — permissive scanner that turns pasted GSQL + (``ADD VERTEX/EDGE`` statements *or* ``gsql ls`` output) into a proposal. +* :func:`emit_add_statements` — produce a list of ``ADD VERTEX/EDGE`` / + ``ALTER EDGE … ADD PAIR`` statements that bring an existing graph schema + up to the proposal (compare-and-add only; never drop). +* :func:`emit_preview_gsql` — render the proposal as a self-contained GSQL + block for the UI's "Preview as GSQL" tab. + +The module is intentionally dependency-light (regex, dataclasses, stdlib +only) so it's unit-testable without spinning up TigerGraph or the LLM. +""" + +from __future__ import annotations + +import re +import time +import uuid +from dataclasses import dataclass, field +from typing import Iterable, List, Optional, Sequence, Set, Tuple + + +# ----------------------------------------------------------------------------- +# Structural type registry +# ----------------------------------------------------------------------------- + +#: GraphRAG-internal vertex types. The user must not propose these as domain +#: types; the permissive parser silently drops any line that names one of +#: these (case-insensitive match). +GRAPHRAG_STRUCTURAL_VERTEX_TYPES: frozenset = frozenset({ + "Document", + "DocumentChunk", + "Entity", + "EntityType", + "RelationshipType", + "Content", + "Community", + "Image", +}) + + +#: GraphRAG-internal edge types. The user must not propose these as domain +#: types either. ``reverse_*`` companions are derived from ``WITH +#: REVERSE_EDGE=…`` declarations and shouldn't be hand-written. +GRAPHRAG_STRUCTURAL_EDGE_TYPES: frozenset = frozenset({ + "HAS_CONTENT", + "IS_HEAD_OF", + "HAS_TAIL", + "CONTAINS_ENTITY", + "MENTIONS_RELATIONSHIP", + "MENTIONS_ENTITY_TYPE", + "IS_AFTER", + "HAS_CHILD", + "ENTITY_HAS_TYPE", + "RELATIONSHIP", + "ENTITY_LINKS_TO", + "IN_COMMUNITY", + "LINKS_TO", + "HAS_PARENT", + "HAS_IMAGE", + "REFERENCES_IMAGE", +}) + + +_GSQL_RESERVED_CACHE: Optional[frozenset] = None + + +def get_gsql_reserved_words() -> frozenset: + """Return the GSQL reserved-keyword set sourced from + ``pyTigerGraph.TigerGraphConnection.getReservedKeywords()``. + + Memoized at first call. ``pyTigerGraph`` is a hard dependency of + this codebase, so an import failure here is a real configuration + error and we let it propagate. + """ + global _GSQL_RESERVED_CACHE + if _GSQL_RESERVED_CACHE is None: + from pyTigerGraph import TigerGraphConnection + + words = TigerGraphConnection.getReservedKeywords() + _GSQL_RESERVED_CACHE = ( + words if isinstance(words, frozenset) else frozenset(words) + ) + return _GSQL_RESERVED_CACHE + + +def is_reserved_word(name: str) -> bool: + """Return True if *name* (case-insensitive) collides with a GSQL + reserved word per pyTigerGraph. Used by the permissive parser to + drop names that would error at schema-change time anyway. + """ + if not name: + return False + return name.upper() in get_gsql_reserved_words() + + +#: Network / transport-level failure markers that ``conn.gsql()`` +#: surfaces as a string return rather than an exception. +_GSQL_TRANSPORT_FAILURE_MARKERS: tuple = ( + "Response ended prematurely", + "Connection refused", + "Connection reset", + "Read timed out", + "Internal Server Error", +) + + +#: Server-reported failure markers that ``conn.gsql()`` includes in +#: its string output without raising. Maintained locally — the +#: pyTigerGraph private helper ``_wrap_gsql_result`` is documented as +#: in flux upstream, so we don't depend on it. Keep this list aligned +#: with upstream's ``_GSQL_ERROR_PATTERNS`` when it stabilizes. +_GSQL_SERVER_ERROR_MARKERS: tuple = ( + 'Encountered "', + "SEMANTIC ERROR", + "Syntax Error", + "Failed to create", + "does not exist", + "is not a valid", + "already exists", + "Invalid syntax", +) + + +def gsql_output_error(output: str) -> Optional[str]: + """Return a short error description if *output* (the string returned + by ``pyTigerGraph.TigerGraphConnection.gsql()``) indicates failure, + else ``None``. + + Two layers, both checked locally so we don't depend on + pyTigerGraph private helpers: + + 1. Transport-level errors (``Response ended prematurely``, + ``Connection refused``, etc.) — pyTigerGraph surfaces these as + a string return rather than an exception. + 2. Server-reported errors (``SEMANTIC ERROR``, ``Failed to + create``, ``Invalid syntax``, etc.) — string markers in the + gsql output. + + Used by :func:`apply_proposal` to flip an "applied" return into + an error when the server reported a problem but pyTigerGraph + didn't raise. + """ + if not output: + return None + + folded = output.casefold() + for marker in _GSQL_TRANSPORT_FAILURE_MARKERS: + if marker.casefold() in folded: + idx = output.lower().find(marker.lower()) + snippet = output[max(0, idx - 40): idx + len(marker) + 200] + return f"GSQL transport error: {marker!r}. Excerpt: {snippet!r}" + + for marker in _GSQL_SERVER_ERROR_MARKERS: + if marker in output: + idx = output.find(marker) + snippet = output[max(0, idx - 40): idx + len(marker) + 200] + return f"GSQL server error: {snippet!r}" + + return None + + +def is_structural_type(name: str) -> bool: + """Return True if *name* (case-insensitive) is a GraphRAG structural + vertex or edge type, OR a ``reverse_*`` companion of one, OR a GSQL + reserved word that would fail at schema-change time. + """ + if not name: + return False + folded = name.casefold() + if folded.startswith("reverse_"): + return True + structural = {t.casefold() for t in GRAPHRAG_STRUCTURAL_VERTEX_TYPES} + structural |= {t.casefold() for t in GRAPHRAG_STRUCTURAL_EDGE_TYPES} + if folded in structural: + return True + return is_reserved_word(name) + + +# ----------------------------------------------------------------------------- +# Canonical proposal dataclass +# ----------------------------------------------------------------------------- + + +#: TigerGraph GSQL primitive attribute types we accept on proposals. +#: Anything else is dropped at parse time so the schema-change job +#: never receives a non-primitive type. +GSQL_PRIMITIVE_TYPES: frozenset = frozenset({ + "STRING", "INT", "UINT", "DOUBLE", "FLOAT", "BOOL", "DATETIME", +}) + + +@dataclass +class AttributeProposal: + """One ``(name, type)`` pair on a vertex or edge type.""" + + name: str + type: str = "STRING" + + def to_dict(self) -> dict: + return {"name": self.name, "type": self.type} + + +@dataclass +class VertexProposal: + name: str + description: str = "" + attributes: List[AttributeProposal] = field(default_factory=list) + + def to_dict(self) -> dict: + return { + "name": self.name, + "description": self.description, + "attributes": [a.to_dict() for a in self.attributes], + } + + +@dataclass +class EdgeProposal: + name: str + pairs: List[Tuple[str, str]] = field(default_factory=list) + description: str = "" + attributes: List[AttributeProposal] = field(default_factory=list) + # ``True`` for ``DIRECTED EDGE`` (default), ``False`` for + # ``UNDIRECTED EDGE``. Captured from the parser; propagated to the + # emitter so the schema-change job uses the right keyword and + # WITH-clause shape (undirected edges have no REVERSE_EDGE). + directed: bool = True + + def to_dict(self) -> dict: + return { + "name": self.name, + "description": self.description, + "pairs": [list(p) for p in self.pairs], + "attributes": [a.to_dict() for a in self.attributes], + "directed": self.directed, + } + + +@dataclass +class SchemaProposal: + """Canonical in-memory representation of a domain schema proposal.""" + + vertices: List[VertexProposal] = field(default_factory=list) + edges: List[EdgeProposal] = field(default_factory=list) + domain_label: Optional[str] = None + + # --- Construction helpers ----------------------------------------------- + + def add_vertex( + self, + name: str, + description: str = "", + attributes: Optional[Iterable[Tuple[str, str]]] = None, + ) -> VertexProposal: + existing = self.find_vertex(name) + if existing is not None: + if description and not existing.description: + existing.description = description + if attributes: + self._merge_attrs(existing.attributes, attributes) + return existing + v = VertexProposal(name=name, description=description) + if attributes: + self._merge_attrs(v.attributes, attributes) + self.vertices.append(v) + return v + + def add_edge_pair( + self, + name: str, + from_vt: str, + to_vt: str, + description: str = "", + attributes: Optional[Iterable[Tuple[str, str]]] = None, + directed: bool = True, + ) -> EdgeProposal: + existing = self.find_edge(name) + pair = (from_vt, to_vt) + if existing is None: + existing = EdgeProposal( + name=name, + pairs=[pair], + description=description, + directed=directed, + ) + if attributes: + self._merge_attrs(existing.attributes, attributes) + self.edges.append(existing) + else: + if pair not in existing.pairs: + existing.pairs.append(pair) + if description and not existing.description: + existing.description = description + if attributes: + self._merge_attrs(existing.attributes, attributes) + # If the same edge name appears twice with mismatched + # direction, prefer the first declaration's choice and + # log nothing — schema-change time will reject anyway. + return existing + + @staticmethod + def _merge_attrs( + target: List[AttributeProposal], + new_attrs: Iterable[Tuple[str, str]], + ) -> None: + """Merge new ``(name, type)`` tuples into *target*. Ignores + attributes whose name is already present (case-insensitive), + so the first declared type wins. Filters out attributes whose + type isn't a recognized GSQL primitive — those would error at + schema-change time, and we drop silently to keep the parser + permissive. + """ + existing_names = {a.name.casefold() for a in target} + for name, type_str in new_attrs: + if not name: + continue + if name.casefold() in existing_names: + continue + if type_str.upper() not in GSQL_PRIMITIVE_TYPES: + continue + target.append(AttributeProposal(name=name, type=type_str.upper())) + existing_names.add(name.casefold()) + + # --- Lookup helpers ----------------------------------------------------- + + def find_vertex(self, name: str) -> Optional[VertexProposal]: + folded = name.casefold() + return next( + (v for v in self.vertices if v.name.casefold() == folded), None + ) + + def find_edge(self, name: str) -> Optional[EdgeProposal]: + folded = name.casefold() + return next( + (e for e in self.edges if e.name.casefold() == folded), None + ) + + def vertex_names(self) -> Set[str]: + return {v.name for v in self.vertices} + + # --- Cleanup ------------------------------------------------------------ + + def drop_dangling_pairs(self) -> int: + """Remove ``(FROM, TO)`` pairs whose endpoints aren't in the + proposal's vertex set. Returns the number of pairs dropped. + Edges whose pair list becomes empty are removed entirely. + """ + names = {v.name for v in self.vertices} + names_folded = {n.casefold() for n in names} + dropped = 0 + kept_edges: List[EdgeProposal] = [] + for edge in self.edges: + kept_pairs: List[Tuple[str, str]] = [] + for src, tgt in edge.pairs: + if ( + src.casefold() in names_folded + and tgt.casefold() in names_folded + ): + kept_pairs.append((src, tgt)) + else: + dropped += 1 + if kept_pairs: + edge.pairs = kept_pairs + kept_edges.append(edge) + else: + dropped += 0 # whole edge dropped, not counted as a pair-drop + self.edges = kept_edges + return dropped + + # --- Serialization ------------------------------------------------------ + + def to_dict(self) -> dict: + out: dict = { + "vertices": [v.to_dict() for v in self.vertices], + "edges": [e.to_dict() for e in self.edges], + } + if self.domain_label: + out["domain_label"] = self.domain_label + return out + + @classmethod + def from_dict(cls, data: dict) -> "SchemaProposal": + prop = cls(domain_label=data.get("domain_label")) + for v in data.get("vertices", []) or []: + attrs = [ + (a.get("name", ""), a.get("type", "STRING")) + for a in v.get("attributes", []) or [] + ] + prop.add_vertex( + name=v["name"], + description=v.get("description", ""), + attributes=attrs, + ) + for e in data.get("edges", []) or []: + attrs = [ + (a.get("name", ""), a.get("type", "STRING")) + for a in e.get("attributes", []) or [] + ] + edge_directed = bool(e.get("directed", True)) + for pair in e.get("pairs", []) or []: + prop.add_edge_pair( + name=e["name"], + from_vt=pair[0], + to_vt=pair[1], + description=e.get("description", ""), + attributes=attrs, + directed=edge_directed, + ) + return prop + + +# ----------------------------------------------------------------------------- +# Permissive GSQL parser +# ----------------------------------------------------------------------------- + + +# A line that contains "VERTEX (...)" anywhere on it. +# Captures the name and (optionally) the parenthesized attribute list. +# Allows leading whitespace, optional dash, optional ADD prefix. +_VERTEX_LINE_RE = re.compile( + r""" + ^ # start of line (re.MULTILINE) + [\s\-]* # leading whitespace, optional dash + (?:add\s+)? # optional ADD + vertex # VERTEX + \s+ + (?P[A-Za-z_][A-Za-z0-9_]*) # type name + \s* + \( # opening paren of attribute list + (?P[^()]*) # attribute body (no nested parens) + \) # closing paren + """, + re.IGNORECASE | re.VERBOSE | re.MULTILINE | re.DOTALL, +) + + +# A line that contains "DIRECTED EDGE (...)" or +# "UNDIRECTED EDGE (...)". Captures the direction keyword (so +# the parser can preserve it on the proposal), the edge name, and the +# FROM/TO body. Attribute / WITH-clause text after the closing paren +# is intentionally not captured. +_EDGE_LINE_RE = re.compile( + r""" + ^ # start of line + [\s\-]* # leading whitespace, optional dash + (?:add\s+)? # optional ADD + (?Pdirected|undirected) # DIRECTED or UNDIRECTED + \s+edge + \s+ + (?P[A-Za-z_][A-Za-z0-9_]*) # edge name + \s* + \( # opening paren + (?P.*?) # FROM/TO body (non-greedy) + \) # closing paren + """, + re.IGNORECASE | re.VERBOSE | re.MULTILINE | re.DOTALL, +) + + +# Within an edge body, a single (FROM , TO ) clause. Multi-pair +# bodies are separated by `|`. +_EDGE_PAIR_RE = re.compile( + r""" + \bfrom\s+ + (?P[A-Za-z_][A-Za-z0-9_]*) + \s*,\s* + \bto\s+ + (?P[A-Za-z_][A-Za-z0-9_]*) + """, + re.IGNORECASE | re.VERBOSE, +) + + +# A single ``name `` token in an attribute body. +_ATTR_TOKEN_RE = re.compile( + r""" + \b + (?P[A-Za-z_][A-Za-z0-9_]*) + \s+ + (?PSTRING|INT|UINT|DOUBLE|FLOAT|BOOL|DATETIME) + \b + """, + re.IGNORECASE | re.VERBOSE, +) + + +# Strip ``PRIMARY_ID `` so the attribute +# scanner doesn't collect the id field. The system always auto-adds +# ``PRIMARY_ID id STRING``; user-supplied values are honored only if +# they appear as the literal PRIMARY_ID — otherwise they're treated +# as plain attributes. +_PRIMARY_ID_RE = re.compile( + r"\bPRIMARY_ID\b\s+[A-Za-z_][A-Za-z0-9_]*\s+(?:STRING|INT|UINT|DOUBLE|FLOAT|BOOL|DATETIME)", + re.IGNORECASE, +) + + +# Strip a ``FROM , TO `` clause so the attribute scanner doesn't +# accidentally pick up "FROM" / "TO" tokens or their vertex-type +# placeholders. Used when scanning edge attribute bodies. +_FROM_TO_CLAUSE_RE = re.compile( + r"\bfrom\s+[A-Za-z_][A-Za-z0-9_]*\s*,\s*to\s+[A-Za-z_][A-Za-z0-9_]*", + re.IGNORECASE, +) + + +def _extract_attributes(body: str, *, is_edge_body: bool) -> List[Tuple[str, str]]: + """Scan an attribute body and return ``(name, type)`` pairs that + look like primitive attribute declarations. Skips ``PRIMARY_ID`` + entries (the system auto-adds those) and, for edge bodies, FROM/TO + pair clauses. + """ + if not body: + return [] + cleaned = _PRIMARY_ID_RE.sub("", body) + if is_edge_body: + cleaned = _FROM_TO_CLAUSE_RE.sub("", cleaned) + seen: Set[str] = set() + out: List[Tuple[str, str]] = [] + for m in _ATTR_TOKEN_RE.finditer(cleaned): + name = m.group("name") + type_str = m.group("type").upper() + folded = name.casefold() + if folded in seen: + continue + # Skip GSQL keywords that may slip through (FROM/TO already + # stripped, but be defensive against other reserved tokens). + if folded in {"from", "to", "primary_id"}: + continue + seen.add(folded) + out.append((name, type_str)) + return out + + +# Matches a comment block: +# * one or more `// ...` lines, or +# * a single `/* ... */` block. +# Used to find descriptions immediately preceding a VERTEX/EDGE line. +_COMMENT_BLOCK_RE = re.compile( + r""" + (?: + (?:^[ \t]*//[ \t]?(?P.*)$\n?)+ # one+ // lines + | + ^[ \t]*/\*(?P.*?)\*/[ \t]*\n # /* … */ block + ) + """, + re.MULTILINE | re.DOTALL | re.VERBOSE, +) + + +def _extract_description_for(text: str, decl_start: int) -> str: + """Return the comment block's text immediately preceding *decl_start* + in *text*, or an empty string if none is present. + + A comment block is one or more consecutive ``//`` line comments, or a + single ``/* … */`` block, separated from the declaration by at most + blank lines. + """ + # Walk backwards from decl_start over blank/whitespace-only lines. + cursor = decl_start + # Skip any whitespace immediately before the decl + while cursor > 0 and text[cursor - 1] in (" ", "\t"): + cursor -= 1 + # Walk back one or more blank lines + while cursor > 0 and text[cursor - 1] == "\n": + # Look at the line before this newline + prev_line_end = cursor - 1 + prev_line_start = text.rfind("\n", 0, prev_line_end) + 1 + prev_line = text[prev_line_start:prev_line_end] + if prev_line.strip() == "": + cursor = prev_line_start + continue + break + + # Now cursor points at the start of the line that's potentially a comment. + # Walk back over consecutive `//` lines collecting their bodies. + comment_lines: List[str] = [] + while cursor > 0: + line_end = cursor - 1 # newline before cursor + line_start = text.rfind("\n", 0, line_end) + 1 + line = text[line_start:line_end] + stripped = line.lstrip() + if stripped.startswith("//"): + comment_lines.insert(0, stripped[2:].lstrip()) + cursor = line_start + elif stripped.startswith("/*") or stripped.endswith("*/"): + # Try a /* … */ block ending on this line + block_end = text.rfind("*/", 0, cursor) + block_start = text.rfind("/*", 0, block_end) + if block_start == -1 or block_end == -1: + break + body = text[block_start + 2:block_end] + # Strip leading * on each line (typical /* * … */ style) + cleaned = re.sub(r"^\s*\*?\s?", "", body, flags=re.MULTILINE) + comment_lines.insert(0, cleaned.strip()) + break + else: + break + + return " ".join(s.strip() for s in comment_lines if s.strip()) + + +def parse_gsql_schema(text: str) -> SchemaProposal: + """Permissively scan *text* for ``VERTEX`` / ``DIRECTED EDGE`` + declarations and return a :class:`SchemaProposal`. + + The scanner ignores everything that doesn't match the two declaration + patterns: section headers (``Vertex Types:``, ``Edge Types:``), + ``Indexes:``, ``Queries:`` blocks, ``CREATE GRAPH`` / + ``INSTALL QUERY`` / ``ALTER`` lines, blank lines, etc. ``ADD`` + prefix and the ``- `` bullet from ``gsql ls`` output are both + accepted; ``;`` terminators are tolerated. + + Lines naming a structural type (case-insensitive) are silently dropped. + ``reverse_*`` edges (auto-generated by ``WITH REVERSE_EDGE=…``) are + silently dropped. ``(FROM, TO)`` pairs whose endpoints don't resolve + to a vertex extracted from the same payload are dropped after parsing + (see :meth:`SchemaProposal.drop_dangling_pairs`). + """ + proposal = SchemaProposal() + + # Pass 1: vertices + for m in _VERTEX_LINE_RE.finditer(text): + name = m.group("name") + if is_structural_type(name): + continue + desc = _extract_description_for(text, m.start()) + attrs = _extract_attributes(m.group("body") or "", is_edge_body=False) + proposal.add_vertex(name=name, description=desc, attributes=attrs) + + # Pass 2: edges + for m in _EDGE_LINE_RE.finditer(text): + name = m.group("name") + if is_structural_type(name): + continue + if name.lower().startswith("reverse_"): + continue + body = m.group("body") or "" + desc = _extract_description_for(text, m.start()) + attrs = _extract_attributes(body, is_edge_body=True) + directed = (m.group("dir") or "directed").lower() == "directed" + for pm in _EDGE_PAIR_RE.finditer(body): + from_vt = pm.group("from") + to_vt = pm.group("to") + if is_structural_type(from_vt) and is_structural_type(to_vt): + # Both endpoints are structural — definitely not a domain + # edge pair the user is trying to add. Drop it. + continue + proposal.add_edge_pair( + name=name, + from_vt=from_vt, + to_vt=to_vt, + description=desc, + attributes=attrs, + directed=directed, + ) + + # Filter dangling pairs (FROM/TO that don't resolve to a vertex we + # actually extracted from the same payload). + proposal.drop_dangling_pairs() + return proposal + + +# ----------------------------------------------------------------------------- +# GSQL emission +# ----------------------------------------------------------------------------- + + +@dataclass +class ExistingSchema: + """Snapshot of what's already on the graph, used by the diff emitter. + + ``vertex_types`` is the set of vertex-type names currently on the + graph (case-sensitive). ``edge_pairs`` maps an edge type name to the + set of ``(FROM, TO)`` pairs already declared for that edge. + """ + + vertex_types: Set[str] = field(default_factory=set) + edge_pairs: dict = field(default_factory=dict) + + def has_vertex(self, name: str) -> bool: + folded = name.casefold() + return any(v.casefold() == folded for v in self.vertex_types) + + def has_edge(self, name: str) -> bool: + return name in self.edge_pairs or any( + k.casefold() == name.casefold() for k in self.edge_pairs + ) + + def has_edge_pair(self, name: str, from_vt: str, to_vt: str) -> bool: + # Edge name lookup is case-insensitive + edge_key = next( + (k for k in self.edge_pairs if k.casefold() == name.casefold()), + None, + ) + if edge_key is None: + return False + for src, tgt in self.edge_pairs.get(edge_key, set()): + if src.casefold() == from_vt.casefold() and tgt.casefold() == to_vt.casefold(): + return True + return False + + +def emit_add_statements( + proposal: SchemaProposal, + existing: Optional[ExistingSchema] = None, +) -> List[str]: + """Diff *proposal* against *existing* and return a list of GSQL + statements (sans trailing ``;``) that, when run inside a + ``SCHEMA_CHANGE JOB`` against a graph in the *existing* state, bring + the graph up to the proposal. + + Order is deterministic and dependency-safe: + + 1. ``ADD VERTEX (PRIMARY_ID id STRING) WITH PRIMARY_ID_AS_ATTRIBUTE="true"`` + for every domain vertex type that doesn't already exist. + 2. ``ADD DIRECTED EDGE (FROM , TO [| FROM …]) WITH REVERSE_EDGE="reverse_"`` + for every domain edge type that doesn't exist on the graph at all. + 3. ``ALTER EDGE ADD PAIR (FROM , TO )`` for every + ``(FROM, TO)`` pair on an existing edge type that's missing. + + No ``DROP``s are ever emitted — the diff is strictly additive. + """ + if existing is None: + existing = ExistingSchema() + + stmts: List[str] = [] + + # 1. New vertex types + for v in proposal.vertices: + if existing.has_vertex(v.name): + continue + attrs_part = "" + if v.attributes: + attrs_part = ", " + ", ".join( + f"{a.name} {a.type}" for a in v.attributes + ) + stmts.append( + f'ADD VERTEX {v.name} (PRIMARY_ID id STRING{attrs_part}) ' + f'WITH PRIMARY_ID_AS_ATTRIBUTE="true"' + ) + + # 2 + 3. Edges: fully new, or new pairs on an existing edge + for e in proposal.edges: + if not e.pairs: + continue + if not existing.has_edge(e.name): + pairs_str = " | ".join( + f"FROM {src}, TO {tgt}" for src, tgt in e.pairs + ) + attrs_part = "" + if e.attributes: + attrs_part = ", " + ", ".join( + f"{a.name} {a.type}" for a in e.attributes + ) + edge_kw = "DIRECTED EDGE" if e.directed else "UNDIRECTED EDGE" + # Undirected edges have no reverse companion, so omit the + # WITH REVERSE_EDGE clause. + with_clause = ( + f' WITH REVERSE_EDGE="reverse_{e.name}"' if e.directed else "" + ) + stmts.append( + f'ADD {edge_kw} {e.name} ({pairs_str}{attrs_part}){with_clause}' + ) + else: + # Existing edge: only ALTER ADD PAIR is supported. Attributes + # of an existing edge can't be added at the same time; that's + # a separate ALTER ATTRIBUTE statement and is out of scope + # for the additive Phase 1 diff emitter. + for src, tgt in e.pairs: + if existing.has_edge_pair(e.name, src, tgt): + continue + stmts.append( + f"ALTER EDGE {e.name} ADD PAIR (FROM {src}, TO {tgt})" + ) + + return stmts + + +def emit_structural_link_alters( + proposal: SchemaProposal, + existing: Optional[ExistingSchema] = None, +) -> List[str]: + """For every domain vertex in *proposal*, emit ``ALTER EDGE … ADD + PAIR`` statements that connect it to the GraphRAG core via the + structural edges: + + * ``CONTAINS_ENTITY`` — ``Document`` / ``DocumentChunk`` → domain vertex + + The typed-relationship pattern (``IS_HEAD_OF`` / ``HAS_TAIL``) lives + at the meta-schema layer (``EntityType`` ↔ ``RelationshipType``) and + does NOT need per-domain-vertex pairs. The original schema + declaration covers the only pairs we ever traverse. + + Pairs already on the graph (per *existing*) are skipped. The + statements are returned in a deterministic order so the schema + diff is reproducible. + """ + if existing is None: + existing = ExistingSchema() + + # Skip the structural-link emit entirely when the GraphRAG core + # types aren't on the graph — without them the ALTER would + # reference an undeclared endpoint and fail. In production these + # are always present by the time apply_proposal runs (init_supportai + # creates the structural schema first), but unit tests and + # bare-graph fixtures may not have them. + has_doc = existing.has_vertex("Document") + has_chunk = existing.has_vertex("DocumentChunk") + + stmts: List[str] = [] + for v in proposal.vertices: + # CONTAINS_ENTITY: Document / DocumentChunk → + if has_doc and not existing.has_edge_pair("CONTAINS_ENTITY", "Document", v.name): + stmts.append( + f"ALTER EDGE CONTAINS_ENTITY ADD PAIR (FROM Document, TO {v.name})" + ) + if has_chunk and not existing.has_edge_pair("CONTAINS_ENTITY", "DocumentChunk", v.name): + stmts.append( + f"ALTER EDGE CONTAINS_ENTITY ADD PAIR (FROM DocumentChunk, TO {v.name})" + ) + return stmts + + +def emit_preview_gsql(proposal: SchemaProposal) -> str: + """Render *proposal* as a self-contained GSQL block suitable for the + UI's "Preview as GSQL" tab. Comments above each declaration carry + the description, when set. + """ + lines: List[str] = [] + if proposal.domain_label: + lines.append(f"// Domain: {proposal.domain_label}") + lines.append("") + + for v in proposal.vertices: + if v.description: + lines.append(f"// {v.description}") + attrs_part = "" + if v.attributes: + attrs_part = ", " + ", ".join( + f"{a.name} {a.type}" for a in v.attributes + ) + lines.append( + f'ADD VERTEX {v.name} (PRIMARY_ID id STRING{attrs_part}) ' + f'WITH PRIMARY_ID_AS_ATTRIBUTE="true";' + ) + lines.append("") + + for e in proposal.edges: + if not e.pairs: + continue + if e.description: + lines.append(f"// {e.description}") + pairs_str = " | ".join(f"FROM {src}, TO {tgt}" for src, tgt in e.pairs) + attrs_part = "" + if e.attributes: + attrs_part = ", " + ", ".join( + f"{a.name} {a.type}" for a in e.attributes + ) + edge_kw = "DIRECTED EDGE" if e.directed else "UNDIRECTED EDGE" + with_clause = ( + f' WITH REVERSE_EDGE="reverse_{e.name}"' if e.directed else "" + ) + lines.append( + f'ADD {edge_kw} {e.name} ({pairs_str}{attrs_part}){with_clause};' + ) + lines.append("") + + return "\n".join(lines).rstrip() + "\n" + + +# ----------------------------------------------------------------------------- +# TigerGraph-side schema reader +# ----------------------------------------------------------------------------- + + +def read_existing_schema(conn) -> ExistingSchema: + """Read the current vertex / edge schema from a TigerGraph + connection and return an :class:`ExistingSchema` snapshot suitable + for :func:`emit_add_statements`. + + Works with both ``pyTigerGraph.TigerGraphConnection`` and our + ``TigerGraphConnectionProxy`` wrapper. Only the synchronous + ``getVertexTypes`` / ``getEdgeTypes`` / ``getEdgeType`` API is used. + + Edge pairs are extracted from the edge-type metadata returned by + pyTigerGraph. For single-pair edges the metadata exposes + ``FromVertexTypeName`` / ``ToVertexTypeName`` directly. For + multi-pair edges (where those fields are ``"*"``) the metadata + contains an ``EdgePairs`` list of ``{"From": ..., "To": ...}`` + dicts. We accept both shapes. + + Errors during schema introspection are not swallowed — the caller + needs to know if the snapshot is incomplete before diffing. If the + graph hasn't been initialized at all (no vertex types yet), + pyTigerGraph returns an empty list, which produces an + ``ExistingSchema`` with empty ``vertex_types`` / ``edge_pairs`` + (the diff emitter then emits a full ``ADD`` for everything in the + proposal — which is the desired behavior on a fresh graph). + """ + snapshot = ExistingSchema() + + vertex_types = conn.getVertexTypes() or [] + snapshot.vertex_types = set(vertex_types) + + for et_name in conn.getEdgeTypes() or []: + meta = conn.getEdgeType(et_name) or {} + pairs: Set[Tuple[str, str]] = set() + + from_v = meta.get("FromVertexTypeName") + to_v = meta.get("ToVertexTypeName") + if from_v and to_v and from_v != "*" and to_v != "*": + pairs.add((from_v, to_v)) + + # Multi-pair edges: an EdgePairs list either always (some TG + # versions) or only when From/To are "*" (other versions). + for ep in meta.get("EdgePairs", []) or []: + f = ep.get("From") + t = ep.get("To") + if f and t: + pairs.add((f, t)) + + if pairs: + snapshot.edge_pairs[et_name] = pairs + + return snapshot + + +# ----------------------------------------------------------------------------- +# Atomic apply +# ----------------------------------------------------------------------------- + + +def build_schema_change_job( + graphname: str, + statements: Sequence[str], + job_name: Optional[str] = None, +) -> Tuple[str, str]: + """Wrap *statements* into a single ``CREATE SCHEMA_CHANGE JOB`` / + ``RUN`` / ``DROP`` GSQL block for *graphname*. + + Returns ``(gsql_block, job_name)``. The job name is generated with a + short uuid suffix so re-runs against the same graph don't collide + with a previously-created (but never dropped) job. + + The returned block is intended to be passed verbatim to + ``conn.gsql(...)``; running every ``ADD`` / ``ALTER`` inside one job + is what makes the application atomic. + """ + if not statements: + raise ValueError("build_schema_change_job: statements is empty") + if job_name is None: + job_name = f"add_domain_schema_{uuid.uuid4().hex[:8]}" + + body = ";\n ".join(s.rstrip(";") for s in statements) + ";" + block = ( + f"USE GRAPH {graphname}\n" + f"CREATE SCHEMA_CHANGE JOB {job_name} FOR GRAPH {graphname} {{\n" + f" {body}\n" + f"}}\n" + f"RUN SCHEMA_CHANGE JOB {job_name}\n" + f"DROP JOB {job_name}" + ) + return block, job_name + + +def read_type_metadata(conn) -> Tuple[dict, dict]: + """Read every ``EntityType`` / ``RelationshipType`` vertex from + *conn* and return two dicts: + + ( + {entity_type_id: description}, + {relationship_type_id: definition}, + ) + + Empty / missing values are dropped so callers can ``.get(name, "")`` + without distinguishing "no row" from "row with empty description". + Errors propagate — callers needing best-effort behavior should wrap + in their own try/except. + """ + entity_descs: dict = {} + rel_defs: dict = {} + + try: + rows = conn.getVertices("EntityType") or [] + except Exception: + rows = [] + for row in rows: + attrs = row.get("attributes", row) + v_id = row.get("v_id") or attrs.get("id") + desc = (attrs.get("description") or "").strip() + if v_id and desc: + entity_descs[v_id] = desc + + try: + rows = conn.getVertices("RelationshipType") or [] + except Exception: + rows = [] + for row in rows: + attrs = row.get("attributes", row) + v_id = row.get("v_id") or attrs.get("id") + defn = (attrs.get("definition") or "").strip() + if v_id and defn: + rel_defs[v_id] = defn + + return entity_descs, rel_defs + + +async def read_existing_schema_async(conn) -> "ExistingSchema": + """Async counterpart to :func:`read_existing_schema` — used by the + ECC pipeline where ``conn`` is an ``AsyncTigerGraphConnection``. + + Returns a raw :class:`ExistingSchema` snapshot. Callers that want + to filter structural types (e.g. for domain-only consumers like + the extractor builder) should use :func:`is_structural_type` on + the returned vertex / edge names — this helper deliberately does + not couple the live-schema read to the proposal-time concept of + "domain vs structural", so live-schema consumers stay independent + of the proposal lifecycle. + """ + snapshot = ExistingSchema() + snapshot.vertex_types = set(await conn.getVertexTypes() or []) + for et_name in await conn.getEdgeTypes() or []: + meta = await conn.getEdgeType(et_name) or {} + pairs: Set[Tuple[str, str]] = set() + from_v = meta.get("FromVertexTypeName") + to_v = meta.get("ToVertexTypeName") + if from_v and to_v and from_v != "*" and to_v != "*": + pairs.add((from_v, to_v)) + for ep in meta.get("EdgePairs", []) or []: + f = ep.get("From") + t = ep.get("To") + if f and t: + pairs.add((f, t)) + if pairs: + snapshot.edge_pairs[et_name] = pairs + return snapshot + + +async def read_type_metadata_async(conn) -> Tuple[dict, dict]: + """Async counterpart to :func:`read_type_metadata` — used by the + ECC pipeline where the available connection is + ``pyTigerGraph.AsyncTigerGraphConnection``. + + Same return shape: ``({entity_id: description}, {rel_id: definition})``. + Errors propagate to the caller. + """ + entity_descs: dict = {} + rel_defs: dict = {} + + try: + rows = await conn.getVertices("EntityType") or [] + except Exception: + rows = [] + for row in rows: + attrs = row.get("attributes", row) + v_id = row.get("v_id") or attrs.get("id") + desc = (attrs.get("description") or "").strip() + if v_id and desc: + entity_descs[v_id] = desc + + try: + rows = await conn.getVertices("RelationshipType") or [] + except Exception: + rows = [] + for row in rows: + attrs = row.get("attributes", row) + v_id = row.get("v_id") or attrs.get("id") + defn = (attrs.get("definition") or "").strip() + if v_id and defn: + rel_defs[v_id] = defn + + return entity_descs, rel_defs + + +def _short_name(name: str) -> str: + """Lowercase, underscore-separated form of *name* — used as the + ``short_name`` attribute on ``RelationshipType`` vertices for display. + Trims to at most ~32 characters (the column has no length but display + is friendlier when short). + """ + folded = re.sub(r"[^A-Za-z0-9]+", "_", name).strip("_").lower() + return folded[:32] + + +def upsert_type_metadata( + conn, + proposal: SchemaProposal, +) -> dict: + """Upsert ``EntityType`` / ``RelationshipType`` vertices with the + descriptions from *proposal*. Does not touch existing rows whose + description / definition is already non-empty unless the proposal + carries a non-empty value of its own (callers may opt to override + by passing a description; we always pass through what the proposal + has). + + Returns ``{"entity_types": [...], "relationship_types": [...]}`` + listing the ids upserted. + """ + now = int(time.time()) + entity_ids: List[str] = [] + relationship_ids: List[str] = [] + + for v in proposal.vertices: + # EntityType schema: (id STRING, description STRING, epoch_added UINT) + attrs = {"epoch_added": now} + if v.description: + attrs["description"] = v.description + conn.upsertVertex("EntityType", v.name, attributes=attrs) + entity_ids.append(v.name) + + for e in proposal.edges: + # RelationshipType schema: + # (id STRING, definition STRING, short_name STRING, + # epoch_added UINT, epoch_processing UINT, epoch_processed UINT) + attrs = { + "epoch_added": now, + "short_name": _short_name(e.name), + } + if e.description: + attrs["definition"] = e.description + conn.upsertVertex("RelationshipType", e.name, attributes=attrs) + relationship_ids.append(e.name) + + return { + "entity_types": entity_ids, + "relationship_types": relationship_ids, + } + + +def apply_proposal( + conn, + graphname: str, + proposal: SchemaProposal, + job_name: Optional[str] = None, +) -> dict: + """Diff *proposal* against the current schema on *conn* and apply the + additive delta as a single atomic ``SCHEMA_CHANGE JOB``. + + Returns a result dict:: + + { + "status": "applied" | "no-op", + "statements": [...], # ADD/ALTER statements that were emitted + "job_name": "", + "gsql_output": "", + "summary": {...}, # summarize(proposal) + } + + Schema introspection errors propagate; the caller decides whether the + overall init flow should be marked as failed. The structural GraphRAG + schema must already exist on the graph (so the diff sees structural + types and only emits domain-side ADDs). + """ + existing = read_existing_schema(conn) + domain_stmts = emit_add_statements(proposal, existing) + # Run the structural-link emitter against an *augmented* snapshot + # so vertices we're about to ADD are treated as present — otherwise + # has_edge_pair would always say "missing" and we'd over-emit. + augmented = ExistingSchema( + vertex_types=set(existing.vertex_types) | {v.name for v in proposal.vertices}, + edge_pairs=dict(existing.edge_pairs), + ) + structural_stmts = emit_structural_link_alters(proposal, augmented) + statements = domain_stmts + structural_stmts + summary = summarize(proposal) + + if not statements: + # Even on no-op, refresh metadata so descriptions edited in the + # review panel land on EntityType / RelationshipType vertices. + metadata = upsert_type_metadata(conn, proposal) + return { + "status": "no-op", + "statements": [], + "job_name": None, + "gsql_output": "", + "summary": summary, + "metadata": metadata, + } + + block, job_name = build_schema_change_job(graphname, statements, job_name) + output = conn.gsql(block) + err = gsql_output_error(output) + if err: + # pyTigerGraph's gsql() returned a failure response without + # raising — surface it explicitly so the caller doesn't + # falsely report "applied". Skip metadata upsert (the schema + # change didn't land, so writing EntityType vertices for + # types that don't exist would also fail). + return { + "status": "error", + "statements": statements, + "job_name": job_name, + "gsql_output": output, + "error": err, + "summary": summary, + "metadata": {"entity_types": [], "relationship_types": []}, + } + metadata = upsert_type_metadata(conn, proposal) + return { + "status": "applied", + "statements": statements, + "job_name": job_name, + "gsql_output": output, + "summary": summary, + "metadata": metadata, + } + + +# ----------------------------------------------------------------------------- +# Validation summary (informational, never blocking) +# ----------------------------------------------------------------------------- + + +def summarize(proposal: SchemaProposal) -> dict: + """Return a small descriptive payload for logging / API responses + (counts and lists of names). Never raises. + """ + return { + "vertex_count": len(proposal.vertices), + "edge_count": len(proposal.edges), + "vertex_names": [v.name for v in proposal.vertices], + "edge_names": [e.name for e in proposal.edges], + "edge_pair_count": sum(len(e.pairs) for e in proposal.edges), + "domain_label": proposal.domain_label, + } diff --git a/common/embeddings/tigergraph_embedding_store.py b/common/embeddings/tigergraph_embedding_store.py index 748f166..e4758b8 100644 --- a/common/embeddings/tigergraph_embedding_store.py +++ b/common/embeddings/tigergraph_embedding_store.py @@ -69,11 +69,7 @@ def __init__( tg_version = self.conn.getVer() ver = tg_version.split(".") if int(ver[0]) >= 4 and int(ver[1]) >= 2: - logger.info(f"Installing GDS library") - q_res = self.conn.gsql( - """USE GLOBAL\nimport package gds\ninstall function gds.**""" - ) - logger.info(f"Done installing GDS library with status {q_res}") + self._ensure_gds_installed() if self.conn.graphname and not self.conn.graphname == "MyGraph": current_schema = self.conn.gsql(f"USE GRAPH {self.conn.graphname}\n ls") if "(Dimension=" in current_schema: @@ -82,6 +78,47 @@ def __init__( else: raise Exception(f"Current TigerGraph version {ver} does not support vector feature!") + def _ensure_gds_installed(self) -> None: + """Install the gds package only if the gds.vector sub-package + isn't already present. + + Probes via ``SHOW PACKAGE gds`` whose output looks like + ``Packages "gds":\\n - Sub-Packages:\\n - vector\\n`` when + the sub-package is installed (verified empirically). Checking + the sub-package (rather than just the top-level ``gds``) also + catches a partial-install state where ``gds`` is present but + ``gds.vector`` isn't. + + The probe is a fast catalog read (~60ms) compared to + ``install function gds.**`` which takes a global catalog + write lock for the duration of the install scan (~3 minutes + against a remote TG). Skipping the install when the package + is present avoids that lock on every container restart. + + Falls through to the install on any probe failure — better to + occasionally re-install than to skip an install that's + actually missing. + """ + try: + sub_packages = self.conn.gsql("SHOW PACKAGE gds") + except Exception as exc: # noqa: BLE001 — defensive + logger.warning( + f"GDS-presence probe failed: {exc}. Falling through to install." + ) + sub_packages = "" + # The expected installed output is: + # 'Packages "gds":\n - Sub-Packages:\n - vector\n' + # When gds isn't installed at all, ``SHOW PACKAGE gds`` returns + # an error message instead, so this check fails closed. + if "- vector" in sub_packages: + logger.info("GDS library already installed; skipping install.") + return + logger.info("Installing GDS library") + q_res = self.conn.gsql( + """USE GLOBAL\nimport package gds\ninstall function gds.**""" + ) + logger.info(f"Done installing GDS library with status {q_res}") + def install_vector_queries(self): logger.info(f"Installing vector queries") vector_queries = [ @@ -121,6 +158,12 @@ def set_graphname(self, graphname): self.vector_attr_cache = {} if self.conn.apiToken or self.conn.jwtToken: self.conn.getToken() + # Re-verify GDS presence on every graphname switch. Cheap when + # the package is already installed (one LS call) and recovers + # from a mid-flight DROP ALL or admin-side wipe before the + # per-graph vector-query install below tries to reference + # missing gds.vector.* UDFs. + self._ensure_gds_installed() if self.conn.graphname and not self.conn.graphname == "MyGraph": current_schema = self.conn.gsql(f"USE GRAPH {self.conn.graphname}\n ls") if "(Dimension=" in current_schema: diff --git a/common/extractors/LLMEntityRelationshipExtractor.py b/common/extractors/LLMEntityRelationshipExtractor.py index dec1753..877197b 100644 --- a/common/extractors/LLMEntityRelationshipExtractor.py +++ b/common/extractors/LLMEntityRelationshipExtractor.py @@ -32,11 +32,112 @@ def __init__( allowed_entity_types: List[str] = None, allowed_relationship_types: List[str] = None, strict_mode: bool = False, + entity_type_definitions: dict = None, + relationship_type_definitions: dict = None, + domain_edge_endpoints: dict = None, ): self.llm_service = llm_service self.allowed_vertex_types = allowed_entity_types self.allowed_edge_types = allowed_relationship_types + # When True the existing parser filter (drop nodes/rels whose + # type isn't in the allowed list) is enforced AND the prompt + # tells the LLM to stay within the schema. Read from + # graphrag_config.strict_mode by the ECC builder. self.strict_mode = strict_mode + self.entity_type_definitions = dict(entity_type_definitions or {}) + self.relationship_type_definitions = dict( + relationship_type_definitions or {} + ) + # Per-edge ``{name: [(from_vt, to_vt), ...]}`` derived from the + # live schema. Used by the prompt to tell the LLM the valid + # source/target pairs per relationship type, and by the ingest + # worker to validate that an extracted relationship's endpoints + # match a declared pair before writing IS_HEAD_OF / HAS_TAIL. + self.domain_edge_endpoints = { + k: list(v) for k, v in (domain_edge_endpoints or {}).items() + } + + def _format_definitions(self, defs: dict) -> str: + """Render a ``{type_name: definition}`` dict as one + ``- : `` line per type, sorted by name. Used + when assembling the schema-aware extraction prompt. + """ + if not defs: + return "" + return "\n".join( + f"- {name}: {definition}" + for name, definition in sorted(defs.items()) + if definition + ) + + def _format_edge_endpoints(self) -> str: + """Render ``{edge_name: [(from, to), ...]}`` as + ``- : -> [, -> ]`` lines, sorted + by edge name. Empty when no endpoints are configured. + """ + if not self.domain_edge_endpoints: + return "" + lines = [] + for name, pairs in sorted(self.domain_edge_endpoints.items()): + pair_strs = ", ".join(f"{f} -> {t}" for f, t in pairs) or "" + defn = self.relationship_type_definitions.get(name, "") + tail = f" — {defn}" if defn else "" + lines.append(f"- {name}: {pair_strs}{tail}") + return "\n".join(lines) + + def _build_schema_prompt_messages(self) -> list: + """Return the human-message tuples that describe the domain + schema to the LLM. Used by both sync and async extraction paths. + Empty list when no schema is configured. + """ + msgs = [] + entity_def_block = self._format_definitions(self.entity_type_definitions) + rel_def_block = self._format_definitions(self.relationship_type_definitions) + endpoints_block = self._format_edge_endpoints() + if not (entity_def_block or rel_def_block or endpoints_block): + return msgs + + if self.strict_mode: + msgs.append(( + "human", + "STRICT SCHEMA MODE: only emit entities whose entity_type " + "matches one of the schema entity types listed below, and " + "only emit relationships whose relation_type matches a " + "schema relationship type AND whose source / target " + "entity types match a declared (FROM, TO) endpoint pair " + "for that relationship. Drop any entity or relationship " + "that doesn't fit. Do NOT invent new types.", + )) + else: + msgs.append(( + "human", + "When deciding the entity_type / relationship_type for an " + "extraction, strongly prefer the schema types listed below " + "and use their definitions to disambiguate similar types. " + "Ignore page-structure / chart / layout artifacts (axes, " + "segments, percentages, page numbers, sections, navigation " + "menus, captions). Prefer concrete real-world entities over " + "abstract categorical groupings. Only invent a new type " + "when nothing in the schema fits.", + )) + if entity_def_block: + msgs.append(( + "human", + f"Schema entity types with definitions:\n{entity_def_block}", + )) + if endpoints_block: + msgs.append(( + "human", + "Schema relationship types — each line lists the valid " + "(source -> target) endpoint pairs for that relationship " + "and the relationship's definition:\n" + endpoints_block, + )) + elif rel_def_block: + msgs.append(( + "human", + f"Schema relationship types with definitions:\n{rel_def_block}", + )) + return msgs def _parse_json_output(self, content: str) -> dict: """Parse JSON from LLM output with multiple fallback strategies. @@ -298,6 +399,7 @@ async def adocument_er_extraction(self, document): prompt.append(("human", f"Allowed Node Types: {self.allowed_vertex_types}")) if self.allowed_edge_types: prompt.append(("human", f"Allowed Edge Types: {self.allowed_edge_types}")) + prompt.extend(self._build_schema_prompt_messages()) prompt = ChatPromptTemplate.from_messages(prompt) chain = prompt | self.llm_service.llm # | parser er = await self._aextract_kg_from_doc(document, chain, parser) @@ -336,6 +438,7 @@ def document_er_extraction(self, document): prompt.append(("human", f"Allowed Node Types: {self.allowed_vertex_types}")) if self.allowed_edge_types: prompt.append(("human", f"Allowed Edge Types: {self.allowed_edge_types}")) + prompt.extend(self._build_schema_prompt_messages()) prompt = ChatPromptTemplate.from_messages(prompt) chain = prompt | self.llm_service.llm # | parser er = self._extract_kg_from_doc(document, chain, parser) diff --git a/common/gsql/supportai/SupportAI_Schema.gsql b/common/gsql/supportai/SupportAI_Schema.gsql index c756fd3..79aa865 100644 --- a/common/gsql/supportai/SupportAI_Schema.gsql +++ b/common/gsql/supportai/SupportAI_Schema.gsql @@ -22,14 +22,13 @@ CREATE SCHEMA_CHANGE JOB add_supportai_schema { ADD VERTEX Content(PRIMARY_ID id STRING, ctype STRING, text STRING, epoch_added UINT) WITH STATS="OUTDEGREE_BY_EDGETYPE", PRIMARY_ID_AS_ATTRIBUTE="true"; ADD VERTEX EntityType(PRIMARY_ID id STRING, description STRING, epoch_added UINT) WITH STATS="OUTDEGREE_BY_EDGETYPE", PRIMARY_ID_AS_ATTRIBUTE="true"; ADD DIRECTED EDGE HAS_CONTENT(FROM Document, TO Content|FROM DocumentChunk, TO Content) WITH REVERSE_EDGE="reverse_HAS_CONTENT"; - ADD DIRECTED EDGE IS_HEAD_OF(FROM Entity, TO RelationshipType) WITH REVERSE_EDGE="reverse_IS_HEAD_OF"; - ADD DIRECTED EDGE HAS_TAIL(FROM RelationshipType, TO Entity) WITH REVERSE_EDGE="reverse_HAS_TAIL"; + ADD DIRECTED EDGE IS_HEAD_OF(FROM EntityType, TO RelationshipType) WITH REVERSE_EDGE="reverse_IS_HEAD_OF"; + ADD DIRECTED EDGE HAS_TAIL(FROM RelationshipType, TO EntityType) WITH REVERSE_EDGE="reverse_HAS_TAIL"; ADD DIRECTED EDGE CONTAINS_ENTITY(FROM DocumentChunk, TO Entity|FROM Document, TO Entity) WITH REVERSE_EDGE="reverse_CONTAINS_ENTITY"; ADD DIRECTED EDGE MENTIONS_RELATIONSHIP(FROM DocumentChunk, TO RelationshipType|FROM Document, TO RelationshipType) WITH REVERSE_EDGE="reverse_MENTIONS_RELATIONSHIP"; ADD DIRECTED EDGE IS_AFTER(FROM DocumentChunk, TO DocumentChunk) WITH REVERSE_EDGE="reverse_IS_AFTER"; ADD DIRECTED EDGE HAS_CHILD(FROM Document, TO DocumentChunk) WITH REVERSE_EDGE="reverse_HAS_CHILD"; ADD DIRECTED EDGE ENTITY_HAS_TYPE(FROM Entity, TO EntityType) WITH REVERSE_EDGE="reverse_ENTITY_HAS_TYPE"; - ADD DIRECTED EDGE RELATIONSHIP_TYPE(FROM EntityType, TO EntityType, DISCRIMINATOR(relation_type STRING), frequency INT) WITH REVERSE_EDGE="reverse_RELATIONSHIP_TYPE"; // GraphRAG ADD VERTEX Community (PRIMARY_ID id STRING, iteration UINT, description STRING) WITH STATS="OUTDEGREE_BY_EDGETYPE", PRIMARY_ID_AS_ATTRIBUTE="true"; diff --git a/common/gsql/supportai/create_entity_type_relationships.gsql b/common/gsql/supportai/create_entity_type_relationships.gsql deleted file mode 100644 index 860f55c..0000000 --- a/common/gsql/supportai/create_entity_type_relationships.gsql +++ /dev/null @@ -1,20 +0,0 @@ -CREATE OR REPLACE DISTRIBUTED QUERY create_entity_type_relationships(/* Parameters here */) SYNTAX v2{ - MapAccum>> @rel_type_count; // entity type, relationship type for entity type, frequency - SumAccum @@rels_inserted; - ents = {Entity.*}; - accum_types = SELECT et FROM ents:e -(RELATIONSHIP>:r)- Entity:e2 -(ENTITY_HAS_TYPE>:eht)- EntityType:et - WHERE r.relation_type != "DOC_CHUNK_COOCCURRENCE" - ACCUM - e.@rel_type_count += (et.id -> (r.relation_type -> 1)); - - ets = SELECT et FROM ents:e -(ENTITY_HAS_TYPE>:eht)- EntityType:et - ACCUM - FOREACH (entity_type, rel_type_freq) IN e.@rel_type_count DO - FOREACH (rel_type, freq) IN e.@rel_type_count.get(entity_type) DO - INSERT INTO RELATIONSHIP_TYPE VALUES (et.id, entity_type, rel_type, freq), - @@rels_inserted += 1 - END - END; - - PRINT @@rels_inserted as relationships_inserted; -} \ No newline at end of file diff --git a/common/gsql/supportai/retrievers/Content_Similarity_Search.gsql b/common/gsql/supportai/retrievers/Content_Similarity_Search.gsql index ed917e0..6877c0a 100644 --- a/common/gsql/supportai/retrievers/Content_Similarity_Search.gsql +++ b/common/gsql/supportai/retrievers/Content_Similarity_Search.gsql @@ -44,6 +44,12 @@ CREATE OR REPLACE DISTRIBUTED QUERY Content_Similarity_Search(STRING json_list_v @@final_retrieval += (s.id -> s.definition) ELSE IF s.type == "Community" THEN @@final_retrieval += (s.id -> s.description) + ELSE IF s.type != "DocumentChunk" AND s.type != "Document" + AND s.type != "Content" AND s.type != "Image" + AND s.type != "EntityType" THEN + // Domain vertex type — surface ": " so the + // LLM sees the schema-aware label. + @@final_retrieval += (s.id -> s.type + ": " + replace(s.id, "_", " ")) END; @@verbose_info += ("start_set" -> @@start_set_type); diff --git a/common/gsql/supportai/retrievers/Content_Similarity_Vector_Search.gsql b/common/gsql/supportai/retrievers/Content_Similarity_Vector_Search.gsql index 24648d9..5d58f89 100644 --- a/common/gsql/supportai/retrievers/Content_Similarity_Vector_Search.gsql +++ b/common/gsql/supportai/retrievers/Content_Similarity_Vector_Search.gsql @@ -42,6 +42,12 @@ CREATE OR REPLACE DISTRIBUTED QUERY Content_Similarity_Vector_Search(STRING v_ty @@final_retrieval += (s.id -> s.definition) ELSE IF s.type == "Community" THEN @@final_retrieval += (s.id -> s.description) + ELSE IF s.type != "DocumentChunk" AND s.type != "Document" + AND s.type != "Content" AND s.type != "Image" + AND s.type != "EntityType" THEN + // Domain vertex type — surface ": " so the + // LLM sees the schema-aware label. + @@final_retrieval += (s.id -> s.type + ": " + replace(s.id, "_", " ")) END; @@verbose_info += ("start_set" -> @@start_set_type); diff --git a/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search.gsql b/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search.gsql index 9d68c00..8a49e75 100644 --- a/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search.gsql +++ b/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search.gsql @@ -49,9 +49,7 @@ CREATE OR REPLACE DISTRIBUTED QUERY GraphRAG_Hybrid_Search(STRING json_list_vts start = SELECT t FROM start:s -((RELATIONSHIP>| CONTAINS_ENTITY>| reverse_CONTAINS_ENTITY>| - IS_AFTER>| - IS_HEAD_OF>| - HAS_TAIL>):e)- :t + IS_AFTER>):e)- :t WHERE s.@visited < 1 AND t NOT IN s.@parents ACCUM s.@visited += 1, t.@num_times_seen += 1, t.@parents += s, t.@parents += s.@parents, t.@paths += s.@paths, t.@paths += e POST-ACCUM(t) @@tmp_set += t; @@ -76,6 +74,14 @@ CREATE OR REPLACE DISTRIBUTED QUERY GraphRAG_Hybrid_Search(STRING json_list_vts s.@context += tmp_dsc ELSE IF s.type == "DocumentChunk" THEN @@to_retrieve_content += s + ELSE + // Domain vertex type instance — surface its type label + // and id so the LLM sees "Company: acme corp" instead of + // an unlabeled identifier. Domain VTs mirror Entity + // instances by id, so the description data is already + // captured via the Entity branch above; this branch adds + // the type-aware grounding the schema-aware path provides. + s.@context += s.type + ": " + replace(s.id, "_", " ") END POST-ACCUM(s) IF NOT (chunk_only OR doc_only) OR (chunk_only OR doc_only) AND s.type == "DocumentChunk" THEN diff --git a/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search_Display.gsql b/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search_Display.gsql index 6a6f9b9..a5dcae5 100644 --- a/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search_Display.gsql +++ b/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Search_Display.gsql @@ -48,9 +48,7 @@ CREATE OR REPLACE DISTRIBUTED QUERY GraphRAG_Hybrid_Search_Display(STRING json_l start = SELECT t FROM start:s -((RELATIONSHIP>| CONTAINS_ENTITY>| reverse_CONTAINS_ENTITY>| - IS_AFTER>| - IS_HEAD_OF>| - HAS_TAIL>):e)- :t + IS_AFTER>):e)- :t WHERE s.@visited < 1 AND t NOT IN s.@parents ACCUM s.@visited += 1, t.@num_times_seen += 1, t.@parents += s, t.@parents += s.@parents, t.@paths += s.@paths, t.@paths += e POST-ACCUM(t) @@tmp_set += t, t.@seen_in_hop += to_string(hierachy), @@ -80,6 +78,10 @@ CREATE OR REPLACE DISTRIBUTED QUERY GraphRAG_Hybrid_Search_Display(STRING json_l s.@context += tmp_dsc ELSE IF s.type == "DocumentChunk" THEN @@to_retrieve_content += s + ELSE + // Domain vertex type — surface ": " so the + // LLM sees the schema-aware label. + s.@context += s.type + ": " + replace(s.id, "_", " ") END POST-ACCUM(s) IF NOT chunk_only OR chunk_only AND s.type == "DocumentChunk" THEN diff --git a/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Vector_Search.gsql b/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Vector_Search.gsql index 2816156..d9fc9b4 100644 --- a/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Vector_Search.gsql +++ b/common/gsql/supportai/retrievers/GraphRAG_Hybrid_Vector_Search.gsql @@ -55,9 +55,7 @@ CREATE OR REPLACE DISTRIBUTED QUERY GraphRAG_Hybrid_Vector_Search(Set v_ start = SELECT t FROM start:s -((RELATIONSHIP>| CONTAINS_ENTITY>| reverse_CONTAINS_ENTITY>| - IS_AFTER>| - IS_HEAD_OF>| - HAS_TAIL>):e)- :t + IS_AFTER>):e)- :t WHERE s.@visited < 1 AND t NOT IN s.@parents ACCUM s.@visited += 1, t.@num_times_seen += 1, t.@parents += s, t.@parents += s.@parents, t.@paths += s.@paths, t.@paths += e POST-ACCUM(t) @@tmp_set += t; @@ -82,6 +80,10 @@ CREATE OR REPLACE DISTRIBUTED QUERY GraphRAG_Hybrid_Vector_Search(Set v_ s.@context += tmp_dsc ELSE IF s.type == "DocumentChunk" THEN @@to_retrieve_content += s + ELSE + // Domain vertex type — surface ": " so the + // LLM sees the schema-aware label. + s.@context += s.type + ": " + replace(s.id, "_", " ") END POST-ACCUM(s) IF NOT (chunk_only OR doc_only) OR (chunk_only OR doc_only) AND s.type == "DocumentChunk" THEN diff --git a/common/llm_services/aws_bedrock_service.py b/common/llm_services/aws_bedrock_service.py index de6143a..ce6056c 100644 --- a/common/llm_services/aws_bedrock_service.py +++ b/common/llm_services/aws_bedrock_service.py @@ -22,6 +22,70 @@ logger = logging.getLogger(__name__) +#: Per-model-family ``max_tokens`` caps for Bedrock-hosted models. +#: Keys are case-insensitive prefixes / substrings of the model id; the +#: longest matching prefix wins. Models not matched fall back to the +#: generic default (see :data:`_BEDROCK_MAX_TOKENS_DEFAULT`). +#: +#: References (cap = max output tokens supported by the model on Bedrock): +#: - Anthropic Claude 3 / 3.5 Haiku / 3 Opus: 4096 +#: - Anthropic Claude 3.5 / 3.7 Sonnet: 8192 +#: - Anthropic Claude Sonnet 4 / 4.5: 64000 (we cap at 8192 for safety) +#: - Amazon Titan Text: 4096 +#: - Amazon Nova: 5120 +#: - Cohere Command: 4000 +#: - Meta Llama 2: 2048; Llama 3: 4096 +#: - AI21 Jurassic / Jamba: 4096 +#: - Mistral: 8192 +_BEDROCK_MAX_TOKENS_BY_MODEL: tuple = ( + # Anthropic Claude 3.x family — explicit capped at 4096 + ("anthropic.claude-3-5-haiku", 4096), + ("anthropic.claude-3-haiku", 4096), + ("anthropic.claude-3-opus", 4096), + ("anthropic.claude-3-sonnet", 4096), + ("anthropic.claude-instant", 4096), + # Anthropic Claude 3.5 / 3.7 Sonnet — 8192 + ("anthropic.claude-3-5-sonnet", 8192), + ("anthropic.claude-3-7-sonnet", 8192), + # Amazon Titan Text models — capped at 4096 + ("amazon.titan-text", 4096), + ("amazon.titan-tg1", 4096), + # Cohere Command — 4000 + ("cohere.command", 4000), + # Meta Llama + ("meta.llama2", 2048), + ("meta.llama3", 4096), + # AI21 Jamba / Jurassic + ("ai21.", 4096), +) +_BEDROCK_MAX_TOKENS_DEFAULT = 8192 + + +def _bedrock_max_tokens_for_model(model_id: str) -> int: + """Return the recommended ``max_tokens`` for the given Bedrock model + id. Falls back to :data:`_BEDROCK_MAX_TOKENS_DEFAULT` when no + family-specific cap is registered. + """ + if not model_id: + return _BEDROCK_MAX_TOKENS_DEFAULT + mid = model_id.lower() + # Cross-region inference profiles are prefixed with the region + # short code (``us.``, ``eu.``, ``apac.``, ``us-gov.``); strip so + # ``us.anthropic.claude-3-haiku-...`` matches the same family. + for prefix in ("us.", "eu.", "apac.", "us-gov."): + if mid.startswith(prefix): + mid = mid[len(prefix):] + break + # Walk the table in order; longest-prefix match wins. The table is + # already sorted with more-specific entries first + # (``claude-3-5-haiku`` before ``claude-3-haiku``). + best: tuple = ("", _BEDROCK_MAX_TOKENS_DEFAULT) + for prefix, cap in _BEDROCK_MAX_TOKENS_BY_MODEL: + if mid.startswith(prefix) and len(prefix) > len(best[0]): + best = (prefix, cap) + return best[1] + + class AWSBedrock(LLM_Model): def __init__(self, config): super().__init__(config) @@ -45,11 +109,25 @@ def __init__(self, config): "AWS_SECRET_ACCESS_KEY" ], ) + # Resolve ``max_tokens`` so the langchain-aws built-in default + # of 1024 (Anthropic Claude on InvokeModel) doesn't truncate + # large prompts. Priority: + # 1. ``model_kwargs["max_tokens"]`` — explicit per-deployment override + # 2. ``token_limit`` config field — shared with retrieval-side context cap + # 3. Known model-family cap (Claude 3.x, Titan, Cohere, etc.) + # 4. Generic fallback: 8192 + merged_kwargs = dict(config.get("model_kwargs") or {"temperature": 0}) + if "max_tokens" not in merged_kwargs: + cfg_limit = config.get("token_limit") + if isinstance(cfg_limit, int) and cfg_limit > 0: + merged_kwargs["max_tokens"] = cfg_limit + else: + merged_kwargs["max_tokens"] = _bedrock_max_tokens_for_model(model_name) self.llm = ChatBedrock( client=client, model_id=model_name, region_name=config.get("region_name", "us-east-1"), - model_kwargs=config.get("model_kwargs", {"temperature": 0}), + model_kwargs=merged_kwargs, ) self.prompt_path = config["prompt_path"] diff --git a/common/llm_services/base_llm.py b/common/llm_services/base_llm.py index ba1c770..ffd7a59 100644 --- a/common/llm_services/base_llm.py +++ b/common/llm_services/base_llm.py @@ -146,52 +146,172 @@ async def ainvoke_with_parser( @property def map_question_schema_prompt(self): """Property to get the prompt for the MapQuestionToSchema tool.""" - return self._read_prompt_file(self.prompt_path + "map_question_to_schema.txt") + result = self._read_prompt_file(self.prompt_path + "map_question_to_schema.txt") + if result is not None: + return result + return """# Map Question to Schema + +Replace entities and relationships in the question with their canonical schema names provided in the Inputs section below. + +## Rules +- If an entity (e.g. "John Doe") is referred to by different names or pronouns ("Joe", "he"), use the most complete identifier ("John Doe") consistently. +- Choose the better mapping between a vertex type and one of its attributes. +- Ensure entities are either source or target vertices of the chosen relationships. +- If an entity maps to a vertex attribute, consider generating a `WHERE` clause. +- For synonyms, output the canonical form from the schema choices. +- Generate the **complete** rewritten question. Keep the case of schema elements unchanged. +- Do NOT generate `target_vertex_ids` unless the term `id` is explicitly mentioned in the question. + +## Inputs +- **Vertices**: {vertices} +- **Vertex attributes**: {verticesAttrs} +- **Edges**: {edges} +- **Edge source/target**: {edgesInfo} +- **Question**: {question} +- **Conversation**: {conversation} + +{format_instructions} +""" @property def generate_function_prompt(self): """Property to get the prompt for the GenerateFunction tool.""" - return self._read_prompt_file(self.prompt_path + "generate_function.txt") + result = self._read_prompt_file(self.prompt_path + "generate_function.txt") + if result is not None: + return result + return """# pyTigerGraph Function Selection + +Use the schema below to write the pyTigerGraph function call that answers the question via a `pyTigerGraph` connection. + +## Selection Rules +- For "how many", counts, totals, or graph-DB statistics, always pick a function whose name contains `Count` (e.g. `getVertexCount`, `getEdgeCount`). +- Never pick a function not described in the docstrings below. +- If entities map to vertex attributes, consider a `WHERE` clause. +- When constructing `WHERE`, quote string attribute values properly. Example: `('Person', where='name="William Torres"')` — applies to every string attribute (name, email, address, etc.). +- Do NOT generate `target_vertex_ids` unless the term `id` is explicitly mentioned in the question. +- Pick exactly **one** function to execute. + +## Schema +- **Vertex Types**: {vertex_types} +- **Vertex Attributes**: {vertex_attributes} +- **Vertex IDs**: {vertex_ids} +- **Edge Types**: {edge_types} +- **Edge Attributes**: {edge_attributes} + +## Question +{question} + +## Reference Docstrings +1. {doc1} +2. {doc2} +3. {doc3} +4. {doc4} +5. {doc5} +6. {doc6} +7. {doc7} +8. {doc8} + +## Output +- If the function output answers the user's question, return that answer immediately. +- Output **valid JSON only** — no extra text would render the response invalid. + +{format_instructions} +""" @property def entity_relationship_extraction_prompt(self): """Property to get the prompt for the EntityRelationshipExtraction tool.""" - return self._read_prompt_file( + result = self._read_prompt_file( self.prompt_path + "entity_relationship_extraction.txt" ) - - @property - def generate_cypher_prompt(self): - """Property to get the prompt for the GenerateCypher tool.""" - result = self._read_prompt_file(self.prompt_path + "generate_cypher.txt") if result is not None: return result - return """You're an expert in OpenCypher programming. Given the following schema and history, what is the OpenCypher query that retrieves the {question} - Only include attributes that are found in the schema. Never include any attributes that are not found in the schema. - Use attributes instead of primary id if attribute name is closer to the keyword type in the question. - Use as less vertex type, edge type and attributes as possible. If an attribute is not found in the schema, please exclude it from the query. - Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex. - Never use directed edge pattern in the OpenCypher query. Always use and create query using undirected pattern. - Always use double quotes for strings instead of single quotes. + return """# Knowledge Graph Extraction + +You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph. + +## Goals +- **Nodes** represent entities, concepts, and properties of entities. +- Aim for simplicity and clarity so the graph is accessible to a vast audience. - Avoid generating invalid OpenCypher queries based on the errors from history below. +## Node Labeling +- **Consistency**: use basic or elementary types. Label a person as `person`, not `mathematician` / `scientist`. +- **Node IDs**: never use integers. Use names or human-readable identifiers found in the text. - Schema: {schema} - History: {history} +## Numerical Data and Dates +- Incorporate as **attributes / properties** of the respective nodes. +- Do NOT create separate nodes for dates or numerical values. +- Properties are key-value. Use properties only for dates and numbers; string properties become new nodes. +- Never use escaped single or double quotes within property values. +- Use `camelCase` for property keys (e.g. `birthDate`). - You cannot use the following clauses: - OPTIONAL MATCH - CREATE - MERGE - REMOVE - UNION - UNION ALL - UNWIND - SET +## Coreference Resolution +- Maintain entity consistency: if "John Doe" is referred to as "Joe" or "he", always use the most complete identifier (`John Doe`) throughout. - Make sure to have correct attribute names in the OpenCypher query and not to name result aliases that are vertex or edge types. +## Strict Compliance +- Follow these rules strictly. Non-compliance, including poor formatting, results in termination. - ONLY write the OpenCypher query in the response. Do not include any other information in the response.""" +## No-Relationship Nodes +- Include nodes that have no relationships. Add the node and leave the relationships section empty.""" + + @property + def generate_cypher_prompt(self): + """Property to get the prompt for the GenerateCypher tool.""" + result = self._read_prompt_file(self.prompt_path + "generate_cypher.txt") + if result is not None: + return result + return """# OpenCypher Query Generation + +You are an expert in OpenCypher. Generate the best query that retrieves the answer to: **{question}**. + +## Schema and History +- **Schema**: {schema} +- **History**: {history} + +## Construction Rules +- Distinguish entity **value** from entity **type** carefully. +- Remove duplicate words with the same meaning in the question. +- Only use attributes that exist in the schema. Pick the closest matching attribute name when multiple candidates exist. +- Prefer attributes over primary IDs when an attribute name is more similar to the keyword in the question. +- Keep the query minimal — fewest vertex types, edge types, and attributes possible. +- Do NOT return attributes that aren't explicitly mentioned in the question. If only a vertex is mentioned, return only the vertex. +- Always include the entity from the `WHERE` clause in the final `RETURN`. Use vertex name over ID when available. +- Always use **undirected** edge patterns. Ensure edges connect correct vertex types per schema. +- Use **double quotes** for strings. +- For string comparisons in `WHERE`, convert with `toLower()`. +- Use multi-word, underscore-joined aliases for `ORDER BY`. Aliases / attributes used in `ORDER BY` must be in `RETURN`. Always specify `ASC` / `DESC` based on data type. +- For "summarize" / "write a summary" questions, fetch all neighbour nodes and edges. +- Avoid invalid queries based on errors in the history above. + +## Supported +- **Clauses**: `MATCH`, `OPTIONAL MATCH`, `MANDATORY MATCH`, `WHERE`, `RETURN`, `WITH`, `ORDER BY`, `SKIP`, `LIMIT`, `DELETE`, `DETACH DELETE` +- **Operators**: + - Math: `+`, `-`, `*`, `/`, `%`, `^` + - Comparison: `=`, `<`, `<=`, `>`, `>=`, `<>`, `IS NULL`, `IS NOT NULL` + - Boolean: `AND`, `OR`, `NOT`, `XOR` + - String / list: `CONTAINS`, `STARTS WITH`, `ENDS WITH`, `IN`, `DISTINCT`, `[ ]`, `.` +- **Functions**: + - Aggregation: `count`, `sum`, `avg`, `min`, `max`, `stDev`, `stDevP` + - Math: `abs`, `sqrt`, `log`, `exp`, `sin`, `cos`, `tan`, `radians`, `degrees` + - String: `left`, `right`, `substring`, `replace`, `trim`, `toLower`, `toUpper`, `split` + - List: `head`, `last`, `size`, `range`, `coalesce`, `tail` + - Other: `id`, `elementId`, `labels`, `properties`, `timestamp` +- **Expressions**: `CASE` + +## Unsupported +- **Clauses**: `CALL`, `CREATE`, `MERGE`, `REMOVE`, `SET`, `UNION`, `UNION ALL`, `UNWIND` +- **Functions**: `collect`, `exists`, `keys`, `nodes`, `relationships`, `length`, `percentileCont`, `percentileDisc`, `startNode`, `endNode`, `reverse` (list form) +- **Syntax limits**: + - `WITH` must group by exactly one vertex variable. + - Path variables (`p = (...)`) not supported. + - `MATCH` must reference variables from prior `WITH`. + - Disconnected `MATCH` fragments not supported. + +## Output +- The query must return both the entity from the question AND the requested data. +- Validate syntax before responding. +- Aliases must NOT match vertex / edge types, operator / function names, or reserved keywords. Use multi-word underscore identifiers. +- Output ONLY the OpenCypher query — no explanation.""" @property def generate_gsql_prompt(self): @@ -199,37 +319,30 @@ def generate_gsql_prompt(self): result = self._read_prompt_file(self.prompt_path + "generate_gsql.txt") if result is not None: return result - return """You're an expert in GSQL (Graph SQL) programming for TigerGraph. Given the following schema: {schema}, what is the GSQL query that retrieves the answer for question: {question} - Only include attributes that are found in the schema. Never include any attributes that are not found in the schema. - Use attributes instead of primary id if attribute name is more similar to the keyword type in the question. - Use as few vertex types, edge types and attributes as possible. If an attribute is not found in the schema, please exclude it from the query. - Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex. - Always use double quotes for strings instead of single quotes. - Use alias for ORDER BY if any, and make sure the alias or attributes used in ORDER BY is also in PRINT. Always add ASC or DESC for ORDER BY based on data type. - - Avoid generating invalid GSQL queries based on the errors from history below. - - Schema: {schema} - History: {history} - - Additionally, you cannot use the following clauses: - CREATE - DELETE - INSERT - UPDATE - UPSERT - - Here's some commonly used abbreviations: - dt -> date - pct -> percentage - qty -> quantity - lng -> longitude - cm -> Contract Manufacturer - - Always make the GSQL query returns the entity in the original question together with the data to be queried. - Make sure to have correct attribute names in the GSQL query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords, always construct alias with multiple words connected with underscore. - - ONLY write the GSQL query in the response. Do not include any other information in the response.""" + return """# GSQL Query Generation + +You are an expert in TigerGraph GSQL. Generate the GSQL query that retrieves the answer to: **{question}**. + +## Schema and History +- **Schema**: {schema} +- **History**: {history} + +## Construction Rules +- Only use attributes in the schema. Never invent attributes. +- Prefer attributes over primary IDs when the attribute name is more similar to a keyword in the question. +- Keep the query minimal — fewest vertex types, edge types, and attributes possible. +- Do NOT return attributes the question doesn't mention. If only a vertex is mentioned, return only the vertex. +- Always use **double quotes** for strings. +- Use aliases for `ORDER BY`. Aliases / attributes used in `ORDER BY` must also be in `PRINT`. Always specify `ASC` / `DESC` based on data type. +- Avoid invalid queries based on errors in the history above. + +## Unsupported +- **Clauses**: `CREATE`, `DELETE`, `INSERT`, `UPDATE`, `UPSERT` + +## Output +- The query must return both the entity from the question AND the requested data. +- Aliases must NOT match vertex / edge types, operator / function names, or reserved keywords. Use multi-word underscore identifiers. +- Output ONLY the GSQL query — no explanation.""" @property def route_response_prompt(self): @@ -237,26 +350,34 @@ def route_response_prompt(self): result = self._read_prompt_file(self.prompt_path + "route_response.txt") if result is not None: return result - return """\ -You are an expert at routing a user question to a vectorstore, function calls, or conversation history. -Use the conversation history for questions that are similar to previous ones or that reference earlier answers or responses. -Use the vectorstore for questions that would be best suited by text documents. -Use the function calls for questions that ask about structured data, or operations on structured data. -Questions referring to same entities in a previous, earlier, or above answer or response should be routed to the conversation history. -Keep in mind that some questions about documents such as "how many documents are there?" can be answered by function calls. -The function calls can be used to answer questions about these entities: {v_types} and relationships: {e_types}. -IMPORTANT: Questions about graph database statistics or metadata MUST be routed to function calls. This includes: -- Counting vertices/nodes/edges (e.g. "how many vertices are there", "how many edges in the graph") -- Listing or describing vertex/edge types, schema, or graph structure -- Aggregations, totals, or summaries of data stored in the graph database -- Any question mentioning "graph", "graph db", "graph database", "vertices", "nodes", or "edges" in the context of statistics or counts -These are database queries, NOT document lookups — always route them to function calls. -Otherwise, use vectorstore. Choose one of 'functions', 'vectorstore', or 'history' based on the question and conversation history. -Return a JSON with a single key 'datasource' and no preamble or explanation. -Question to route: {question} -Conversation history: {conversation} -Format: {format_instructions}\ -""" + return """# Route the Question + +Route the user question to one of: `functions`, `vectorstore`, or `history`. + +## Routing +- **`history`**: questions similar to previous ones, or that reference earlier answers / responses, or that refer to the same entities mentioned in a previous answer. +- **`vectorstore`**: questions best answered by text documents. +- **`functions`**: questions about structured data or operations on structured data. Available entities: {v_types}; relationships: {e_types}. Some "how many documents are there?" style questions can be answered here. + +## Mandatory `functions` Routing +Any question about graph database **statistics or metadata** MUST route to `functions`: +- Counts of vertices / nodes / edges (e.g. "how many edges in the graph"). +- Listing or describing vertex / edge types, schema, or graph structure. +- Aggregations, totals, or summaries of data in the graph database. +- Any question mentioning "graph", "graph db", "graph database", "vertices", "nodes", or "edges" in the context of statistics / counts. + +These are **database queries, not document lookups** — always route them to `functions`. + +Otherwise, route to `vectorstore`. + +## Output +Return JSON with a single key `datasource` (value: `functions`, `vectorstore`, or `history`). No preamble or explanation. + +## Inputs +- **Question**: {question} +- **Conversation history**: {conversation} + +{format_instructions}""" @property def hyde_prompt(self): @@ -264,8 +385,13 @@ def hyde_prompt(self): result = self._read_prompt_file(self.prompt_path + "hyde.txt") if result is not None: return result - return """You are a helpful agent that is writing an example of a document that might answer this question: {question} - Answer:""" + return """# Hypothetical Document + +Write an example of a document that might answer this question. + +**Question**: {question} + +**Answer**:""" @property def chatbot_response_prompt(self): @@ -273,13 +399,27 @@ def chatbot_response_prompt(self): result = self._read_prompt_file(self.prompt_path + "chatbot_response.txt") if result is not None: return result - return """Given the answer context in JSON format, rephrase it to answer the question. \n - Use only the provided information in context without adding any reasoning or additional logic. \n - Make sure all information in the answer are covered in the generated answer.\n - - Question: {question} \n - Answer: {context} \n - Format: {format_instructions}""" + return """# AI-Powered Knowledge Graph Assistant + +You are a highly efficient, empathetic, and professional AI assistant. Use the provided contexts to answer the user's question. + +## Rules +- The contexts arrive as JSON key-context pairs. **Combine and rephrase** them to answer the question. +- **Score** each context for relevance and use only the high-scoring ones — do not invent additional logic. +- **Cover** the relevant information, especially image references that carry critical visual information. +- **Preserve** image links exactly as `![description](url)` in the final answer when used. Do NOT modify or omit them. +- **Format** the answer in Markdown — titles, paragraphs, bulleted / numbered lists, images, and tables. Place images and tables below the related text section. +- **Tables**: every row, including the header, starts on a new line. +- **Output as JSON** — escape characters as needed so the response is valid JSON. Include every field required by the format instructions; set unknown fields to empty. +- Treat context keys as citations only when asked; otherwise do NOT include citations in the final answer. + +## Inputs +- **Question**: {question} +- **Contexts**: {context} +- **Query**: {query} + +{format_instructions} +""" @property def keyword_extraction_prompt(self): @@ -287,7 +427,20 @@ def keyword_extraction_prompt(self): result = self._read_prompt_file(self.prompt_path + "keyword_extraction.txt") if result is not None: return result - return """You are a helpful assistant responsible for extracting key terms (glossary) from all the questions below to represent their original meaning as much as possible. Each term should only contain a couple of words. Include a quality score for the each extracted glossary, based on how important and frequent it's in the given questions. The quality score should range from 0 (poor) to 100 (excellent), with higher scores indicating terms that are both significant and frequent in the context of the questions.\nThe output should only contain the extracted terms and their quality scores using the required format.\n\nQuestion: {question}\n\n{format_instructions}\n""" + return """# Keyword Extraction + +Extract key terms (glossary) from the question(s) below to represent their original meaning as faithfully as possible. + +## Rules +- Each term should contain only a couple of words. +- Score each extracted term **0 (poor)** to **100 (excellent)** based on how important and frequent it is in the question(s). Higher scores indicate terms that are both significant and frequent. +- Output ONLY the extracted terms with their quality scores in the required format. + +## Question +{question} + +{format_instructions} +""" @property def question_expansion_prompt(self): @@ -295,7 +448,18 @@ def question_expansion_prompt(self): result = self._read_prompt_file(self.prompt_path + "question_expansion.txt") if result is not None: return result - return """You are a helpful assistant responsible for generating 10 new questions similar to the original question below to represent its meaning in a more clear way.\nInclude a quality score for the answer, based on how well it represents the meaning of the original question. The quality score should be between 0 (poor) and 100 (excellent).\n\nQuestion: {question}\n\n{format_instructions}\n""" + return """# Question Expansion + +Generate **10 new questions** similar to the original question below to express its meaning more clearly. + +## Scoring +Include a quality score per generated question, **0 (poor)** to **100 (excellent)**, based on how well it represents the meaning of the original question. + +## Question +{question} + +{format_instructions} +""" @property def graphrag_scoring_prompt(self): @@ -303,7 +467,19 @@ def graphrag_scoring_prompt(self): result = self._read_prompt_file(self.prompt_path + "graphrag_scoring.txt") if result is not None: return result - return """You are a helpful assistant responsible for generating an answer to the question below using the data provided.\nInclude a quality score for the answer, based on how well it answers the question. The quality score should be between 0 (poor) and 100 (excellent).\n\nQuestion: {question}\nContext: {context}\n\n{format_instructions}\n""" + return """# Quality-Scored Answer + +Generate an answer to the question below using the provided data, and include a quality score. + +## Scoring +The quality score is between **0 (poor)** and **100 (excellent)**, based on how well the answer addresses the question. + +## Inputs +- **Question**: {question} +- **Context**: {context} + +{format_instructions} +""" @property def community_summarize_prompt(self): @@ -311,10 +487,61 @@ def community_summarize_prompt(self): result = self._read_prompt_file(self.prompt_path + "community_summarization.txt") if result is not None: return result - raise FileNotFoundError( - f"Community summarization prompt file not found in {self.prompt_path}. " - "Please ensure community_summarization.txt exists in the configured prompt path." - ) + return """# Community Summary + +Generate a comprehensive summary of the data below. + +## Rules +- Concatenate the descriptions into a single, comprehensive summary that includes information from **all** descriptions. +- Resolve contradictions; do NOT add information that is not in the descriptions. +- Write in **third person** and include the entity name(s) for full context. + +## Data +- **Community Title**: {entity_name} +- **Description List**: {description_list} +""" + + @property + def schema_extraction_prompt(self): + """Property to get the prompt for sample-doc schema extraction.""" + result = self._read_prompt_file(self.prompt_path + "schema_extraction.txt") + if result is not None: + return result + return """# Schema Extraction + +You are a knowledge-graph schema architect. From the sample documents provided in the Inputs section below, produce a domain schema as TigerGraph GSQL `VERTEX` / `DIRECTED EDGE` / `UNDIRECTED EDGE` declarations (no leading `ADD`). Return GSQL only — no fences, no commentary, no JSON. + +## Rules + +1. **Vertex inclusion**: a vertex type's instances must be individuated in the source (each instance has its own identity), appear **2+ times**, and have at least one natural attribute beyond `name`. Concrete or conceptual is fine. Skip categorical wrappers — names ending in `_record`, `_management`, `_context`, `_grouping`, or labels of classes-of-classes. +2. **Skip layout**: do NOT produce types for axes, page numbers, captions, table cells, or other document-rendering artifacts. +3. **Edge naming**: use a specific action verb. Include an edge type ONLY IF the source documents contain **2+ concrete instances** of that relationship between named entities — do NOT propose merely-plausible edges. Avoid generic edges (`RELATED_TO`, `CONNECTED_TO`, `ASSOCIATED_WITH`, `HAS`, `BELONGS_TO`). Use `DIRECTED EDGE` for asymmetric verbs and `UNDIRECTED EDGE` only for genuinely symmetric peer relationships. +4. **Reserved names**: do NOT use a name (case-insensitive) matching any of the reserved structural types or GSQL keywords listed in the Inputs section. Pick a synonym or qualifier (e.g. `KeywordRecord`). +5. **Attributes**: each `VERTEX` has **1–5** attributes; each `EDGE` has **0–3**. Primitive types only: `STRING`, `INT`, `UINT`, `DOUBLE`, `FLOAT`, `BOOL`, `DATETIME`. Do NOT include any id / primary-key field. +6. **Comments**: every `VERTEX` and `EDGE` MUST be preceded by exactly one `// ` line. +7. **Size**: produce **8–25** vertex types and **8–25** edge types. + +## Example Output (illustrative — pick names that fit YOUR documents) + + // A natural person referenced in the documents. + VERTEX Person(name STRING, role STRING); + + // An organization or institutional body. + VERTEX Organization(name STRING, founded_at DATETIME); + + // A person works for an organization in a given role. + DIRECTED EDGE WORKS_FOR(FROM Person, TO Organization, role STRING); + + // Two people are colleagues — symmetric peer relationship. + UNDIRECTED EDGE COLLEAGUE_OF(FROM Person, TO Person); + +## Inputs +- **Reserved structural types** (case-insensitive): {structural_types} +- **Reserved GSQL keywords** (case-insensitive): {tg_keywords} +- **Sample documents**: + +{samples} +""" @property def contextualize_question_prompt(self): @@ -325,13 +552,18 @@ def contextualize_question_prompt(self): ) if result is not None: return result - return ( - "Given the following conversation history and a follow-up " - "question, rewrite the follow-up question into a standalone, " - "self-contained question suitable for searching a knowledge " - "graph. Do NOT answer the question; only rewrite it.\n\n" - "Conversation history:\n{history}\n\n" - "Follow-up question: {question}\n\n" - "Standalone question:" - ) + return """# Standalone Question Rewrite + +Given the conversation history and a follow-up question, rewrite the follow-up into a **standalone, self-contained** question suitable for searching a knowledge graph. + +Do **NOT** answer the question — only rewrite it. + +## Conversation History +{history} + +## Follow-up Question +{question} + +## Standalone Question +""" diff --git a/common/prompts/aws_bedrock_claude3haiku/chatbot_response.txt b/common/prompts/aws_bedrock_claude3haiku/chatbot_response.txt deleted file mode 100644 index 6acdaf5..0000000 --- a/common/prompts/aws_bedrock_claude3haiku/chatbot_response.txt +++ /dev/null @@ -1,17 +0,0 @@ -You are a highly efficient and empathetic AI-powered knowledge graph assistant. Your goal is to provide accurate, helpful, and friendly response while maintaining professionalism. - -Follow these guidelines: -- Give the contexts in JSON format contains key-context pairs, combine and rephrase it to answer the question. -- Score the contexts for their relevance to the question and use only the information of the high-scoring contexts without adding extra logic. -- Make sure most relevant information in the provided contexts are covered in the generated answer, especially image references providing critical visual information. -- Make sure to preserve the image links in markdown syntax "![description](url)" with its orignal format in the final answer if the context contains the links are used in the response. Do NOT modify or omit these image references. -- Use markdown syntax to geneate the answer, including title, paragraphs, bulleted or numbered list, images and tables if any, and place images or tables below the related text section. -- Ensure that each row of every table, including the header row, starts on a new line. -- Generate the answer in JSON format, make sure to escape necessary characters in order to return a valid JSON response only. -- Make sure all the fields required by the format instructions are included, set a field to empty if you don't have that information. -- Use the keys of the contexts used as citations if asked, DO NOT include citations in the final answer - -Question: {question} -Contexts: {context} -Query: {query} -Format: {format_instructions} diff --git a/common/prompts/aws_bedrock_claude3haiku/community_summarization.txt b/common/prompts/aws_bedrock_claude3haiku/community_summarization.txt deleted file mode 100644 index 50e4619..0000000 --- a/common/prompts/aws_bedrock_claude3haiku/community_summarization.txt +++ /dev/null @@ -1,11 +0,0 @@ -You are a helpful assistant responsible for generating a comprehensive summary of the data provided below. -Given one or two entities, and a list of descriptions, all related to the same entity or group of entities. -Please concatenate all of these into a single, comprehensive description. Make sure to include information collected from all the descriptions. -If the provided descriptions are contradictory, please resolve the contradictions and provide a single, coherent summary, but do not add any information that is not in the description. -Make sure it is written in third person, and include the entity names so we the have full context. - -####### --Data- -Commuinty Title: {entity_name} -Description List: {description_list} - diff --git a/common/prompts/aws_bedrock_claude3haiku/entity_relationship_extraction.txt b/common/prompts/aws_bedrock_claude3haiku/entity_relationship_extraction.txt deleted file mode 100644 index 852dded..0000000 --- a/common/prompts/aws_bedrock_claude3haiku/entity_relationship_extraction.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Knowledge Graph Instructions for GPT-4 -## 1. Overview -You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph. -- **Nodes** represent entities, concepts, and properties of entities. -- The aim is to achieve simplicity and clarity in the knowledge graph, making it accessible for a vast audience. -## 2. Labeling Nodes -- **Consistency**: Ensure you use basic or elementary types for node labels. -- For example, when you identify an entity representing a person, always label it as **"person"**. Avoid using more specific terms like "mathematician" or "scientist". -- **Node IDs**: Never utilize integers as node IDs. Node IDs should be names or human-readable identifiers found in the text. -## 3. Handling Numerical Data and Dates -- Numerical data, like age or other related information, should be incorporated as attributes or properties of the respective nodes. -- **No Separate Nodes for Dates/Numbers**: Do not create separate nodes for dates or numerical values. Always attach them as attributes or properties of nodes. -- **Property Format**: Properties must be in a key-value format. Only use properties for dates and numbers, string properties should be new nodes. -- **Quotation Marks**: Never use escaped single or double quotes within property values. -- **Naming Convention**: Use camelCase for property keys, e.g., `birthDate`. -## 4. Coreference Resolution -- **Maintain Entity Consistency**: When extracting entities, it's vital to ensure consistency. -If an entity, such as "John Doe", is mentioned multiple times in the text but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the knowledge graph. In this example, use "John Doe" as the entity ID. -Remember, the knowledge graph should be coherent and easily understandable, so maintaining consistency in entity references is crucial. -## 5. Strict Compliance -Adhere to the rules strictly. Non-compliance will result in termination, including poor formatting. -## 6. Handling Instances with No Relationships -If a node has no relationships, it should still be included in the knowledge graph. Simply add the node and leave the relationships section empty. \ No newline at end of file diff --git a/common/prompts/aws_bedrock_claude3haiku/generate_cypher.txt b/common/prompts/aws_bedrock_claude3haiku/generate_cypher.txt deleted file mode 100644 index 732ed49..0000000 --- a/common/prompts/aws_bedrock_claude3haiku/generate_cypher.txt +++ /dev/null @@ -1,85 +0,0 @@ -You're an expert in OpenCypher programming. Given the following schema, find the best OpenCypher query that retrieves the answer for question {question}. -If there're multiple words in the question having same meaning then remove the duplication. -Always carefully distinguish entity value from entity type. For example, "MAC LOB" is referring to a LOB named "MAC" because there is a vertex type Lob matching the word "LOB". -Only include attributes that are found in the schema. Never include any attributes that are not found in the schema. -Use attributes instead of primary id if attribute name is more similar to the keyword type in the question. Always use the closest attribute name when there're multiple candidates. -Use as less vertex type, edge type and attributes as possible. If an attribute is not found in the schema, please exclude it from the query. -Always make sure the attributes used exist in the vertex type or edge type referenced, DO NOT use an attribute that does not exist in the vertex or edge from the schema. -Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex. -Always include the entity from the WHERE clause to the final RETURN result. Use vertex name instead of ID whenever available. -Never use directed edge pattern in the OpenCypher query. Always use and create query using undirected pattern. Always ensure the edge used starts from and ends with correct vertex types matching the schema. -Always use double quotes for strings instead of single quotes. -Always convert strings to lower case using toLower() function for string comparision in WHERE clause. -Use alias for ORDER BY if any, avoid using short alias names especially single letter alias, always use meaningful words connected by underscore. -Always make sure the alias or attributes used in ORDER BY is the same type in RETURN. Always add ASC or DESC for ORDER BY based on data type. -For questions like "summarize" or "write a summary" about something, fetch all information on its neighbour nodes and edges. - -Avoid to generate invalid OpenCypher queries based on the errors from history below. - -Schema: {schema} -History: {history} - -Only use the Supported Clauses, Operators, Functions and Expressions below but do not use any of the Unsupported Features, Functions or Syntax Limitations below: - -Supported Clauses: -MATCH / OPTIONAL MATCH / MANDATORY MATCH: Match patterns in the graph. -WHERE: Filter results. -RETURN / WITH: Project query results, alias fields, chain query parts. -ORDER BY / SKIP / LIMIT: Control output order, offset, and size. -DELETE / DETACH DELETE: Delete nodes/edges. - -Supported Operators: -Mathematical: +, -, *, /, %, ^ (exponent) -Comparison: =, <, <=, >, >=, <>, IS NULL, IS NOT NULL -Boolean: AND, OR, NOT, XOR -String/List: CONTAINS, STARTS WITH, ENDS WITH, IN, DISTINCT, [ ] (subscript), . (property access) - -Supported Functions: -Aggregation: count(), sum(), avg(), min(), max(), stDev(), stDevP() -Math: abs(), sqrt(), log(), exp(), sin(), cos(), tan(), radians(), degrees() -String: left(), right(), substring(), replace(), trim(), toLower(), toUpper(), split() -List: head(), last(), size(), range(), coalesce(), tail() -Others: id(), elementId(), labels(), properties(), timestamp() - -Supported Expressions: -CASE: Conditional logic. - -Supported Operators: -Comparison: IS NULL, IS NOT NULL - -Unsupported Features: -Clauses Not Yet Supported -CALL, CREATE, MERGE, REMOVE, SET, UNION, UNION ALL, UNWIND - -Unsupported Functions: -collect(), exists(), keys(), nodes(), relationships(), length(), percentileCont(), percentileDisc(), startNode(), endNode(), reverse() (list form) - -Syntax Limitations: -WITH clause must group by exactly one vertex variable. -Path variables (e.g. p = (...)) not supported. -MATCH must reference variables from prior WITH. -Disconnected MATCH fragments not supported. - -Additionally, you cannot use the following clauses: -CREATE -MERGE -REMOVE -UNION -UNION ALL -UNWIND -SET - -Here's some commonly used abbreviations: -dt -> date -wk -> week -yr -> year -pct -> percentage -qty -> quantity -lng -> longitude -cm -> Contract Manufacturer - -Always make the cypher query returns the entity in the original question together with the data to be queried. -Make sure to have correct attribute names in the OpenCypher query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords, always construct alias with multiple words connected with underscore. -Always validate the syntax for the generated OpenCypher query before writing to response. - -ONLY write the OpenCypher query in the response. Do not include any other information in the response. diff --git a/common/prompts/aws_bedrock_claude3haiku/generate_function.txt b/common/prompts/aws_bedrock_claude3haiku/generate_function.txt deleted file mode 100644 index 359b46c..0000000 --- a/common/prompts/aws_bedrock_claude3haiku/generate_function.txt +++ /dev/null @@ -1,27 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs below to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. -Vertex Types: {vertex_types} -Vertex Attributes: {vertex_attributes} -Vertex IDs: {vertex_ids} -Edge Types: {edge_types} -Edge Attributes: {edge_attributes} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Fourth Docstring: {doc4} -Fifth Docstring: {doc5} -Sixth Docstring: {doc6} -Seventh Docstring: {doc7} -Eighth Docstring: {doc8} - -If the output of this function answers the user's question, immediately return that answer. - -Follow the output directions below on how to structure your response -Only include valid JSON do not include any other texts which would render the response invalid JSON. -{format_instructions} diff --git a/common/prompts/aws_bedrock_claude3haiku/graphrag_scoring.txt b/common/prompts/aws_bedrock_claude3haiku/graphrag_scoring.txt deleted file mode 100644 index 38ef643..0000000 --- a/common/prompts/aws_bedrock_claude3haiku/graphrag_scoring.txt +++ /dev/null @@ -1,7 +0,0 @@ -You are a helpful assistant responsible for generating an answer to the question below using the data provided. -Include a quality score for the answer, based on how well it answers the question. The quality score should be between 0 (poor) and 100 (excellent). - -Question: {question} -Context: {context} - -{format_instructions} diff --git a/common/prompts/aws_bedrock_claude3haiku/map_question_to_schema.txt b/common/prompts/aws_bedrock_claude3haiku/map_question_to_schema.txt deleted file mode 100644 index 8e4cf05..0000000 --- a/common/prompts/aws_bedrock_claude3haiku/map_question_to_schema.txt +++ /dev/null @@ -1,14 +0,0 @@ -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If there are words that are synonyms with the entities or relationships above, make sure to output the cannonical form found in the choices above. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -Respond in JSON (and only JSON). Follow the format instructions below: -{format_instructions} -question: {question} -conversation: {conversation} diff --git a/common/prompts/aws_bedrock_titan/generate_function.txt b/common/prompts/aws_bedrock_titan/generate_function.txt deleted file mode 100644 index b0be05c..0000000 --- a/common/prompts/aws_bedrock_titan/generate_function.txt +++ /dev/null @@ -1,14 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. -Vertex Types: {vertices} -Edge Types: {edges} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Python Call: conn. \ No newline at end of file diff --git a/common/prompts/aws_bedrock_titan/map_question_to_schema.txt b/common/prompts/aws_bedrock_titan/map_question_to_schema.txt deleted file mode 100644 index d9fb173..0000000 --- a/common/prompts/aws_bedrock_titan/map_question_to_schema.txt +++ /dev/null @@ -1,19 +0,0 @@ -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. -Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -Example: How many universities are there? -Response: How many vertices are University Vetexes? -Example: What is the schema? -Response: What is the schema? -Example: How many transactions are there? -Response: How many TRANSACTION Edges are there? -{format_instructions} -question: {question} -conversation: {conversation} diff --git a/common/prompts/azure_open_ai_gpt35_turbo_instruct/entity_relationship_extraction.txt b/common/prompts/azure_open_ai_gpt35_turbo_instruct/entity_relationship_extraction.txt deleted file mode 100644 index 852dded..0000000 --- a/common/prompts/azure_open_ai_gpt35_turbo_instruct/entity_relationship_extraction.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Knowledge Graph Instructions for GPT-4 -## 1. Overview -You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph. -- **Nodes** represent entities, concepts, and properties of entities. -- The aim is to achieve simplicity and clarity in the knowledge graph, making it accessible for a vast audience. -## 2. Labeling Nodes -- **Consistency**: Ensure you use basic or elementary types for node labels. -- For example, when you identify an entity representing a person, always label it as **"person"**. Avoid using more specific terms like "mathematician" or "scientist". -- **Node IDs**: Never utilize integers as node IDs. Node IDs should be names or human-readable identifiers found in the text. -## 3. Handling Numerical Data and Dates -- Numerical data, like age or other related information, should be incorporated as attributes or properties of the respective nodes. -- **No Separate Nodes for Dates/Numbers**: Do not create separate nodes for dates or numerical values. Always attach them as attributes or properties of nodes. -- **Property Format**: Properties must be in a key-value format. Only use properties for dates and numbers, string properties should be new nodes. -- **Quotation Marks**: Never use escaped single or double quotes within property values. -- **Naming Convention**: Use camelCase for property keys, e.g., `birthDate`. -## 4. Coreference Resolution -- **Maintain Entity Consistency**: When extracting entities, it's vital to ensure consistency. -If an entity, such as "John Doe", is mentioned multiple times in the text but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the knowledge graph. In this example, use "John Doe" as the entity ID. -Remember, the knowledge graph should be coherent and easily understandable, so maintaining consistency in entity references is crucial. -## 5. Strict Compliance -Adhere to the rules strictly. Non-compliance will result in termination, including poor formatting. -## 6. Handling Instances with No Relationships -If a node has no relationships, it should still be included in the knowledge graph. Simply add the node and leave the relationships section empty. \ No newline at end of file diff --git a/common/prompts/azure_open_ai_gpt35_turbo_instruct/generate_function.txt b/common/prompts/azure_open_ai_gpt35_turbo_instruct/generate_function.txt deleted file mode 100644 index e0a83d0..0000000 --- a/common/prompts/azure_open_ai_gpt35_turbo_instruct/generate_function.txt +++ /dev/null @@ -1,29 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs below to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -Never add more than one function call in the response, and only use the functions provided below. Do not chain function calls together. -For example, if the correct function is `getVertexCount()` do not use `getVertexCount().limit()`. -If the correct function is `getEdges()` do not use `getEdges().count()`. - -Vertex Types: {vertex_types} -Vertex Attributes: {vertex_attributes} -Vertex IDs: {vertex_ids} -Edge Types: {edge_types} -Edge Attributes: {edge_attributes} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Fourth Docstring: {doc4} -Fifth Docstring: {doc5} -Sixth Docstring: {doc6} -Seventh Docstring: {doc7} -Eighth Docstring: {doc8} - -Follow the output directions below on how to structure your response, make sure to exactly match the output format. -{format_instructions} diff --git a/common/prompts/azure_open_ai_gpt35_turbo_instruct/map_question_to_schema.txt b/common/prompts/azure_open_ai_gpt35_turbo_instruct/map_question_to_schema.txt deleted file mode 100644 index d72726e..0000000 --- a/common/prompts/azure_open_ai_gpt35_turbo_instruct/map_question_to_schema.txt +++ /dev/null @@ -1,17 +0,0 @@ -You are mapping a question from a user to entities represented in a graph database. -The question is: {question} -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. -Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If there are words that are synonyms with the entities or relationships above, make sure to output the cannonical form found in the choices above. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. If some entites are mapped to attributes, may consider to generate a where clause. -Format your response following the directions below. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -{format_instructions} -question: {question} -conversation: {conversation} \ No newline at end of file diff --git a/common/prompts/custom/aml/chatbot_response.txt b/common/prompts/custom/aml/chatbot_response.txt deleted file mode 100644 index 05532c4..0000000 --- a/common/prompts/custom/aml/chatbot_response.txt +++ /dev/null @@ -1,29 +0,0 @@ -You are a highly efficient and empathetic AI-powered assistant in JSON parsing and generating. -Given the following context in JSON format, rephrase it to answer the question. -Use only the provided information in context without adding any reasoning or additional logic. -Make sure all information in the context are covered in the generated answer. -Make sure to extract and include the image links in markdown syntax in the generated answer when their summaries are referenced, and preserve the link URLs in their original format. -Use compact markdown syntax to geneate the answer, including title, bulleted or numbered list, images and tables if any, and place images or tables below the related text section. -Ensure that each row of every table, including the header row, starts on a new line. -Always only return a JSON contains the answer and otheh required fields after validation. Assign an empty value to the field if you cannot determine it. - -For questions related to financial graph or transaction graph, create a suspicious activity report. -- The narrative should be clear, comprehensive, and avoid institution-specific acronyms. -- The narrative should include paragraphs for Summary of **Investigation**, **Suspicious Activity Overview**, **Details of Suspicious Activities**, **Investigation Conducted**, and **Conclusion** -- The narrative must provide information about the subject to include phone numbers, email addresses, addresses and government IDs. -- The narrative must include any suspicious transactions and the start of the suspicious transactions. -- The narrative must specify the suspicious activity observed (types of transactions, amount, frequency). -- The narrative must tell the complete story. A reviewer should understand the full picture without needing additional context. - -Narrative Writing Guidelines: -- Write in clear, complete sentences -- Spell out all acronyms (no institution-specific jargon) -- Include specific dates, amounts, and account numbers -- Describe the investigation conducted -- Note any customer explanations received and why they were insufficient -- Include any supporting documentation references - -Question: {question} -Context: {context} -Query: {query} -Format: {format_instructions} diff --git a/common/prompts/custom/aml/community_summarization.txt b/common/prompts/custom/aml/community_summarization.txt deleted file mode 100644 index 50e4619..0000000 --- a/common/prompts/custom/aml/community_summarization.txt +++ /dev/null @@ -1,11 +0,0 @@ -You are a helpful assistant responsible for generating a comprehensive summary of the data provided below. -Given one or two entities, and a list of descriptions, all related to the same entity or group of entities. -Please concatenate all of these into a single, comprehensive description. Make sure to include information collected from all the descriptions. -If the provided descriptions are contradictory, please resolve the contradictions and provide a single, coherent summary, but do not add any information that is not in the description. -Make sure it is written in third person, and include the entity names so we the have full context. - -####### --Data- -Commuinty Title: {entity_name} -Description List: {description_list} - diff --git a/common/prompts/custom/aml/entity_relationship_extraction.txt b/common/prompts/custom/aml/entity_relationship_extraction.txt deleted file mode 100644 index 852dded..0000000 --- a/common/prompts/custom/aml/entity_relationship_extraction.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Knowledge Graph Instructions for GPT-4 -## 1. Overview -You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph. -- **Nodes** represent entities, concepts, and properties of entities. -- The aim is to achieve simplicity and clarity in the knowledge graph, making it accessible for a vast audience. -## 2. Labeling Nodes -- **Consistency**: Ensure you use basic or elementary types for node labels. -- For example, when you identify an entity representing a person, always label it as **"person"**. Avoid using more specific terms like "mathematician" or "scientist". -- **Node IDs**: Never utilize integers as node IDs. Node IDs should be names or human-readable identifiers found in the text. -## 3. Handling Numerical Data and Dates -- Numerical data, like age or other related information, should be incorporated as attributes or properties of the respective nodes. -- **No Separate Nodes for Dates/Numbers**: Do not create separate nodes for dates or numerical values. Always attach them as attributes or properties of nodes. -- **Property Format**: Properties must be in a key-value format. Only use properties for dates and numbers, string properties should be new nodes. -- **Quotation Marks**: Never use escaped single or double quotes within property values. -- **Naming Convention**: Use camelCase for property keys, e.g., `birthDate`. -## 4. Coreference Resolution -- **Maintain Entity Consistency**: When extracting entities, it's vital to ensure consistency. -If an entity, such as "John Doe", is mentioned multiple times in the text but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the knowledge graph. In this example, use "John Doe" as the entity ID. -Remember, the knowledge graph should be coherent and easily understandable, so maintaining consistency in entity references is crucial. -## 5. Strict Compliance -Adhere to the rules strictly. Non-compliance will result in termination, including poor formatting. -## 6. Handling Instances with No Relationships -If a node has no relationships, it should still be included in the knowledge graph. Simply add the node and leave the relationships section empty. \ No newline at end of file diff --git a/common/prompts/custom/aml/generate_cypher.txt b/common/prompts/custom/aml/generate_cypher.txt deleted file mode 100644 index 732ed49..0000000 --- a/common/prompts/custom/aml/generate_cypher.txt +++ /dev/null @@ -1,85 +0,0 @@ -You're an expert in OpenCypher programming. Given the following schema, find the best OpenCypher query that retrieves the answer for question {question}. -If there're multiple words in the question having same meaning then remove the duplication. -Always carefully distinguish entity value from entity type. For example, "MAC LOB" is referring to a LOB named "MAC" because there is a vertex type Lob matching the word "LOB". -Only include attributes that are found in the schema. Never include any attributes that are not found in the schema. -Use attributes instead of primary id if attribute name is more similar to the keyword type in the question. Always use the closest attribute name when there're multiple candidates. -Use as less vertex type, edge type and attributes as possible. If an attribute is not found in the schema, please exclude it from the query. -Always make sure the attributes used exist in the vertex type or edge type referenced, DO NOT use an attribute that does not exist in the vertex or edge from the schema. -Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex. -Always include the entity from the WHERE clause to the final RETURN result. Use vertex name instead of ID whenever available. -Never use directed edge pattern in the OpenCypher query. Always use and create query using undirected pattern. Always ensure the edge used starts from and ends with correct vertex types matching the schema. -Always use double quotes for strings instead of single quotes. -Always convert strings to lower case using toLower() function for string comparision in WHERE clause. -Use alias for ORDER BY if any, avoid using short alias names especially single letter alias, always use meaningful words connected by underscore. -Always make sure the alias or attributes used in ORDER BY is the same type in RETURN. Always add ASC or DESC for ORDER BY based on data type. -For questions like "summarize" or "write a summary" about something, fetch all information on its neighbour nodes and edges. - -Avoid to generate invalid OpenCypher queries based on the errors from history below. - -Schema: {schema} -History: {history} - -Only use the Supported Clauses, Operators, Functions and Expressions below but do not use any of the Unsupported Features, Functions or Syntax Limitations below: - -Supported Clauses: -MATCH / OPTIONAL MATCH / MANDATORY MATCH: Match patterns in the graph. -WHERE: Filter results. -RETURN / WITH: Project query results, alias fields, chain query parts. -ORDER BY / SKIP / LIMIT: Control output order, offset, and size. -DELETE / DETACH DELETE: Delete nodes/edges. - -Supported Operators: -Mathematical: +, -, *, /, %, ^ (exponent) -Comparison: =, <, <=, >, >=, <>, IS NULL, IS NOT NULL -Boolean: AND, OR, NOT, XOR -String/List: CONTAINS, STARTS WITH, ENDS WITH, IN, DISTINCT, [ ] (subscript), . (property access) - -Supported Functions: -Aggregation: count(), sum(), avg(), min(), max(), stDev(), stDevP() -Math: abs(), sqrt(), log(), exp(), sin(), cos(), tan(), radians(), degrees() -String: left(), right(), substring(), replace(), trim(), toLower(), toUpper(), split() -List: head(), last(), size(), range(), coalesce(), tail() -Others: id(), elementId(), labels(), properties(), timestamp() - -Supported Expressions: -CASE: Conditional logic. - -Supported Operators: -Comparison: IS NULL, IS NOT NULL - -Unsupported Features: -Clauses Not Yet Supported -CALL, CREATE, MERGE, REMOVE, SET, UNION, UNION ALL, UNWIND - -Unsupported Functions: -collect(), exists(), keys(), nodes(), relationships(), length(), percentileCont(), percentileDisc(), startNode(), endNode(), reverse() (list form) - -Syntax Limitations: -WITH clause must group by exactly one vertex variable. -Path variables (e.g. p = (...)) not supported. -MATCH must reference variables from prior WITH. -Disconnected MATCH fragments not supported. - -Additionally, you cannot use the following clauses: -CREATE -MERGE -REMOVE -UNION -UNION ALL -UNWIND -SET - -Here's some commonly used abbreviations: -dt -> date -wk -> week -yr -> year -pct -> percentage -qty -> quantity -lng -> longitude -cm -> Contract Manufacturer - -Always make the cypher query returns the entity in the original question together with the data to be queried. -Make sure to have correct attribute names in the OpenCypher query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords, always construct alias with multiple words connected with underscore. -Always validate the syntax for the generated OpenCypher query before writing to response. - -ONLY write the OpenCypher query in the response. Do not include any other information in the response. diff --git a/common/prompts/custom/aml/generate_function.txt b/common/prompts/custom/aml/generate_function.txt deleted file mode 100644 index 359b46c..0000000 --- a/common/prompts/custom/aml/generate_function.txt +++ /dev/null @@ -1,27 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs below to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. -Vertex Types: {vertex_types} -Vertex Attributes: {vertex_attributes} -Vertex IDs: {vertex_ids} -Edge Types: {edge_types} -Edge Attributes: {edge_attributes} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Fourth Docstring: {doc4} -Fifth Docstring: {doc5} -Sixth Docstring: {doc6} -Seventh Docstring: {doc7} -Eighth Docstring: {doc8} - -If the output of this function answers the user's question, immediately return that answer. - -Follow the output directions below on how to structure your response -Only include valid JSON do not include any other texts which would render the response invalid JSON. -{format_instructions} diff --git a/common/prompts/custom/aml/graphrag_scoring.txt b/common/prompts/custom/aml/graphrag_scoring.txt deleted file mode 100644 index 38ef643..0000000 --- a/common/prompts/custom/aml/graphrag_scoring.txt +++ /dev/null @@ -1,7 +0,0 @@ -You are a helpful assistant responsible for generating an answer to the question below using the data provided. -Include a quality score for the answer, based on how well it answers the question. The quality score should be between 0 (poor) and 100 (excellent). - -Question: {question} -Context: {context} - -{format_instructions} diff --git a/common/prompts/custom/aml/map_question_to_schema.txt b/common/prompts/custom/aml/map_question_to_schema.txt deleted file mode 100644 index 8e4cf05..0000000 --- a/common/prompts/custom/aml/map_question_to_schema.txt +++ /dev/null @@ -1,14 +0,0 @@ -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If there are words that are synonyms with the entities or relationships above, make sure to output the cannonical form found in the choices above. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -Respond in JSON (and only JSON). Follow the format instructions below: -{format_instructions} -question: {question} -conversation: {conversation} diff --git a/common/prompts/gcp_vertexai_palm/community_summarization.txt b/common/prompts/gcp_vertexai_palm/community_summarization.txt deleted file mode 100644 index 50e4619..0000000 --- a/common/prompts/gcp_vertexai_palm/community_summarization.txt +++ /dev/null @@ -1,11 +0,0 @@ -You are a helpful assistant responsible for generating a comprehensive summary of the data provided below. -Given one or two entities, and a list of descriptions, all related to the same entity or group of entities. -Please concatenate all of these into a single, comprehensive description. Make sure to include information collected from all the descriptions. -If the provided descriptions are contradictory, please resolve the contradictions and provide a single, coherent summary, but do not add any information that is not in the description. -Make sure it is written in third person, and include the entity names so we the have full context. - -####### --Data- -Commuinty Title: {entity_name} -Description List: {description_list} - diff --git a/common/prompts/gcp_vertexai_palm/entity_relationship_extraction.txt b/common/prompts/gcp_vertexai_palm/entity_relationship_extraction.txt deleted file mode 100644 index 852dded..0000000 --- a/common/prompts/gcp_vertexai_palm/entity_relationship_extraction.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Knowledge Graph Instructions for GPT-4 -## 1. Overview -You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph. -- **Nodes** represent entities, concepts, and properties of entities. -- The aim is to achieve simplicity and clarity in the knowledge graph, making it accessible for a vast audience. -## 2. Labeling Nodes -- **Consistency**: Ensure you use basic or elementary types for node labels. -- For example, when you identify an entity representing a person, always label it as **"person"**. Avoid using more specific terms like "mathematician" or "scientist". -- **Node IDs**: Never utilize integers as node IDs. Node IDs should be names or human-readable identifiers found in the text. -## 3. Handling Numerical Data and Dates -- Numerical data, like age or other related information, should be incorporated as attributes or properties of the respective nodes. -- **No Separate Nodes for Dates/Numbers**: Do not create separate nodes for dates or numerical values. Always attach them as attributes or properties of nodes. -- **Property Format**: Properties must be in a key-value format. Only use properties for dates and numbers, string properties should be new nodes. -- **Quotation Marks**: Never use escaped single or double quotes within property values. -- **Naming Convention**: Use camelCase for property keys, e.g., `birthDate`. -## 4. Coreference Resolution -- **Maintain Entity Consistency**: When extracting entities, it's vital to ensure consistency. -If an entity, such as "John Doe", is mentioned multiple times in the text but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the knowledge graph. In this example, use "John Doe" as the entity ID. -Remember, the knowledge graph should be coherent and easily understandable, so maintaining consistency in entity references is crucial. -## 5. Strict Compliance -Adhere to the rules strictly. Non-compliance will result in termination, including poor formatting. -## 6. Handling Instances with No Relationships -If a node has no relationships, it should still be included in the knowledge graph. Simply add the node and leave the relationships section empty. \ No newline at end of file diff --git a/common/prompts/gcp_vertexai_palm/generate_function.txt b/common/prompts/gcp_vertexai_palm/generate_function.txt deleted file mode 100644 index fe7d3cc..0000000 --- a/common/prompts/gcp_vertexai_palm/generate_function.txt +++ /dev/null @@ -1,33 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs below to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -Never add more than one function call in the response, and only use the functions provided below. Do not chain function calls together. -For example, if the correct function is `getVertexCount()` do not use `getVertexCount().limit()`. -If the correct function is `getEdges()` do not use `getEdges().count()`. - -Vertex Types: {vertex_types} -Vertex Attributes: {vertex_attributes} -Vertex IDs: {vertex_ids} -Edge Types: {edge_types} -Edge Attributes: {edge_attributes} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Fourth Docstring: {doc4} -Fifth Docstring: {doc5} -Sixth Docstring: {doc6} -Seventh Docstring: {doc7} -Eighth Docstring: {doc8} - -Make sure to carefully read the question and the docstrings to determine the correct function to call. -Choose the simplest function that will answer the question. -Only choose one function to call, and do not change the syntax of the function call. - -Follow the output directions below on how to structure your response: -{format_instructions} diff --git a/common/prompts/gcp_vertexai_palm/map_question_to_schema.txt b/common/prompts/gcp_vertexai_palm/map_question_to_schema.txt deleted file mode 100644 index b9051bc..0000000 --- a/common/prompts/gcp_vertexai_palm/map_question_to_schema.txt +++ /dev/null @@ -1,14 +0,0 @@ -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. -Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If there are words that are synonyms with the entities or relationships above, make sure to output the cannonical form found in the choices above. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -{format_instructions} -question: {question} -conversation: {conversation} diff --git a/common/prompts/google_gemini/chatbot_response.txt b/common/prompts/google_gemini/chatbot_response.txt deleted file mode 100644 index 6acdaf5..0000000 --- a/common/prompts/google_gemini/chatbot_response.txt +++ /dev/null @@ -1,17 +0,0 @@ -You are a highly efficient and empathetic AI-powered knowledge graph assistant. Your goal is to provide accurate, helpful, and friendly response while maintaining professionalism. - -Follow these guidelines: -- Give the contexts in JSON format contains key-context pairs, combine and rephrase it to answer the question. -- Score the contexts for their relevance to the question and use only the information of the high-scoring contexts without adding extra logic. -- Make sure most relevant information in the provided contexts are covered in the generated answer, especially image references providing critical visual information. -- Make sure to preserve the image links in markdown syntax "![description](url)" with its orignal format in the final answer if the context contains the links are used in the response. Do NOT modify or omit these image references. -- Use markdown syntax to geneate the answer, including title, paragraphs, bulleted or numbered list, images and tables if any, and place images or tables below the related text section. -- Ensure that each row of every table, including the header row, starts on a new line. -- Generate the answer in JSON format, make sure to escape necessary characters in order to return a valid JSON response only. -- Make sure all the fields required by the format instructions are included, set a field to empty if you don't have that information. -- Use the keys of the contexts used as citations if asked, DO NOT include citations in the final answer - -Question: {question} -Contexts: {context} -Query: {query} -Format: {format_instructions} diff --git a/common/prompts/google_gemini/community_summarization.txt b/common/prompts/google_gemini/community_summarization.txt deleted file mode 100644 index 50e4619..0000000 --- a/common/prompts/google_gemini/community_summarization.txt +++ /dev/null @@ -1,11 +0,0 @@ -You are a helpful assistant responsible for generating a comprehensive summary of the data provided below. -Given one or two entities, and a list of descriptions, all related to the same entity or group of entities. -Please concatenate all of these into a single, comprehensive description. Make sure to include information collected from all the descriptions. -If the provided descriptions are contradictory, please resolve the contradictions and provide a single, coherent summary, but do not add any information that is not in the description. -Make sure it is written in third person, and include the entity names so we the have full context. - -####### --Data- -Commuinty Title: {entity_name} -Description List: {description_list} - diff --git a/common/prompts/google_gemini/entity_relationship_extraction.txt b/common/prompts/google_gemini/entity_relationship_extraction.txt deleted file mode 100644 index 852dded..0000000 --- a/common/prompts/google_gemini/entity_relationship_extraction.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Knowledge Graph Instructions for GPT-4 -## 1. Overview -You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph. -- **Nodes** represent entities, concepts, and properties of entities. -- The aim is to achieve simplicity and clarity in the knowledge graph, making it accessible for a vast audience. -## 2. Labeling Nodes -- **Consistency**: Ensure you use basic or elementary types for node labels. -- For example, when you identify an entity representing a person, always label it as **"person"**. Avoid using more specific terms like "mathematician" or "scientist". -- **Node IDs**: Never utilize integers as node IDs. Node IDs should be names or human-readable identifiers found in the text. -## 3. Handling Numerical Data and Dates -- Numerical data, like age or other related information, should be incorporated as attributes or properties of the respective nodes. -- **No Separate Nodes for Dates/Numbers**: Do not create separate nodes for dates or numerical values. Always attach them as attributes or properties of nodes. -- **Property Format**: Properties must be in a key-value format. Only use properties for dates and numbers, string properties should be new nodes. -- **Quotation Marks**: Never use escaped single or double quotes within property values. -- **Naming Convention**: Use camelCase for property keys, e.g., `birthDate`. -## 4. Coreference Resolution -- **Maintain Entity Consistency**: When extracting entities, it's vital to ensure consistency. -If an entity, such as "John Doe", is mentioned multiple times in the text but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the knowledge graph. In this example, use "John Doe" as the entity ID. -Remember, the knowledge graph should be coherent and easily understandable, so maintaining consistency in entity references is crucial. -## 5. Strict Compliance -Adhere to the rules strictly. Non-compliance will result in termination, including poor formatting. -## 6. Handling Instances with No Relationships -If a node has no relationships, it should still be included in the knowledge graph. Simply add the node and leave the relationships section empty. \ No newline at end of file diff --git a/common/prompts/google_gemini/generate_cypher.txt b/common/prompts/google_gemini/generate_cypher.txt deleted file mode 100644 index f347194..0000000 --- a/common/prompts/google_gemini/generate_cypher.txt +++ /dev/null @@ -1,84 +0,0 @@ -You're an expert in OpenCypher programming. Given the following schema, find the best OpenCypher query that retrieves the answer for question {question}. -If there're multiple words in the question having same meaning then remove the duplication. -Always carefully distinguish entity value from entity type. For example, "MAC LOB" is referring to a LOB named "MAC" because there is a vertex type Lob matching the word "LOB". -Only include attributes that are found in the schema. Never include any attributes that are not found in the schema. -Use attributes instead of primary id if attribute name is more similar to the keyword type in the question. Always use the closest attribute name when there're multiple candidates. -Use as less vertex type, edge type and attributes as possible. If an attribute is not found in the schema, please exclude it from the query. -Always make sure the attributes used exist in the vertex type or edge type referenced, DO NOT use an attribute that does not exist in the vertex or edge from the schema. -Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex. -Always return the entity from the WHERE clause together with the final result in the RETURN statement. Use vertex name instead of ID whenever available. -Never use directed edge pattern in the OpenCypher query. Always use and create query using undirected pattern. Always ensure the edge used starts from and ends with correct vertex types matching the schema. -Always use double quotes for strings instead of single quotes. -Always convert strings to lower case using toLower() function for string comparision in WHERE clause. -Use alias for ORDER BY if any, avoid using short alias names especially single letter alias, always use meaningful words connected by underscore. -Always make sure the alias or attributes used in ORDER BY is the same type in RETURN. Always add ASC or DESC for ORDER BY based on data type. - -Avoid to generate invalid OpenCypher queries based on the errors from history below. - -Schema: {schema} -History: {history} - -Only use the Supported Clauses, Operators, Functions and Expressions below but do not use any of the Unsupported Features, Functions or Syntax Limitations below: - -Supported Clauses: -MATCH / OPTIONAL MATCH / MANDATORY MATCH: Match patterns in the graph. -WHERE: Filter results. -RETURN / WITH: Project query results, alias fields, chain query parts. -ORDER BY / SKIP / LIMIT: Control output order, offset, and size. -DELETE / DETACH DELETE: Delete nodes/edges. - -Supported Operators: -Mathematical: +, -, *, /, %, ^ (exponent) -Comparison: =, <, <=, >, >=, <>, IS NULL, IS NOT NULL -Boolean: AND, OR, NOT, XOR -String/List: CONTAINS, STARTS WITH, ENDS WITH, IN, DISTINCT, [ ] (subscript), . (property access) - -Supported Functions: -Aggregation: count(), sum(), avg(), min(), max(), stDev(), stDevP() -Math: abs(), sqrt(), log(), exp(), sin(), cos(), tan(), radians(), degrees() -String: left(), right(), substring(), replace(), trim(), toLower(), toUpper(), split() -List: head(), last(), size(), range(), coalesce(), tail() -Others: id(), elementId(), labels(), properties(), timestamp() - -Supported Expressions: -CASE: Conditional logic. - -Supported Operators: -Comparison: IS NULL, IS NOT NULL - -Unsupported Features: -Clauses Not Yet Supported -CALL, CREATE, MERGE, REMOVE, SET, UNION, UNION ALL, UNWIND - -Unsupported Functions: -collect(), exists(), keys(), nodes(), relationships(), length(), percentileCont(), percentileDisc(), startNode(), endNode(), reverse() (list form) - -Syntax Limitations: -WITH clause must group by exactly one vertex variable. -Path variables (e.g. p = (...)) not supported. -MATCH must reference variables from prior WITH. -Disconnected MATCH fragments not supported. - -Additionally, you cannot use the following clauses: -CREATE -MERGE -REMOVE -UNION -UNION ALL -UNWIND -SET - -Here's some commonly used abbreviations: -dt -> date -wk -> week -yr -> year -pct -> percentage -qty -> quantity -lng -> longitude -cm -> Contract Manufacturer - -Always make the cypher query returns the entity in the original question together with the data to be queried. -Make sure to have correct attribute names in the OpenCypher query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords, always construct alias with multiple words connected with underscore. -Always validate the syntax for the generated OpenCypher query before writing to response. - -ONLY write the OpenCypher query in the response. Do not include any other information in the response. diff --git a/common/prompts/google_gemini/generate_function.txt b/common/prompts/google_gemini/generate_function.txt deleted file mode 100644 index a7e4ee0..0000000 --- a/common/prompts/google_gemini/generate_function.txt +++ /dev/null @@ -1,24 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs below to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. -Vertex Types: {vertex_types} -Vertex Attributes: {vertex_attributes} -Vertex IDs: {vertex_ids} -Edge Types: {edge_types} -Edge Attributes: {edge_attributes} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Fourth Docstring: {doc4} -Fifth Docstring: {doc5} -Sixth Docstring: {doc6} -Seventh Docstring: {doc7} -Eighth Docstring: {doc8} - -Follow the output directions below on how to structure your response: -{format_instructions} diff --git a/common/prompts/google_gemini/graphrag_scoring.txt b/common/prompts/google_gemini/graphrag_scoring.txt deleted file mode 100644 index 38ef643..0000000 --- a/common/prompts/google_gemini/graphrag_scoring.txt +++ /dev/null @@ -1,7 +0,0 @@ -You are a helpful assistant responsible for generating an answer to the question below using the data provided. -Include a quality score for the answer, based on how well it answers the question. The quality score should be between 0 (poor) and 100 (excellent). - -Question: {question} -Context: {context} - -{format_instructions} diff --git a/common/prompts/google_gemini/map_question_to_schema.txt b/common/prompts/google_gemini/map_question_to_schema.txt deleted file mode 100644 index 81ed53d..0000000 --- a/common/prompts/google_gemini/map_question_to_schema.txt +++ /dev/null @@ -1,15 +0,0 @@ -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. -Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If there are words that are synonyms with the entities or relationships above, make sure to output the cannonical form found in the choices above. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -{format_instructions} -question: {question} -conversation: {conversation} - diff --git a/common/prompts/google_gemini/question_expansion.txt b/common/prompts/google_gemini/question_expansion.txt deleted file mode 100644 index b04aed8..0000000 --- a/common/prompts/google_gemini/question_expansion.txt +++ /dev/null @@ -1,6 +0,0 @@ -You are a helpful assistant responsible for generating 10 new questions similar to the original question below to represent its meaning in a more clear way. -Include a quality score for the answer, based on how well it represents the meaning of the original question. The quality score should be between 0 (poor) and 100 (excellent). - -Question: {question} - -{format_instructions} diff --git a/common/prompts/llama_70b/generate_function.txt b/common/prompts/llama_70b/generate_function.txt deleted file mode 100644 index a7e4ee0..0000000 --- a/common/prompts/llama_70b/generate_function.txt +++ /dev/null @@ -1,24 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs below to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. -Vertex Types: {vertex_types} -Vertex Attributes: {vertex_attributes} -Vertex IDs: {vertex_ids} -Edge Types: {edge_types} -Edge Attributes: {edge_attributes} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Fourth Docstring: {doc4} -Fifth Docstring: {doc5} -Sixth Docstring: {doc6} -Seventh Docstring: {doc7} -Eighth Docstring: {doc8} - -Follow the output directions below on how to structure your response: -{format_instructions} diff --git a/common/prompts/llama_70b/map_question_to_schema.txt b/common/prompts/llama_70b/map_question_to_schema.txt deleted file mode 100644 index 81ed53d..0000000 --- a/common/prompts/llama_70b/map_question_to_schema.txt +++ /dev/null @@ -1,15 +0,0 @@ -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. -Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If there are words that are synonyms with the entities or relationships above, make sure to output the cannonical form found in the choices above. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -{format_instructions} -question: {question} -conversation: {conversation} - diff --git a/common/prompts/openai_gpt4/chatbot_response.txt b/common/prompts/openai_gpt4/chatbot_response.txt deleted file mode 100644 index 6acdaf5..0000000 --- a/common/prompts/openai_gpt4/chatbot_response.txt +++ /dev/null @@ -1,17 +0,0 @@ -You are a highly efficient and empathetic AI-powered knowledge graph assistant. Your goal is to provide accurate, helpful, and friendly response while maintaining professionalism. - -Follow these guidelines: -- Give the contexts in JSON format contains key-context pairs, combine and rephrase it to answer the question. -- Score the contexts for their relevance to the question and use only the information of the high-scoring contexts without adding extra logic. -- Make sure most relevant information in the provided contexts are covered in the generated answer, especially image references providing critical visual information. -- Make sure to preserve the image links in markdown syntax "![description](url)" with its orignal format in the final answer if the context contains the links are used in the response. Do NOT modify or omit these image references. -- Use markdown syntax to geneate the answer, including title, paragraphs, bulleted or numbered list, images and tables if any, and place images or tables below the related text section. -- Ensure that each row of every table, including the header row, starts on a new line. -- Generate the answer in JSON format, make sure to escape necessary characters in order to return a valid JSON response only. -- Make sure all the fields required by the format instructions are included, set a field to empty if you don't have that information. -- Use the keys of the contexts used as citations if asked, DO NOT include citations in the final answer - -Question: {question} -Contexts: {context} -Query: {query} -Format: {format_instructions} diff --git a/common/prompts/openai_gpt4/community_summarization.txt b/common/prompts/openai_gpt4/community_summarization.txt deleted file mode 100644 index 50e4619..0000000 --- a/common/prompts/openai_gpt4/community_summarization.txt +++ /dev/null @@ -1,11 +0,0 @@ -You are a helpful assistant responsible for generating a comprehensive summary of the data provided below. -Given one or two entities, and a list of descriptions, all related to the same entity or group of entities. -Please concatenate all of these into a single, comprehensive description. Make sure to include information collected from all the descriptions. -If the provided descriptions are contradictory, please resolve the contradictions and provide a single, coherent summary, but do not add any information that is not in the description. -Make sure it is written in third person, and include the entity names so we the have full context. - -####### --Data- -Commuinty Title: {entity_name} -Description List: {description_list} - diff --git a/common/prompts/openai_gpt4/entity_relationship_extraction.txt b/common/prompts/openai_gpt4/entity_relationship_extraction.txt deleted file mode 100644 index 852dded..0000000 --- a/common/prompts/openai_gpt4/entity_relationship_extraction.txt +++ /dev/null @@ -1,24 +0,0 @@ -# Knowledge Graph Instructions for GPT-4 -## 1. Overview -You are a top-tier algorithm designed for extracting information in structured formats to build a knowledge graph. -- **Nodes** represent entities, concepts, and properties of entities. -- The aim is to achieve simplicity and clarity in the knowledge graph, making it accessible for a vast audience. -## 2. Labeling Nodes -- **Consistency**: Ensure you use basic or elementary types for node labels. -- For example, when you identify an entity representing a person, always label it as **"person"**. Avoid using more specific terms like "mathematician" or "scientist". -- **Node IDs**: Never utilize integers as node IDs. Node IDs should be names or human-readable identifiers found in the text. -## 3. Handling Numerical Data and Dates -- Numerical data, like age or other related information, should be incorporated as attributes or properties of the respective nodes. -- **No Separate Nodes for Dates/Numbers**: Do not create separate nodes for dates or numerical values. Always attach them as attributes or properties of nodes. -- **Property Format**: Properties must be in a key-value format. Only use properties for dates and numbers, string properties should be new nodes. -- **Quotation Marks**: Never use escaped single or double quotes within property values. -- **Naming Convention**: Use camelCase for property keys, e.g., `birthDate`. -## 4. Coreference Resolution -- **Maintain Entity Consistency**: When extracting entities, it's vital to ensure consistency. -If an entity, such as "John Doe", is mentioned multiple times in the text but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the knowledge graph. In this example, use "John Doe" as the entity ID. -Remember, the knowledge graph should be coherent and easily understandable, so maintaining consistency in entity references is crucial. -## 5. Strict Compliance -Adhere to the rules strictly. Non-compliance will result in termination, including poor formatting. -## 6. Handling Instances with No Relationships -If a node has no relationships, it should still be included in the knowledge graph. Simply add the node and leave the relationships section empty. \ No newline at end of file diff --git a/common/prompts/openai_gpt4/generate_cypher.txt b/common/prompts/openai_gpt4/generate_cypher.txt deleted file mode 100644 index 732ed49..0000000 --- a/common/prompts/openai_gpt4/generate_cypher.txt +++ /dev/null @@ -1,85 +0,0 @@ -You're an expert in OpenCypher programming. Given the following schema, find the best OpenCypher query that retrieves the answer for question {question}. -If there're multiple words in the question having same meaning then remove the duplication. -Always carefully distinguish entity value from entity type. For example, "MAC LOB" is referring to a LOB named "MAC" because there is a vertex type Lob matching the word "LOB". -Only include attributes that are found in the schema. Never include any attributes that are not found in the schema. -Use attributes instead of primary id if attribute name is more similar to the keyword type in the question. Always use the closest attribute name when there're multiple candidates. -Use as less vertex type, edge type and attributes as possible. If an attribute is not found in the schema, please exclude it from the query. -Always make sure the attributes used exist in the vertex type or edge type referenced, DO NOT use an attribute that does not exist in the vertex or edge from the schema. -Do not return attributes that are not explicitly mentioned in the question. If a vertex type is mentioned in the question, only return the vertex. -Always include the entity from the WHERE clause to the final RETURN result. Use vertex name instead of ID whenever available. -Never use directed edge pattern in the OpenCypher query. Always use and create query using undirected pattern. Always ensure the edge used starts from and ends with correct vertex types matching the schema. -Always use double quotes for strings instead of single quotes. -Always convert strings to lower case using toLower() function for string comparision in WHERE clause. -Use alias for ORDER BY if any, avoid using short alias names especially single letter alias, always use meaningful words connected by underscore. -Always make sure the alias or attributes used in ORDER BY is the same type in RETURN. Always add ASC or DESC for ORDER BY based on data type. -For questions like "summarize" or "write a summary" about something, fetch all information on its neighbour nodes and edges. - -Avoid to generate invalid OpenCypher queries based on the errors from history below. - -Schema: {schema} -History: {history} - -Only use the Supported Clauses, Operators, Functions and Expressions below but do not use any of the Unsupported Features, Functions or Syntax Limitations below: - -Supported Clauses: -MATCH / OPTIONAL MATCH / MANDATORY MATCH: Match patterns in the graph. -WHERE: Filter results. -RETURN / WITH: Project query results, alias fields, chain query parts. -ORDER BY / SKIP / LIMIT: Control output order, offset, and size. -DELETE / DETACH DELETE: Delete nodes/edges. - -Supported Operators: -Mathematical: +, -, *, /, %, ^ (exponent) -Comparison: =, <, <=, >, >=, <>, IS NULL, IS NOT NULL -Boolean: AND, OR, NOT, XOR -String/List: CONTAINS, STARTS WITH, ENDS WITH, IN, DISTINCT, [ ] (subscript), . (property access) - -Supported Functions: -Aggregation: count(), sum(), avg(), min(), max(), stDev(), stDevP() -Math: abs(), sqrt(), log(), exp(), sin(), cos(), tan(), radians(), degrees() -String: left(), right(), substring(), replace(), trim(), toLower(), toUpper(), split() -List: head(), last(), size(), range(), coalesce(), tail() -Others: id(), elementId(), labels(), properties(), timestamp() - -Supported Expressions: -CASE: Conditional logic. - -Supported Operators: -Comparison: IS NULL, IS NOT NULL - -Unsupported Features: -Clauses Not Yet Supported -CALL, CREATE, MERGE, REMOVE, SET, UNION, UNION ALL, UNWIND - -Unsupported Functions: -collect(), exists(), keys(), nodes(), relationships(), length(), percentileCont(), percentileDisc(), startNode(), endNode(), reverse() (list form) - -Syntax Limitations: -WITH clause must group by exactly one vertex variable. -Path variables (e.g. p = (...)) not supported. -MATCH must reference variables from prior WITH. -Disconnected MATCH fragments not supported. - -Additionally, you cannot use the following clauses: -CREATE -MERGE -REMOVE -UNION -UNION ALL -UNWIND -SET - -Here's some commonly used abbreviations: -dt -> date -wk -> week -yr -> year -pct -> percentage -qty -> quantity -lng -> longitude -cm -> Contract Manufacturer - -Always make the cypher query returns the entity in the original question together with the data to be queried. -Make sure to have correct attribute names in the OpenCypher query and not to name result aliases that are vertex or edge types, operator or function names, and other reserved keywords, always construct alias with multiple words connected with underscore. -Always validate the syntax for the generated OpenCypher query before writing to response. - -ONLY write the OpenCypher query in the response. Do not include any other information in the response. diff --git a/common/prompts/openai_gpt4/generate_function.txt b/common/prompts/openai_gpt4/generate_function.txt deleted file mode 100644 index 359b46c..0000000 --- a/common/prompts/openai_gpt4/generate_function.txt +++ /dev/null @@ -1,27 +0,0 @@ -Use the vertex types, edge types, and their attributes and IDs below to write the pyTigerGraph function call to answer the question using a pyTigerGraph connection. -When the question asks for "How many", counts, totals, or statistics about vertices/nodes/edges in the graph or graph database, make sure to always select a function that contains "Count" in the description/function call. For example, questions like "how many vertices are there in the graph" or "how many vertices are there in the graph db" should use getVertexCount or getEdgeCount. Make sure never to generate a function that is not listed below. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If a WHERE clause is generated, please follow the instruction with proper quoting. To construct a WHERE clause string. Ensure that string attribute values are properly quoted. -For example, if the generated function contains "('Person', where='name=William Torres')", Expected Output: "('Person', where='name="William Torres"')", This rule applies to all types of attributes. e.g., name, email, address and so on. -Documentation contains helpful Python docstrings for the various functions. Use this knowledge to construct the proper function call. Choose one function to execute. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. -Vertex Types: {vertex_types} -Vertex Attributes: {vertex_attributes} -Vertex IDs: {vertex_ids} -Edge Types: {edge_types} -Edge Attributes: {edge_attributes} -Question: {question} -First Docstring: {doc1} -Second Docstring: {doc2} -Third Docstring: {doc3} -Fourth Docstring: {doc4} -Fifth Docstring: {doc5} -Sixth Docstring: {doc6} -Seventh Docstring: {doc7} -Eighth Docstring: {doc8} - -If the output of this function answers the user's question, immediately return that answer. - -Follow the output directions below on how to structure your response -Only include valid JSON do not include any other texts which would render the response invalid JSON. -{format_instructions} diff --git a/common/prompts/openai_gpt4/graphrag_scoring.txt b/common/prompts/openai_gpt4/graphrag_scoring.txt deleted file mode 100644 index 38ef643..0000000 --- a/common/prompts/openai_gpt4/graphrag_scoring.txt +++ /dev/null @@ -1,7 +0,0 @@ -You are a helpful assistant responsible for generating an answer to the question below using the data provided. -Include a quality score for the answer, based on how well it answers the question. The quality score should be between 0 (poor) and 100 (excellent). - -Question: {question} -Context: {context} - -{format_instructions} diff --git a/common/prompts/openai_gpt4/map_question_to_schema.txt b/common/prompts/openai_gpt4/map_question_to_schema.txt deleted file mode 100644 index 81ed53d..0000000 --- a/common/prompts/openai_gpt4/map_question_to_schema.txt +++ /dev/null @@ -1,15 +0,0 @@ -Replace the entites mentioned in the question to one of these choices: {vertices}. -If an entity, such as "John Doe", is mentioned multiple times in the conversation but is referred to by different names or pronouns (e.g., "Joe", "he"), -always use the most complete identifier for that entity throughout the question. In this example, use "John Doe" as the entity. -Choose a better mapping between vertex type or its attributes: {verticesAttrs}. -Replace the relationships mentioned in the question to one of these choices: {edges}. -Make sure the entities are either the source vertices or target vertices of the relationships: {edgesInfo}. -When certain entities are mapped to vertex attributes, may consider to generate a WHERE clause. -If there are words that are synonyms with the entities or relationships above, make sure to output the cannonical form found in the choices above. -Generate the complete question with the appropriate replacements. Keep the case of the schema elements the same. -Don't generate target_vertex_ids if there is no the term 'id' explicitly mentioned in the question. - -{format_instructions} -question: {question} -conversation: {conversation} - diff --git a/common/prompts/openai_gpt4/question_expansion.txt b/common/prompts/openai_gpt4/question_expansion.txt deleted file mode 100644 index b04aed8..0000000 --- a/common/prompts/openai_gpt4/question_expansion.txt +++ /dev/null @@ -1,6 +0,0 @@ -You are a helpful assistant responsible for generating 10 new questions similar to the original question below to represent its meaning in a more clear way. -Include a quality score for the answer, based on how well it represents the meaning of the original question. The quality score should be between 0 (poor) and 100 (excellent). - -Question: {question} - -{format_instructions} diff --git a/common/utils/image_data_extractor.py b/common/utils/image_data_extractor.py index 711c562..f925e9d 100644 --- a/common/utils/image_data_extractor.py +++ b/common/utils/image_data_extractor.py @@ -73,5 +73,8 @@ def describe_image_with_llm(file_path): response = langchain_client.invoke(messages) return response.content if hasattr(response, "content") else str(response) except Exception as e: + error_str = str(e).lower() + if "throttl" in error_str or "rate" in error_str or "too many" in error_str: + raise # Let caller retry on rate limit logger.error(f"Failed to describe image with LLM: {str(e)}") return "Image: Error processing image description" diff --git a/common/utils/prompt_validation.py b/common/utils/prompt_validation.py new file mode 100644 index 0000000..51c5cfd --- /dev/null +++ b/common/utils/prompt_validation.py @@ -0,0 +1,135 @@ +# Copyright (c) 2024-2026 TigerGraph, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 + +"""Gatekeepers for user-customized prompt templates. + +When a user saves a customized prompt via the *Customize Prompts* UI, +two things must hold before the file is written: + +1. **Required placeholders are present.** Every prompt type has a fixed + set of ``{var}`` tokens the calling code substitutes at runtime + (e.g. ``community_summarization`` always interpolates + ``{entity_name}`` and ``{description_list}``). If the user removes + one of these, the corresponding feature breaks at the next call. + ``validate_and_escape_prompt`` returns the missing list so the API + can reject the save with a 400. + +2. **Stray brace tokens are escaped.** Users frequently include literal + ``{example}`` or ``{TODO}`` text in their prompts as documentation + or examples. ``str.format`` / ``PromptTemplate`` interpret those as + placeholders and either substitute the wrong thing or raise + ``KeyError``. ``validate_and_escape_prompt`` rewrites any + ``{ident}`` whose name isn't a recognized placeholder for the + prompt type into ``{{ident}}`` so the runtime treats it as literal. + +The placeholder sets are derived from ``input_variables=[…]`` at the +caller site (e.g. ``agent_generation.py``, ``community_summarizer.py``, +``map_question_to_schema.py``). Add a new entry here when a new +user-customizable prompt is wired up. +""" + +from __future__ import annotations + +import re +from typing import List, Set, Tuple + + +#: Variables every customized prompt of this type MUST contain. Derived +#: from the ``input_variables`` arguments passed to the +#: ``PromptTemplate`` / ``ChatPromptTemplate`` constructors at the call +#: sites that consume each prompt. +REQUIRED_VARS_BY_PROMPT_TYPE: dict = { + # Used by graphrag/app/agent/agent_generation.py and the supportai + # retrievers' final answer step. + "chatbot_response": {"question", "context"}, + # System message in LLMEntityRelationshipExtractor — input arrives + # via separate human messages, so the customizable prompt doesn't + # need any required placeholders of its own. + "entity_relationship": set(), + # ecc/app/graphrag/community_summarizer.py. + "community_summarization": {"entity_name", "description_list"}, + # graphrag/app/tools/map_question_to_schema.py. + "query_generation": { + "question", + "conversation", + "vertices", + "verticesAttrs", + "edges", + "edgesInfo", + }, + # common/db/schema_extraction.py. + "schema_extraction": {"samples", "structural_types", "tg_keywords"}, +} + + +#: Variables the runtime supplies as ``partial_variables`` (or via a +#: separate prompt message) — they MAY appear in the user content but +#: aren't required. Listed so the escaper doesn't double-brace them. +ALLOWED_PARTIALS_BY_PROMPT_TYPE: dict = { + "chatbot_response": {"format_instructions", "query", "history"}, + "entity_relationship": {"format_instructions", "input"}, + "community_summarization": {"format_instructions"}, + "query_generation": {"format_instructions"}, + "schema_extraction": set(), +} + + +# Match a single-brace placeholder like ``{ident}`` BUT NOT a +# double-brace ``{{ident}}`` (Python's str.format escape) and NOT +# ``{}`` / ``{123}`` (no leading letter or underscore). +# +# The negative lookbehind ``(? Tuple[str, List[str]]: + """Run both gatekeepers on *content* for *prompt_type*. + + Returns ``(escaped_content, missing_required)`` where: + + * ``escaped_content`` is *content* with every stray ``{ident}`` + rewritten to ``{{ident}}``. Tokens whose name is in the + required + partials set are left as-is. + * ``missing_required`` lists the required placeholder names the + user did NOT include. Caller should reject the save when this + list is non-empty. + + For unknown ``prompt_type`` (e.g. a future addition that this + module hasn't been updated for), returns ``(content, [])`` + unchanged so the save isn't blocked — better to ship a forward- + compatible passthrough than fail-closed on a name typo. + """ + if prompt_type not in REQUIRED_VARS_BY_PROMPT_TYPE: + return content, [] + + required: Set[str] = REQUIRED_VARS_BY_PROMPT_TYPE[prompt_type] + allowed_partials: Set[str] = ALLOWED_PARTIALS_BY_PROMPT_TYPE.get( + prompt_type, set() + ) + legal: Set[str] = required | allowed_partials + + found_idents: Set[str] = set() + + def _replace(m: re.Match) -> str: + ident = m.group(1) + found_idents.add(ident) + if ident in legal: + return m.group(0) + return "{{" + ident + "}}" + + escaped = _PLACEHOLDER_RE.sub(_replace, content) + missing = sorted(required - found_idents) + return escaped, missing diff --git a/common/utils/text_extractors.py b/common/utils/text_extractors.py index 449ace5..4b9e545 100644 --- a/common/utils/text_extractors.py +++ b/common/utils/text_extractors.py @@ -235,6 +235,7 @@ def safe_walk(path): files_to_process = [] jsonl_files_copied = [] + cached_jsonl_skipped = [] for file_path in safe_walk(folder_path_obj): if file_path.is_file(): if file_path.name.startswith(('.', '~', '$')) or 'BROMIUM' in file_path.name.upper(): @@ -252,9 +253,41 @@ def safe_walk(path): }) logger.info(f"Copied JSONL file directly: {file_path.name} ({num_lines} documents)") elif file_ext in self.supported_extensions: - files_to_process.append(file_path) - - logger.info(f"Found {len(files_to_process)} files to process, {len(jsonl_files_copied)} JSONL files copied directly") + # If a previous run (e.g. schema extraction) already + # produced a matching JSONL in *temp_folder*, reuse + # it instead of re-converting the source file. This + # saves the per-file PDF / image conversion cost + # when the user uploaded sample files via the + # Initialize Graph dialog and is now ingesting them. + cached_jsonl = os.path.join( + temp_folder, f"{file_path.stem}.jsonl" + ) + if os.path.exists(cached_jsonl): + try: + num_lines = sum( + 1 for _ in open(cached_jsonl, 'r', encoding='utf-8') + ) + except Exception: + num_lines = 0 + cached_jsonl_skipped.append({ + 'file_path': str(file_path), + 'num_documents': num_lines, + 'jsonl_file': os.path.basename(cached_jsonl), + 'status': 'success', + 'cached': True, + }) + logger.info( + f"Reusing cached JSONL for {file_path.name} " + f"({num_lines} documents) — skipping re-conversion" + ) + else: + files_to_process.append(file_path) + + logger.info( + f"Found {len(files_to_process)} files to process, " + f"{len(jsonl_files_copied)} JSONL files copied directly, " + f"{len(cached_jsonl_skipped)} skipped via cached JSONL" + ) semaphore = asyncio.Semaphore(max_concurrent) @@ -265,8 +298,10 @@ async def process_with_semaphore(file_path): tasks = [process_with_semaphore(fp) for fp in files_to_process] results = await asyncio.gather(*tasks, return_exceptions=True) - processed_files_info = list(jsonl_files_copied) - total_docs = sum(f['num_documents'] for f in jsonl_files_copied) + processed_files_info = list(jsonl_files_copied) + list(cached_jsonl_skipped) + total_docs = sum( + f['num_documents'] for f in jsonl_files_copied + cached_jsonl_skipped + ) for result in results: if isinstance(result, Exception): @@ -457,54 +492,64 @@ def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): "content": markdown_content, "position": 0 }] - image_entries = [] - image_counter = 0 - for img_ref in image_refs: + # Phase 1 — describe + base64-encode every image in parallel. + # Each worker hits Bedrock for the description and reads the + # image off disk, so they're I/O-bound; a small thread pool + # cuts wall-clock proportionally for image-heavy PDFs. + # Markdown mutations stay in phase 2 (next loop) because + # insert_description_by_id / replace_path_with_tg_protocol + # mutate the same shared string and must run in deterministic + # order. Concurrency cap is intentionally small to stay below + # Bedrock's per-account throttle. + image_workers = int(os.environ.get("PDF_IMAGE_CONCURRENCY", "8")) + + def _describe_and_encode(img_ref: dict) -> dict: + """Run on a worker thread. Returns one of: + * ``{"ok": True, "img_ref", "description", "image_base64", + "width", "height"}`` + * ``{"ok": False, "img_ref", "error"}`` + Never raises. + """ try: - img_path = Path(img_ref["path"]) # convert to Path - image_id = img_ref["image_id"] - # Image description + img_path = Path(img_ref["path"]) description = describe_image_with_llm(str(img_path)) - markdown_content = insert_description_by_id( - markdown_content, - image_id, - description - ) - # Convert image to base64 pil_image = PILImage.open(img_path) - buffer = io.BytesIO() - if pil_image.mode != "RGB": pil_image = pil_image.convert("RGB") - + buffer = io.BytesIO() pil_image.save(buffer, format="JPEG", quality=95) image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8") - - image_counter += 1 - image_doc_id = f"{base_doc_id}_image_{image_counter}".lower() - - # Replace file path with tg:// protocol reference in markdown - markdown_content = replace_path_with_tg_protocol( - markdown_content, - image_id, - image_doc_id - ) - - image_entries.append({ - "doc_id": image_doc_id, - "doc_type": "image", - "image_description": description, - "image_data": image_base64, - "image_format": "jpg", - "parent_doc": base_doc_id, - "page_number": 0, + return { + "ok": True, + "img_ref": img_ref, + "description": description, + "image_base64": image_base64, "width": pil_image.width, "height": pil_image.height, - "position": image_counter - }) + } + except Exception as img_error: # noqa: BLE001 — keep going + return {"ok": False, "img_ref": img_ref, "error": img_error} + + if image_refs: + with ThreadPoolExecutor( + max_workers=max(1, min(image_workers, len(image_refs))) + ) as ex: + # executor.map preserves input ordering, which is what + # the markdown-mutation phase below relies on. + described = list(ex.map(_describe_and_encode, image_refs)) + else: + described = [] - except Exception as img_error: - logger.warning(f"Failed to process image {img_ref.get('path')}: {img_error}") + # Phase 2 — apply markdown mutations and build image_entries + # in deterministic order using the parallel results. + image_entries: list[dict] = [] + image_counter = 0 + for d in described: + img_ref = d["img_ref"] + if not d.get("ok"): + logger.warning( + f"Failed to process image {img_ref.get('path')}: {d.get('error')}" + ) failed_path = img_ref.get("path", "") if failed_path: markdown_content = re.sub( @@ -512,6 +557,30 @@ def _extract_pdf_with_images_as_docs(file_path, base_doc_id, graphname=None): "", markdown_content, ) + continue + + image_id = img_ref["image_id"] + markdown_content = insert_description_by_id( + markdown_content, image_id, d["description"] + ) + + image_counter += 1 + image_doc_id = f"{base_doc_id}_image_{image_counter}".lower() + markdown_content = replace_path_with_tg_protocol( + markdown_content, image_id, image_doc_id + ) + image_entries.append({ + "doc_id": image_doc_id, + "doc_type": "image", + "image_description": d["description"], + "image_data": d["image_base64"], + "image_format": "jpg", + "parent_doc": base_doc_id, + "page_number": 0, + "width": d["width"], + "height": d["height"], + "position": image_counter, + }) # FINAL CLEANUP — delete folder after processing everything if image_output_folder.exists() and image_output_folder.is_dir(): diff --git a/ecc/app/ecc_util.py b/ecc/app/ecc_util.py index a28567a..7da80bc 100644 --- a/ecc/app/ecc_util.py +++ b/ecc/app/ecc_util.py @@ -1,5 +1,5 @@ from common.chunkers import character_chunker, regex_chunker, semantic_chunker, markdown_chunker, recursive_chunker, html_chunker, single_chunker -from common.config import get_graphrag_config, embedding_service +from common.config import get_graphrag_config, get_embedding_service def get_chunker(chunker_type: str = "", graphname: str = None): cfg = get_graphrag_config(graphname) @@ -8,7 +8,7 @@ def get_chunker(chunker_type: str = "", graphname: str = None): chunker_config = cfg.get("chunker_config", {}) if chunker_type == "semantic": chunker = semantic_chunker.SemanticChunker( - embedding_service, + get_embedding_service(), chunker_config.get("method", "percentile"), chunker_config.get("threshold", 0.95), ) diff --git a/ecc/app/eventual_consistency_checker.py b/ecc/app/eventual_consistency_checker.py index 1c28b53..fb501fe 100644 --- a/ecc/app/eventual_consistency_checker.py +++ b/ecc/app/eventual_consistency_checker.py @@ -155,24 +155,10 @@ def _upsert_rels(self, src_id, src_type, relationships): for x in relationships ], ) - self.conn.upsertEdges( - "Entity", - "IS_HEAD_OF", - "RelationshipType", - [ - (x["source"], x["source"] + ":" + x["type"] + ":" + x["target"], {}) - for x in relationships - ], - ) - self.conn.upsertEdges( - "RelationshipType", - "HAS_TAIL", - "Entity", - [ - (x["source"] + ":" + x["type"] + ":" + x["target"], x["target"], {}) - for x in relationships - ], - ) + # IS_HEAD_OF / HAS_TAIL are meta-schema edges (EntityType ↔ + # RelationshipType); not per-instance. The legacy ECC path + # writes only MENTIONS_RELATIONSHIP from the chunk/document + # source to the RelationshipType meta-vertex. self.conn.upsertEdges( src_type, "MENTIONS_RELATIONSHIP", diff --git a/ecc/app/graphrag/community_summarizer.py b/ecc/app/graphrag/community_summarizer.py index 532b94f..3586e9b 100644 --- a/ecc/app/graphrag/community_summarizer.py +++ b/ecc/app/graphrag/community_summarizer.py @@ -26,7 +26,7 @@ # src: https://github.com/microsoft/graphrag/blob/main/graphrag/index/graph/extractors/summarize/prompts.py -id_pat = re.compile(r"[_\d]*") +id_pat = re.compile(r"(_\d+)+$") class CommunitySummarizer: diff --git a/ecc/app/graphrag/graph_rag.py b/ecc/app/graphrag/graph_rag.py index c7ef5be..3435916 100644 --- a/ecc/app/graphrag/graph_rag.py +++ b/ecc/app/graphrag/graph_rag.py @@ -34,7 +34,6 @@ stream_ids, tg_sem, upsert_batch, - add_rels_between_types ) from pyTigerGraph import AsyncTigerGraphConnection @@ -506,16 +505,10 @@ async def run(graphname: str, conn: AsyncTigerGraphConnection): init_end = time.perf_counter() logger.info("Doc Processing End") - # Type Resolution + # Type Resolution — IS_HEAD_OF / HAS_TAIL writes happen inline in + # the per-relationship extract step (workers.py); no post-processing + # query needed. type_start = time.perf_counter() - if entity_extraction_switch: - logger.info("Type Processing Start") - res = await add_rels_between_types(conn) - if res.get("error", False): - logger.error(f"Error adding relationships between types: {res}") - else: - logger.info(f"Added relationships between types: {res}") - logger.info("Type Processing End") type_end = time.perf_counter() # Community Detection diff --git a/ecc/app/graphrag/util.py b/ecc/app/graphrag/util.py index 094d95b..7973107 100644 --- a/ecc/app/graphrag/util.py +++ b/ecc/app/graphrag/util.py @@ -30,6 +30,11 @@ get_completion_config, get_graphrag_config, ) +from common.db.schema_utils import ( + is_structural_type, + read_existing_schema_async, + read_type_metadata_async, +) from common.embeddings.base_embedding_store import EmbeddingStore from common.embeddings.tigergraph_embedding_store import TigerGraphEmbeddingStore from common.extractors import GraphExtractor, LLMEntityRelationshipExtractor @@ -61,7 +66,6 @@ "common/gsql/graphrag/StreamChunkContent", "common/gsql/graphrag/SetEpochProcessing", "common/gsql/graphrag/get_vertices_or_remove", - "common/gsql/supportai/create_entity_type_relationships", ] load_q = reusable_channel.ReuseableChannel() @@ -142,7 +146,68 @@ async def init( if graph_cfg.get("extractor") == "graphrag": extractor = GraphExtractor() elif graph_cfg.get("extractor") == "llm": - extractor = LLMEntityRelationshipExtractor(get_llm_service(get_completion_config())) + # Read the live schema directly (without going through the + # proposal-flow SchemaProposal type). This intentionally + # supports graphs whose domain types were created outside of + # the proposal flow — admin UI, prior releases, + # external migration scripts — as long as the domain types + # and the EntityType / RelationshipType metadata are on the + # graph, ECC will use them. + try: + existing = await read_existing_schema_async(conn) + except Exception as exc: + logger.warning(f"Loading live schema for extractor failed: {exc}") + from common.db.schema_utils import ExistingSchema + existing = ExistingSchema() + try: + entity_descs, rel_defs = await read_type_metadata_async(conn) + except Exception as exc: + logger.warning(f"Loading type metadata for extractor failed: {exc}") + entity_descs, rel_defs = {}, {} + + # Filter to domain types (drop GraphRAG structural types and + # any pair whose endpoint touches a structural vertex). + domain_vertex_types = sorted( + v for v in existing.vertex_types if not is_structural_type(v) + ) + domain_edge_endpoints: dict = {} + for edge_name, pairs in existing.edge_pairs.items(): + if is_structural_type(edge_name): + continue + domain_pairs = [ + (s, t) + for s, t in pairs + if not is_structural_type(s) and not is_structural_type(t) + ] + if domain_pairs: + domain_edge_endpoints[edge_name] = domain_pairs + domain_edge_types = sorted(domain_edge_endpoints.keys()) + + # Trim the descriptions to domain types only. + domain_entity_defs = { + vt: entity_descs[vt] + for vt in domain_vertex_types + if entity_descs.get(vt) + } + domain_rel_defs = { + et: rel_defs[et] + for et in domain_edge_types + if rel_defs.get(et) + } + + # Strict-mode comes from graphrag_config; default false (legacy + # fallback to plain Entity vertices for non-domain extractions). + strict_mode = bool(graph_cfg.get("strict_mode", False)) + + extractor = LLMEntityRelationshipExtractor( + get_llm_service(get_completion_config(conn.graphname)), + allowed_entity_types=domain_vertex_types or None, + allowed_relationship_types=domain_edge_types or None, + strict_mode=strict_mode, + entity_type_definitions=domain_entity_defs, + relationship_type_definitions=domain_rel_defs, + domain_edge_endpoints=domain_edge_endpoints, + ) else: raise ValueError("Invalid extractor type") @@ -216,6 +281,55 @@ def process_id(v_id: str): return v_id +# Suffixes the LLM commonly tacks onto type labels without adding +# semantic distinction. Stripped during meta-layer normalization so +# ``Company_Type``, ``Company_Class``, ``Company_Entity`` collapse onto +# the same canonical name. +_TYPE_SUFFIXES = ("_type", "_class", "_entity", "_data", "_info", "_record") + + +def normalize_type_name(name: str) -> str: + """Normalize an LLM-emitted vertex / edge type label so trivial + variants collapse onto a single canonical id. + + Applies in order: + + 1. ``process_id`` (lowercase, whitespace → ``-``, strip parens). + 2. Strip a single trailing semantic-suffix from + :data:`_TYPE_SUFFIXES` (e.g. ``company_type`` → ``company``). + 3. Singularize trailing ``ies`` → ``y`` (``companies`` → + ``company``); strip a single trailing ``s`` only when the + preceding char is a consonant other than ``s``, ``i``, or ``u`` + (``reports`` → ``report``; preserves ``series``, ``status``, + ``news``, ``business``). + + Used only for the EntityType / RelationshipType meta-layer in + Case 1 (no domain types declared) — instance ids stay + untouched. Synonym consolidation (``Company`` vs ``Corporation``) + is out of scope for this deterministic pass. + """ + base = process_id(name) + if not base: + return "" + for suffix in _TYPE_SUFFIXES: + if base.endswith(suffix) and len(base) > len(suffix): + base = base[: -len(suffix)] + break + # Singularize defensively. Length thresholds keep short words + # whose final ``s`` / ``ies`` is part of the singular stem + # (``News``, ``Series``, ``Bus``, ``Status``, ``Yes``). + if base.endswith("ies") and len(base) > 6: + base = base[:-3] + "y" + elif ( + base.endswith("s") + and len(base) > 4 + and base[-2] not in "siu" + and not base[-2].isdigit() + ): + base = base[:-1] + return base + + async def upsert_vertex( conn: AsyncTigerGraphConnection, vertex_type: str, @@ -319,17 +433,6 @@ async def get_commuinty_children(conn, i: int, c: str): return descrs -async def add_rels_between_types(conn): - try: - async with tg_sem: - resp = await conn.runInstalledQuery( - "create_entity_type_relationships" - ) - except Exception as e: - logger.error(f"Check Vert EntityType err:\n{e}") - return {"error": True, "message": e} - return resp[0] - async def check_vertex_has_desc(conn, i: int): try: async with tg_sem: diff --git a/ecc/app/graphrag/workers.py b/ecc/app/graphrag/workers.py index 516b83c..bc5819f 100644 --- a/ecc/app/graphrag/workers.py +++ b/ecc/app/graphrag/workers.py @@ -264,13 +264,65 @@ async def extract( logger.error(f"Failed to extract chunk {chunk_id}: {e}") extracted = [] + # Schema-aware ingest helpers — derive case-insensitive + # lookups from the extractor once per chunk so the loops below + # can map LLM-emitted type strings back to canonical schema names. + domain_vt_canonical: dict = {} + domain_edge_canonical: dict = {} + edge_endpoint_pairs: dict = {} + strict_mode = False + if isinstance(extractor, LLMEntityRelationshipExtractor): + domain_vt_canonical = { + v.casefold(): v for v in (extractor.allowed_vertex_types or []) + } + domain_edge_canonical = { + e.casefold(): e for e in (extractor.allowed_edge_types or []) + } + edge_endpoint_pairs = { + name.casefold(): {(f.casefold(), t.casefold()) for f, t in pairs} + for name, pairs in (extractor.domain_edge_endpoints or {}).items() + } + strict_mode = bool(extractor.strict_mode) + + # ``has_domain_types`` distinguishes the two meta-layer cases: + # Case 1: no domain types on the graph or extracted — the + # EntityType / RelationshipType layer becomes a free-text + # catalog of whatever the LLM emitted (legacy behaviour). + # Case 2: at least one domain type exists — the meta-layer + # is restricted to the declared / matched domain types + # only. Non-matched extractions still write to the legacy + # Entity / RELATIONSHIP layer but DO NOT pollute the meta + # layer. + has_domain_types = bool(domain_vt_canonical) or bool(domain_edge_canonical) + # upsert nodes and edges to the graph for doc in extracted: + # Build a node_id → node_type lookup so the relationship + # loop below knows the source/target types (the parser + # currently doesn't carry endpoint types per relationship). + node_type_by_id: dict = {} + for n in doc.nodes: + if not n.id or not n.type: + continue + pid = util.process_id(str(n.id)) + if pid: + node_type_by_id[pid] = n.type + for i, node in enumerate(doc.nodes): logger.info(f"extract writes entity vert to upsert\nNode: {node.id}") v_id = util.process_id(str(node.id)) if len(v_id) == 0: continue + node_type_lower = (node.type or "").casefold() + domain_vt = domain_vt_canonical.get(node_type_lower) + + # Strict mode: drop nodes whose type isn't in the + # schema. The legacy raw-Entity fallback applies only + # when strict_mode is off OR the node matches a domain + # type. + if strict_mode and domain_vt is None: + continue + desc = await get_vert_desc(conn, v_id, node) if len(desc[0]) == 0: @@ -290,22 +342,38 @@ async def extract( ), ) ) + # Meta-layer (EntityType / ENTITY_HAS_TYPE) population: + # Case 1 (no domain types): write for every extracted + # node using a normalized form of the LLM-emitted + # type label so trivial variants + # (``Company`` / ``Companies`` / ``company_type``) + # collapse onto one EntityType row. + # Case 2 (domain types exist): write only when the + # node matches a declared domain VT, using the + # canonical domain-VT name as the EntityType id. + meta_type_id = "" if isinstance(extractor, LLMEntityRelationshipExtractor): + if not has_domain_types: + meta_type_id = util.normalize_type_name(node.type) + elif domain_vt is not None: + # Preserve the canonical schema casing + # (``InvestmentFund``) so the EntityType id + # matches what ``upsert_type_metadata`` writes + # at schema-apply time. Lowercasing here would + # produce a duplicate row keyed + # ``investmentfund``. + meta_type_id = domain_vt + if meta_type_id: logger.info("extract writes type vert to upsert") - type_id = util.process_id(node.type) - if len(type_id) == 0: - continue await upsert_chan.put( ( - util.upsert_vertex, # func to call + util.upsert_vertex, ( conn, - "EntityType", # v_type - type_id, # v_id - { # attrs - "epoch_added": int(time.time()), - }, - ) + "EntityType", + meta_type_id, + {"epoch_added": int(time.time())}, + ), ) ) logger.info("extract writes entity_has_type edge to upsert") @@ -314,12 +382,12 @@ async def extract( util.upsert_edge, ( conn, - "Entity", # src_type - v_id, # src_id - "ENTITY_HAS_TYPE", # edgeType - "EntityType", # tgt_type - type_id, # tgt_id - None, # attributes + "Entity", + v_id, + "ENTITY_HAS_TYPE", + "EntityType", + meta_type_id, + None, ), ) ) @@ -340,6 +408,44 @@ async def extract( ), ) ) + + # Schema-aware: when the node's type matches a domain + # vertex type from the live schema, ALSO upsert the + # vertex as that domain type and link it back to the + # chunk via the multi-pair CONTAINS_ENTITY pair we + # added at init time. + if domain_vt is not None: + logger.info( + f"extract writes domain {domain_vt} vert + CONTAINS_ENTITY pair" + ) + # Domain VTs don't carry the ECC bookkeeping + # ``epoch_added`` attribute — sending it makes TG + # reject the whole batch. + await upsert_chan.put( + ( + util.upsert_vertex, + ( + conn, + domain_vt, + v_id, + {}, + ), + ) + ) + await upsert_chan.put( + ( + util.upsert_edge, + ( + conn, + "DocumentChunk", + chunk_id, + "CONTAINS_ENTITY", + domain_vt, + v_id, + None, + ), + ) + ) for node2 in doc.nodes[i + 1:]: v_id2 = util.process_id(str(node2.id)) if len(v_id2) == 0: @@ -363,66 +469,227 @@ async def extract( logger.info( f"extract writes relates edge to upsert:{edge.source.id} -({edge.type})-> {edge.target.id}" ) - # upsert verts first to make sure their ID becomes an attr - v_id = util.process_id(edge.source.id) # src_id - if len(v_id) == 0: + src_id = util.process_id(edge.source.id) + tgt_id = util.process_id(edge.target.id) + if len(src_id) == 0 or len(tgt_id) == 0: continue - desc = await get_vert_desc(conn, v_id, edge.source) - if len(desc[0]) == 0: - desc[0] = edge.source.id + + # Look up the source / target types from the per-doc + # node lookup (the parser doesn't currently carry + # endpoint types per relationship). + src_type = node_type_by_id.get(src_id, "") + tgt_type = node_type_by_id.get(tgt_id, "") + + rel_type_lower = (edge.type or "").casefold() + canonical_rel = domain_edge_canonical.get(rel_type_lower) + valid_pair = ( + canonical_rel is not None + and (src_type.casefold(), tgt_type.casefold()) + in edge_endpoint_pairs.get(rel_type_lower, set()) + ) + + # Strict mode: only write the typed pattern. Legacy + # Entity → RELATIONSHIP → Entity fallback applies only + # when strict_mode is off. + if strict_mode and not valid_pair: + continue + + # ---- Legacy raw layer: Entity src + Entity tgt + RELATIONSHIP edge ---- + src_desc = await get_vert_desc(conn, src_id, edge.source) + if len(src_desc[0]) == 0: + src_desc[0] = edge.source.id await upsert_chan.put( ( - util.upsert_vertex, # func to call + util.upsert_vertex, ( conn, - "Entity", # v_type - v_id, - { # attrs - "description": desc, + "Entity", + src_id, + { + "description": src_desc, "epoch_added": int(time.time()), }, ), ) ) - v_id = util.process_id(edge.target.id) - if len(v_id) == 0: - continue - desc = await get_vert_desc(conn, v_id, edge.target) - if len(desc[0]) == 0: - desc[0] = edge.target.id + tgt_desc = await get_vert_desc(conn, tgt_id, edge.target) + if len(tgt_desc[0]) == 0: + tgt_desc[0] = edge.target.id await upsert_chan.put( ( - util.upsert_vertex, # func to call + util.upsert_vertex, ( conn, - "Entity", # v_type - v_id, # src_id - { # attrs - "description": desc, + "Entity", + tgt_id, + { + "description": tgt_desc, "epoch_added": int(time.time()), }, ), ) ) - - # upsert the edge between the two entities await upsert_chan.put( ( util.upsert_edge, ( conn, - "Entity", # src_type - util.process_id(edge.source.id), # src_id - "RELATIONSHIP", # edgeType - "Entity", # tgt_type - util.process_id(edge.target.id), # tgt_id - {"relation_type": edge.type}, # attributes + "Entity", + src_id, + "RELATIONSHIP", + "Entity", + tgt_id, + {"relation_type": edge.type}, ), ) ) - # embed "RelationshipType", - # (v_id, content, index_name) - # right now, we're not embedding relationships in graphrag + + # ---- Meta-schema typed-relationship layer ---- + # Two cases: + # Case 1 (no domain types): every extracted + # relationship contributes RelationshipType (via + # LLM-emitted edge.type) and IS_HEAD_OF / HAS_TAIL + # edges between the corresponding EntityType + # vertices (via LLM-emitted src_type / tgt_type). + # Case 2 (domain types exist) with valid_pair: + # same writes but using canonical (declared) names. + # Case 2 without valid_pair: skip the meta-layer + # entirely. The legacy Entity / RELATIONSHIP write + # above is the only persistence for unmatched + # extractions. + # + # IS_HEAD_OF / HAS_TAIL connect EntityType ↔ + # RelationshipType (meta layer), NOT individual domain + # vertex instances. Per-instance domain edges (e.g. + # ``Company → PUBLISHES → Report``) are written + # separately when valid_pair holds. + meta_rel_id = "" + meta_src_et_id = "" + meta_tgt_et_id = "" + if valid_pair: + meta_rel_id = canonical_rel + # Preserve canonical schema casing so the EntityType + # id matches the entity-side write and the row that + # ``upsert_type_metadata`` lays down at schema-apply + # time (``InvestmentFund``, not + # ``investmentfund``). + meta_src_et_id = domain_vt_canonical.get( + src_type.casefold(), "" + ) + meta_tgt_et_id = domain_vt_canonical.get( + tgt_type.casefold(), "" + ) + elif not has_domain_types: + # Case 1: dedup variants via normalize_type_name so + # the meta-layer doesn't overflow with near-duplicate + # labels (``Company``/``Companies``, + # ``WORKS_FOR``/``works_for_type``, etc.). + meta_rel_id = util.normalize_type_name(edge.type) + meta_src_et_id = util.normalize_type_name(src_type) + meta_tgt_et_id = util.normalize_type_name(tgt_type) + + if meta_rel_id and meta_src_et_id and meta_tgt_et_id: + now = int(time.time()) + await upsert_chan.put( + ( + util.upsert_vertex, + (conn, "RelationshipType", meta_rel_id, {"epoch_added": now}), + ) + ) + await upsert_chan.put( + ( + util.upsert_vertex, + (conn, "EntityType", meta_src_et_id, {"epoch_added": now}), + ) + ) + await upsert_chan.put( + ( + util.upsert_vertex, + (conn, "EntityType", meta_tgt_et_id, {"epoch_added": now}), + ) + ) + await upsert_chan.put( + ( + util.upsert_edge, + ( + conn, + "EntityType", + meta_src_et_id, + "IS_HEAD_OF", + "RelationshipType", + meta_rel_id, + None, + ), + ) + ) + await upsert_chan.put( + ( + util.upsert_edge, + ( + conn, + "RelationshipType", + meta_rel_id, + "HAS_TAIL", + "EntityType", + meta_tgt_et_id, + None, + ), + ) + ) + # Chunk → RelationshipType — fires whenever any + # meta-layer write fires (Case 1 always, Case 2 on + # valid_pair). + await upsert_chan.put( + ( + util.upsert_edge, + ( + conn, + "DocumentChunk", + chunk_id, + "MENTIONS_RELATIONSHIP", + "RelationshipType", + meta_rel_id, + None, + ), + ) + ) + + if valid_pair: + # Schema-aware: also write the canonical domain VT + # instances and the per-instance domain edge (the + # schema-declared edge name like ``PUBLISHES``). + # Domain VTs don't carry the ECC bookkeeping + # ``epoch_added`` attribute — sending it makes TG + # reject the whole batch with ``Unknown vertex + # attribute or vector name: epoch_added``. + canonical_src_vt = domain_vt_canonical.get(src_type.casefold()) + canonical_tgt_vt = domain_vt_canonical.get(tgt_type.casefold()) + await upsert_chan.put( + ( + util.upsert_vertex, + (conn, canonical_src_vt, src_id, {}), + ) + ) + await upsert_chan.put( + ( + util.upsert_vertex, + (conn, canonical_tgt_vt, tgt_id, {}), + ) + ) + await upsert_chan.put( + ( + util.upsert_edge, + ( + conn, + canonical_src_vt, + src_id, + canonical_rel, + canonical_tgt_vt, + tgt_id, + None, + ), + ) + ) comm_sem = asyncio.Semaphore(util._worker_concurrency) diff --git a/ecc/app/main.py b/ecc/app/main.py index 58751bb..f4ba391 100644 --- a/ecc/app/main.py +++ b/ecc/app/main.py @@ -33,7 +33,7 @@ from common.config import ( db_config, graphrag_config, - embedding_service, + get_embedding_service, get_llm_service, get_completion_config, get_graphrag_config, @@ -92,13 +92,18 @@ def initialize_eventual_consistency_checker( try: maj, minor, patch = conn.getVer().split(".") - if maj >= "4" and minor >= "2": + if maj >= "4" and minor >= "2": # TigerGraph native vector support embedding_store = TigerGraphEmbeddingStore( conn, - embedding_service, + get_embedding_service(), support_ai_instance=False, ) + else: + raise ValueError( + f"TigerGraph version {maj}.{minor}.{patch} is not supported. " + "Requires >= 4.2." + ) graph_cfg = get_graphrag_config(graphname) index_names = graph_cfg.get( "indexes", @@ -108,7 +113,9 @@ def initialize_eventual_consistency_checker( if graph_cfg.get("extractor") == "llm": from common.extractors import LLMEntityRelationshipExtractor - extractor = LLMEntityRelationshipExtractor(get_llm_service(get_completion_config())) + extractor = LLMEntityRelationshipExtractor( + get_llm_service(get_completion_config(graphname)) + ) else: raise ValueError("Invalid extractor type") @@ -116,7 +123,7 @@ def initialize_eventual_consistency_checker( graph_cfg.get("process_interval_seconds", 300), graph_cfg.get("cleanup_interval_seconds", 300), graphname, - embedding_service, + get_embedding_service(), embedding_store, index_names, conn, diff --git a/ecc/app/supportai/supportai_init.py b/ecc/app/supportai/supportai_init.py index 8d59a5b..db18ab0 100644 --- a/ecc/app/supportai/supportai_init.py +++ b/ecc/app/supportai/supportai_init.py @@ -21,7 +21,7 @@ from aiochannel import Channel from pyTigerGraph import TigerGraphConnection -from common.config import embedding_service, entity_extraction_switch, doc_process_switch +from common.config import get_embedding_service, entity_extraction_switch, doc_process_switch from common.embeddings.base_embedding_store import EmbeddingStore from common.extractors.BaseExtractor import BaseExtractor from supportai import workers @@ -140,7 +140,7 @@ async def embed( logger.info(f"Embed to {graphname}_{index_name}: {v_id}") sp.create_task( workers.embed( - embedding_service, + get_embedding_service(), embedding_store, v_id, content, diff --git a/ecc/app/supportai/util.py b/ecc/app/supportai/util.py index 3e3f07a..e3dd36f 100644 --- a/ecc/app/supportai/util.py +++ b/ecc/app/supportai/util.py @@ -12,7 +12,7 @@ from pyTigerGraph import TigerGraphConnection from common.config import ( - embedding_service, + get_embedding_service, graphrag_config, get_llm_service, get_completion_config, @@ -122,7 +122,7 @@ async def init( embedding_store = TigerGraphEmbeddingStore( conn, - embedding_service, + get_embedding_service(), support_ai_instance=True, ) embedding_store.set_graphname(conn.graphname) diff --git a/ecc/app/supportai/workers.py b/ecc/app/supportai/workers.py index 07104fb..3afc44f 100644 --- a/ecc/app/supportai/workers.py +++ b/ecc/app/supportai/workers.py @@ -303,34 +303,10 @@ async def extract( ) ) - # upsert the edge between the two entities - await upsert_chan.put( - ( - util.upsert_edge, - ( - conn, - "Entity", # src_type - util.process_id(edge.source.id), # src_id - "IS_HEAD_OF", # edgeType - "RelationshipType", # tgt_type - edge.type, # tgt_id - ), - ) - ) - await upsert_chan.put( - ( - util.upsert_edge, - ( - conn, - "RelationshipType", # src_type - edge.type, # src_id - "HAS_TAIL", # edgeType - "Entity", # tgt_type - util.process_id(edge.target.id), # tgt_id - ), - ) - ) - + # IS_HEAD_OF / HAS_TAIL are meta-schema edges between + # EntityType ↔ RelationshipType — not written here per + # Entity instance. Legacy supportai ECC paths without + # per-instance entity_type info skip the meta-layer. # link the relationship to the chunk it came from logger.info("extract writes mentions edge to upsert") await upsert_chan.put( diff --git a/graphrag-ui/src/components/SideMenu.tsx b/graphrag-ui/src/components/SideMenu.tsx index c4072db..a7594b3 100644 --- a/graphrag-ui/src/components/SideMenu.tsx +++ b/graphrag-ui/src/components/SideMenu.tsx @@ -9,6 +9,7 @@ import { HiOutlineChatBubbleOvalLeft } from "react-icons/hi2"; import { MdKeyboardArrowDown, MdKeyboardArrowUp } from "react-icons/md"; import { IoIosArrowForward } from "react-icons/io"; import { useTheme } from "@/components/ThemeProvider"; +import { safeJson } from "@/utils/safeJson"; import { GoGear } from "react-icons/go"; import { useState } from "react"; import { @@ -98,7 +99,7 @@ const SideMenu = ({ height, setGetConversationId }: { height?: string, setGetCon return; } - const data = await response.json(); + const data = await safeJson(response); if (!Array.isArray(data) || data.length === 0) { setConversationId([]); @@ -120,7 +121,7 @@ const SideMenu = ({ height, setGetConversationId }: { height?: string, setGetCon if (!response2.ok) { return null; } - const content = await response2.json(); + const content = await safeJson(response2); // Get the most recent message timestamp for sorting let lastUpdateTime = item.update_ts || item.create_ts; @@ -204,7 +205,7 @@ const SideMenu = ({ height, setGetConversationId }: { height?: string, setGetCon return; } - const data = await response.json(); + const data = await safeJson(response); setConversationId2(data); // Store the conversation data in sessionStorage for the chat component diff --git a/graphrag-ui/src/pages/Setup.tsx b/graphrag-ui/src/pages/Setup.tsx index d5674ac..168ac65 100644 --- a/graphrag-ui/src/pages/Setup.tsx +++ b/graphrag-ui/src/pages/Setup.tsx @@ -20,6 +20,7 @@ import { SelectValue, } from "@/components/ui/select"; import { useConfirm } from "@/hooks/useConfirm"; +import { safeJson } from "@/utils/safeJson"; const DEFAULT_MAX_UPLOAD_SIZE_MB = 100; const envUploadLimit = Number(import.meta.env.VITE_MAX_UPLOAD_SIZE_MB); @@ -106,7 +107,7 @@ const [activeTab, setActiveTab] = useState("upload"); const response = await fetch(`/ui/${ingestGraphName}/uploads/list`, { headers: { Authorization: `Basic ${creds}` }, }); - const data = await response.json(); + const data = await safeJson(response); setUploadedFiles(data.files || []); } catch (error) { console.error("Error fetching files:", error); @@ -162,11 +163,11 @@ const [activeTab, setActiveTab] = useState("upload"); }); if (!response.ok) { - const errorData = await response.json(); + const errorData = await safeJson(response); throw new Error(errorData.detail || `Upload failed: ${response.statusText}`); } - const data = await response.json(); + const data = await safeJson(response); if (data.status === "success") { const uploadedCount = selectedFiles?.length || 0; setUploadMessage("✅ Successfully uploaded the files. Processing..."); @@ -223,11 +224,11 @@ const [activeTab, setActiveTab] = useState("upload"); }); if (!response.ok) { - const errorData = await response.json(); + const errorData = await safeJson(response); throw new Error(errorData.detail || `Upload failed with status ${response.status}`); } - const data = await response.json(); + const data = await safeJson(response); if (data.status === "success") { uploadedCount++; } else { diff --git a/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx b/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx index dc33689..b07c76f 100644 --- a/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx +++ b/graphrag-ui/src/pages/setup/GraphRAGConfig.tsx @@ -34,6 +34,10 @@ const GraphRAGConfig = () => { const [upsertDelay, setUpsertDelay] = useState("0"); const [maxConcurrency, setMaxConcurrency] = useState("10"); + // Schema-aware initialization (Phase 1 sample-doc path) + const [schemaMaxSampleFiles, setSchemaMaxSampleFiles] = useState("5"); + const [schemaMaxTotalMb, setSchemaMaxTotalMb] = useState("50"); + // Chunker-specific settings const [chunkSize, setChunkSize] = useState(""); const [overlapSize, setOverlapSize] = useState(""); @@ -75,6 +79,8 @@ const GraphRAGConfig = () => { setLoadBatchSize(String(graphragConfig.load_batch_size ?? 500)); setUpsertDelay(String(graphragConfig.upsert_delay ?? 0)); setMaxConcurrency(String(graphragConfig.default_concurrency ?? 10)); + setSchemaMaxSampleFiles(String(graphragConfig.schema_max_sample_files ?? 5)); + setSchemaMaxTotalMb(String(graphragConfig.schema_max_total_mb ?? 50)); const chunkerConfig = graphragConfig.chunker_config || {}; setChunkSize(String(chunkerConfig.chunk_size ?? "")); @@ -157,6 +163,8 @@ const GraphRAGConfig = () => { load_batch_size: parseInt(loadBatchSize), upsert_delay: parseInt(upsertDelay), default_concurrency: parseInt(maxConcurrency), + schema_max_sample_files: parseInt(schemaMaxSampleFiles), + schema_max_total_mb: parseInt(schemaMaxTotalMb), }; // Display defaults — used to avoid saving values the user never changed @@ -173,6 +181,8 @@ const GraphRAGConfig = () => { load_batch_size: 500, upsert_delay: 0, default_concurrency: 10, + schema_max_sample_files: 5, + schema_max_total_mb: 50, }; // Determine which config to diff against based on scope @@ -691,6 +701,54 @@ const GraphRAGConfig = () => { )}
+ {/* Schema-aware initialization (Phase 1) */} +
+

+ Schema Initialization +

+

+ Limits for the Generate from sample documents path on + the Initialize Knowledge Graph dialog. +

+ +
+
+ + setSchemaMaxSampleFiles(e.target.value)} + /> +

+ Maximum number of sample documents per schema-extraction run +

+
+ +
+ + setSchemaMaxTotalMb(e.target.value)} + /> +

+ Combined upload cap across all sample files (per-file cap is fixed at 10 MB) +

+
+
+
+ {/* Service Endpoints (global only) */} {configScope !== "graph" && (
diff --git a/graphrag-ui/src/pages/setup/KGAdmin.tsx b/graphrag-ui/src/pages/setup/KGAdmin.tsx index 0579376..572c9fa 100644 --- a/graphrag-ui/src/pages/setup/KGAdmin.tsx +++ b/graphrag-ui/src/pages/setup/KGAdmin.tsx @@ -31,18 +31,32 @@ const KGAdmin = () => { const [initializeDialogOpen, setInitializeDialogOpen] = useState(false); const [refreshDialogOpen, setRefreshDialogOpen] = useState(false); const [ingestDialogOpen, setIngestDialogOpen] = useState(false); - // Reset states when dialogs close const handleInitializeDialogChange = (open: boolean) => { if (!open && isConfirmDialogOpen) { return; } + // Closing the dialog (X, Esc, click-outside-prevented, or the + // Cancel button) intentionally PRESERVES state — schema source, + // typed graph name, picked sample files, the in-flight extract + // spinner, and any returned draft GSQL all stay so the user can + // reopen and pick up where they left off. State is only reset + // when the user clicks the success "Done" button below + // (handleInitializeReset). setInitializeDialogOpen(open); - if (!open) { - setGraphName(""); - setStatusMessage(""); - setStatusType(""); - } + }; + + const handleInitializeReset = () => { + setGraphName(""); + setStatusMessage(""); + setStatusType(""); + setSchemaSource("none"); + setPasteGsql(""); + setDraftProposal(null); + setSampleFiles([]); + setExtractedFingerprint(null); + setAttributesCollapsed(false); + setIsInitComplete(false); }; const handleRefreshDialogChange = (open: boolean) => { @@ -61,6 +75,113 @@ const KGAdmin = () => { const [isInitializing, setIsInitializing] = useState(false); const [statusMessage, setStatusMessage] = useState(""); const [statusType, setStatusType] = useState<"success" | "error" | "">(""); + // True only after the full create-graph + initialize-graph round + // succeeds. The "Done" button gates on this — extraction success + // alone (statusType === "success" mid-flow) must NOT show Done, + // because the user still needs to click Initialize. + const [isInitComplete, setIsInitComplete] = useState(false); + // Schema-source state (Phase 1). 'none' = legacy auto-create path; + // 'gsql' = user pastes ADD VERTEX/EDGE statements (or `gsql ls` + // output); 'samples' = user uploads a few representative documents, + // the backend runs schema_extraction LLM, returns GSQL, and the + // textarea is populated for review/edit before /initialize_graph. + const [schemaSource, setSchemaSource] = useState<"none" | "gsql" | "samples">("none"); + // Two distinct buffers — Paste GSQL is the user's verbatim text for + // the strict-syntax path; Generate-from-samples populates a + // structured proposal (vertices / edges / attributes) the UI edits + // in form mode. + const [pasteGsql, setPasteGsql] = useState(""); + const [draftProposal, setDraftProposal] = useState<{ + vertices: Array<{ + name: string; + description: string; + attributes: Array<{ name: string; type: string }>; + }>; + edges: Array<{ + name: string; + description: string; + pairs: Array<[string, string]>; + attributes: Array<{ name: string; type: string }>; + }>; + domain_label?: string; + } | null>(null); + const [sampleFiles, setSampleFiles] = useState([]); + const [maxSampleFiles, setMaxSampleFiles] = useState(5); + const [maxTotalMb, setMaxTotalMb] = useState(50); + const [isExtractingSchema, setIsExtractingSchema] = useState(false); + // Fingerprint of the file set used for the most recent successful + // extraction. Used to disable the *Extract draft schema* button + // when the same files are selected (no new work to do). + const [extractedFingerprint, setExtractedFingerprint] = useState(null); + // True when the form-mode editor's per-card attribute lists are + // hidden, for a cleaner overview of types. + const [attributesCollapsed, setAttributesCollapsed] = useState(false); + + const fingerprintFiles = (files: File[]): string => + files + .map((f) => `${f.name}:${f.size}:${f.lastModified}`) + .sort() + .join("|"); + + const sampleFingerprint = fingerprintFiles(sampleFiles); + + const PRIMITIVE_TYPES = [ + "STRING", + "INT", + "UINT", + "DOUBLE", + "FLOAT", + "BOOL", + "DATETIME", + ]; + + // Render the form-mode draft proposal back into ADD VERTEX / ADD + // DIRECTED EDGE GSQL for submission to /initialize_graph. Mirrors + // schema_proposal.emit_preview_gsql on the backend so a round-trip + // produces identical output. + const draftProposalToGsql = ( + proposal: NonNullable + ): string => { + const lines: string[] = []; + if (proposal.domain_label) { + lines.push(`// Domain: ${proposal.domain_label}`); + lines.push(""); + } + for (const v of proposal.vertices) { + if (!v.name.trim()) continue; + if (v.description) lines.push(`// ${v.description}`); + const attrs = v.attributes + .filter((a) => a.name.trim()) + .map((a) => `${a.name} ${a.type}`) + .join(", "); + const attrPart = attrs ? `, ${attrs}` : ""; + lines.push( + `ADD VERTEX ${v.name} (PRIMARY_ID id STRING${attrPart}) ` + + `WITH PRIMARY_ID_AS_ATTRIBUTE="true";` + ); + lines.push(""); + } + for (const e of proposal.edges) { + if (!e.name.trim() || e.pairs.length === 0) continue; + if (e.description) lines.push(`// ${e.description}`); + const pairs = e.pairs + .filter(([f, t]) => f.trim() && t.trim()) + .map(([f, t]) => `FROM ${f}, TO ${t}`) + .join(" | "); + if (!pairs) continue; + const attrs = e.attributes + .filter((a) => a.name.trim()) + .map((a) => `${a.name} ${a.type}`) + .join(", "); + const attrPart = attrs ? `, ${attrs}` : ""; + lines.push( + `ADD DIRECTED EDGE ${e.name} (${pairs}${attrPart}) ` + + `WITH REVERSE_EDGE="reverse_${e.name}";` + ); + lines.push(""); + } + return lines.join("\n").trimEnd() + "\n"; + }; // Refresh state const [refreshGraphName, setRefreshGraphName] = useState(""); @@ -82,6 +203,166 @@ const KGAdmin = () => { } }, []); + // Pull schema-init caps from /ui/config when the Initialize dialog opens. + // Read-only here; the values are edited on the GraphRAG Config page. + useEffect(() => { + if (!initializeDialogOpen) return; + // If there's pending sample-flow state (extraction in flight or a + // returned draft), force the "Generate from sample documents" + // radio to be selected so the user immediately sees the spinner / + // form on reopen, instead of landing on the previously-selected + // option. + if (isExtractingSchema || draftProposal) { + setSchemaSource("samples"); + } + const creds = sessionStorage.getItem("creds"); + if (!creds) return; + fetch(`/ui/config`, { headers: { Authorization: `Basic ${creds}` } }) + .then((r) => (r.ok ? r.json() : null)) + .then((data) => { + const cfg = data?.graphrag_config || {}; + if (typeof cfg.schema_max_sample_files === "number") + setMaxSampleFiles(cfg.schema_max_sample_files); + if (typeof cfg.schema_max_total_mb === "number") + setMaxTotalMb(cfg.schema_max_total_mb); + }) + .catch(() => { + /* fall back to defaults */ + }); + }, [initializeDialogOpen]); + + const handleSampleFileSelect = (e: React.ChangeEvent) => { + const list = Array.from(e.target.files || []); + if (list.length > maxSampleFiles) { + setStatusMessage(`Too many files: pick at most ${maxSampleFiles}.`); + setStatusType("error"); + e.target.value = ""; + return; + } + const totalBytes = list.reduce((sum, f) => sum + f.size, 0); + if (totalBytes > maxTotalMb * 1024 * 1024) { + setStatusMessage(`Total size exceeds ${maxTotalMb} MB cap.`); + setStatusType("error"); + e.target.value = ""; + return; + } + const oversize = list.find((f) => f.size > 10 * 1024 * 1024); + if (oversize) { + setStatusMessage(`File ${oversize.name} exceeds the 10 MB per-file cap.`); + setStatusType("error"); + e.target.value = ""; + return; + } + setSampleFiles(list); + setStatusMessage(""); + setStatusType(""); + }; + + const handleExtractFromSamples = async () => { + if (!graphName.trim()) { + setStatusMessage("Enter a graph name before extracting a draft schema."); + setStatusType("error"); + return; + } + if (sampleFiles.length === 0) { + setStatusMessage("Pick at least one sample document first."); + setStatusType("error"); + return; + } + setIsExtractingSchema(true); + setStatusMessage( + `Step 1/2: Converting ${sampleFiles.length} uploaded file${sampleFiles.length === 1 ? "" : "s"} to text…` + ); + setStatusType(""); + try { + const creds = sessionStorage.getItem("creds"); + if (!creds) throw new Error("Not authenticated. Please login first."); + + // Step 1/2: upload + convert. Returns the saved filenames so we + // know exactly which JSONLs to feed to the LLM in step 2. + const form = new FormData(); + sampleFiles.forEach((f) => form.append("files", f)); + const convertResp = await fetch( + `/ui/${graphName}/convert_sample_files`, + { + method: "POST", + headers: { Authorization: `Basic ${creds}` }, + body: form, + } + ); + const convertData = await convertResp.json(); + if (!convertResp.ok) { + throw new Error( + convertData.detail || `Conversion failed: ${convertResp.statusText}` + ); + } + + // Step 2/2: LLM call. The status flip now reflects the real + // backend phase change, not a timer. + setStatusMessage("Step 2/2: Extracting schema with LLM…"); + const resp = await fetch( + `/ui/${graphName}/extract_schema_from_jsonl`, + { + method: "POST", + headers: { + Authorization: `Basic ${creds}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ filenames: convertData.saved_files || [] }), + } + ); + const data = await resp.json(); + if (!resp.ok) { + throw new Error(data.detail || `Extraction failed: ${resp.statusText}`); + } + const proposal = data.proposal; + if ( + !proposal || + ((proposal.vertices?.length ?? 0) === 0 && + (proposal.edges?.length ?? 0) === 0) + ) { + throw new Error("LLM returned no schema. Try different sample files."); + } + // Normalize so every record has the optional fields the form + // editor expects (defensive — backend always sets them today). + setDraftProposal({ + domain_label: proposal.domain_label, + vertices: (proposal.vertices || []).map((v: any) => ({ + name: v.name || "", + description: v.description || "", + attributes: (v.attributes || []).map((a: any) => ({ + name: a.name || "", + type: a.type || "STRING", + })), + })), + edges: (proposal.edges || []).map((e: any) => ({ + name: e.name || "", + description: e.description || "", + pairs: (e.pairs || []).map((p: any) => [ + p?.[0] || "", + p?.[1] || "", + ]) as Array<[string, string]>, + attributes: (e.attributes || []).map((a: any) => ({ + name: a.name || "", + type: a.type || "STRING", + })), + })), + }); + setExtractedFingerprint(fingerprintFiles(sampleFiles)); + setStatusMessage( + `Draft schema ready (${data.summary?.vertex_count ?? "?"} vertex types, ` + + `${data.summary?.edge_count ?? "?"} edge types). Review/edit below, then click Initialize.` + ); + setStatusType("success"); + } catch (error: any) { + console.error("Schema extraction error:", error); + setStatusMessage(`❌ ${error.message}`); + setStatusType("error"); + } finally { + setIsExtractingSchema(false); + } + }; + // Initialize Graph const handleInitializeGraph = async () => { if (!graphName.trim()) { @@ -143,9 +424,20 @@ const KGAdmin = () => { } setStatusMessage("Step 2/2: Initializing GraphRAG schema..."); + const initBody: { schema_gsql?: string } = {}; + if (schemaSource === "gsql" && pasteGsql.trim()) { + initBody.schema_gsql = pasteGsql; + } else if (schemaSource === "samples" && draftProposal) { + const gsql = draftProposalToGsql(draftProposal).trim(); + if (gsql) initBody.schema_gsql = gsql; + } const initResponse = await fetch(`/ui/${graphName}/initialize_graph`, { method: "POST", - headers: { Authorization: `Basic ${creds}` }, + headers: { + Authorization: `Basic ${creds}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(initBody), }); const initData = await initResponse.json(); @@ -165,10 +457,19 @@ const KGAdmin = () => { return; } + const domain = initData.domain_schema_status; + let domainNote = ""; + if (domain && domain.status === "applied") { + const stmts = domain.statements?.length ?? 0; + domainNote = ` Domain schema applied (${stmts} statement${stmts === 1 ? "" : "s"}).`; + } else if (domain && domain.status === "no-op") { + domainNote = " Domain schema already up-to-date."; + } setStatusMessage( - `✅ Graph "${graphName}" created and initialized successfully! You can now close this dialog.` + `✅ Graph "${graphName}" created and initialized successfully!${domainNote} You can now close this dialog.` ); setStatusType("success"); + setIsInitComplete(true); const newGraph = graphName; setAvailableGraphs(prev => { @@ -374,7 +675,7 @@ const KGAdmin = () => {
{/* Card Grid */} -
+
{/* Initialize Card */}
@@ -446,12 +747,13 @@ const KGAdmin = () => {
+
{/* Initialize Dialog */} e.preventDefault()} > @@ -470,16 +772,753 @@ const KGAdmin = () => { placeholder="e.g., MyKnowledgeGraph" value={graphName} onChange={(e) => setGraphName(e.target.value)} - disabled={isInitializing} + disabled={isInitializing || isExtractingSchema} className="dark:border-[#3D3D3D] dark:bg-shadeA" onKeyDown={(e) => { - if (e.key === "Enter" && !isInitializing) { + if (e.key === "Enter" && !isInitializing && !isExtractingSchema) { handleInitializeGraph(); } }} />
+
+ +
+ + + +
+ + {schemaSource === "samples" && ( +
+ +

+ Up to {maxSampleFiles} files, ≤10 MB each, ≤{maxTotalMb} MB total. + Selected: {sampleFiles.length} + {sampleFiles.length > 0 && + ` (${(sampleFiles.reduce((s, f) => s + f.size, 0) / (1024 * 1024)).toFixed(1)} MB)`} +

+ + + {draftProposal && ( +
+
+

+ Review and edit the draft below. Each vertex auto-gets a primary + key id (STRING) — you don't need to add it. Click + Initialize when ready. +

+ +
+ + {/* Vertex types */} +
+
+

+ Vertex types ({draftProposal.vertices.length}) +

+ +
+
+ {draftProposal.vertices.map((v, vIdx) => ( +
+
+ + setDraftProposal((p) => + p + ? { + ...p, + vertices: p.vertices.map((vv, i) => + i === vIdx ? { ...vv, name: e.target.value } : vv + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="flex-1 h-8 text-sm dark:border-[#3D3D3D] dark:bg-shadeA" + /> + +
+ + setDraftProposal((p) => + p + ? { + ...p, + vertices: p.vertices.map((vv, i) => + i === vIdx + ? { ...vv, description: e.target.value } + : vv + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="h-8 text-sm dark:border-[#3D3D3D] dark:bg-shadeA" + /> +
+ Attributes ({v.attributes.length}); primary key id auto-added + {attributesCollapsed && ( + — collapsed + )} +
+ {!attributesCollapsed && v.attributes.map((a, aIdx) => ( +
+ + setDraftProposal((p) => + p + ? { + ...p, + vertices: p.vertices.map((vv, i) => + i === vIdx + ? { + ...vv, + attributes: vv.attributes.map( + (aa, j) => + j === aIdx + ? { + ...aa, + // Auto-replace whitespace + // with underscores so the + // displayed name always + // matches the GSQL + // identifier that will be + // emitted (whitespace is + // not a valid char in + // GSQL idents). + name: e.target.value.replace( + /\s+/g, + "_" + ), + } + : aa + ), + } + : vv + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="flex-1 h-7 text-xs font-mono dark:border-[#3D3D3D] dark:bg-shadeA" + /> + + +
+ ))} + {!attributesCollapsed && ( + + )} +
+ ))} +
+
+ + {/* Edge types */} +
+
+

+ Edge types ({draftProposal.edges.length}) +

+ +
+
+ {draftProposal.edges.map((e, eIdx) => ( +
+
+ + setDraftProposal((p) => + p + ? { + ...p, + edges: p.edges.map((ee, i) => + i === eIdx + ? { ...ee, name: ev.target.value } + : ee + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="flex-1 h-8 text-sm dark:border-[#3D3D3D] dark:bg-shadeA" + /> + +
+ + setDraftProposal((p) => + p + ? { + ...p, + edges: p.edges.map((ee, i) => + i === eIdx + ? { ...ee, description: ev.target.value } + : ee + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="h-8 text-sm dark:border-[#3D3D3D] dark:bg-shadeA" + /> +
+ Endpoints (FROM → TO): +
+ {e.pairs.map((pair, pIdx) => ( +
+ + setDraftProposal((p) => + p + ? { + ...p, + edges: p.edges.map((ee, i) => + i === eIdx + ? { + ...ee, + pairs: ee.pairs.map((pr, j) => + j === pIdx + ? [ev.target.value, pr[1]] + : pr + ) as Array<[string, string]>, + } + : ee + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="flex-1 h-7 text-xs dark:border-[#3D3D3D] dark:bg-shadeA" + /> + + + setDraftProposal((p) => + p + ? { + ...p, + edges: p.edges.map((ee, i) => + i === eIdx + ? { + ...ee, + pairs: ee.pairs.map((pr, j) => + j === pIdx + ? [pr[0], ev.target.value] + : pr + ) as Array<[string, string]>, + } + : ee + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="flex-1 h-7 text-xs dark:border-[#3D3D3D] dark:bg-shadeA" + /> + +
+ ))} + +
+ Attributes ({e.attributes.length}, optional) + {attributesCollapsed && ( + — collapsed + )} +
+ {!attributesCollapsed && e.attributes.map((a, aIdx) => ( +
+ + setDraftProposal((p) => + p + ? { + ...p, + edges: p.edges.map((ee, i) => + i === eIdx + ? { + ...ee, + attributes: ee.attributes.map( + (aa, j) => + j === aIdx + ? { + ...aa, + // Auto-replace whitespace + // with underscores — + // GSQL idents can't have + // spaces, and rendering + // them as `_` makes the + // visual unambiguous. + name: ev.target.value.replace( + /\s+/g, + "_" + ), + } + : aa + ), + } + : ee + ), + } + : p + ) + } + disabled={isInitializing || isExtractingSchema} + className="flex-1 h-7 text-xs font-mono dark:border-[#3D3D3D] dark:bg-shadeA" + /> + + +
+ ))} + {!attributesCollapsed && ( + + )} +
+ ))} +
+
+
+ )} +
+ )} + + {schemaSource === "gsql" && ( +
+

+ Paste TigerGraph GSQL ADD VERTEX / + ADD [UN]DIRECTED EDGE statements (or output of + gsql ls). If you don't include a + PRIMARY_ID, the system auto-adds + PRIMARY_ID id STRING. Lines that don't match + VERTEX / EDGE patterns are silently ignored. +

+