diff --git a/.env.example b/.env.example index ccab7c28e..45a724601 100644 --- a/.env.example +++ b/.env.example @@ -71,3 +71,15 @@ LANGFLOW_SUPERUSER= # Superuser password # Example: LANGFLOW_SUPERUSER_PASSWORD=123456 LANGFLOW_SUPERUSER_PASSWORD= + +# STORE_URL +# Example: LANGFLOW_STORE_URL=https://api.langflow.store +LANGFLOW_STORE_URL= + +# DOWNLOAD_WEBHOOK_URL +# +LANGFLOW_DOWNLOAD_WEBHOOK_URL= + +# LIKE_WEBHOOK_URL +# +LANGFLOW_LIKE_WEBHOOK_URL= \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index bb61b0b9e..a8229b155 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -16,7 +16,8 @@ "debug" ], "jinja": true, - "justMyCode": true + "justMyCode": true, + "envFile": "${workspaceFolder}/.env" }, { "name": "Python: Remote Attach", diff --git a/Makefile b/Makefile index d5d699fed..41319e0e3 100644 --- a/Makefile +++ b/Makefile @@ -30,13 +30,12 @@ else endif format: - poetry run black . poetry run ruff . --fix + poetry run ruff format . cd src/frontend && npm run format lint: poetry run mypy src/backend/langflow - poetry run black . --check poetry run ruff . --fix install_frontend: @@ -46,6 +45,7 @@ install_frontendc: cd src/frontend && rm -rf node_modules package-lock.json && npm install run_frontend: + @-kill -9 `lsof -t -i:3000` cd src/frontend && npm start run_cli: @@ -73,12 +73,13 @@ install_backend: backend: make install_backend + @-kill -9 `lsof -t -i:7860` ifeq ($(login),1) @echo "Running backend without autologin"; - poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --log-level debug --workers 3 + poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env else @echo "Running backend with autologin"; - LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --log-level debug --workers 3 + LANGFLOW_AUTO_LOGIN=True poetry run langflow run --backend-only --port 7860 --host 0.0.0.0 --no-open-browser --env-file .env endif build_and_run: diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..8c3f329a0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,932 @@ +{ + "name": "langflow", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@radix-ui/react-popover": "^1.0.7", + "cmdk": "^0.2.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.5.0.tgz", + "integrity": "sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==", + "dependencies": { + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.5.3.tgz", + "integrity": "sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==", + "dependencies": { + "@floating-ui/core": "^1.4.2", + "@floating-ui/utils": "^0.1.3" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", + "dependencies": { + "@floating-ui/dom": "^1.5.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.1.6.tgz", + "integrity": "sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==" + }, + "node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", + "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.0.tgz", + "integrity": "sha512-Yn9YU+QlHYLWwV1XfKiqnGVpWYWk6MeBVM6x/bcoyPvxgjQGoeT35482viLPctTMWoMw0PoHgqfSox7Ig+957Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-context": "1.0.0", + "@radix-ui/react-dismissable-layer": "1.0.0", + "@radix-ui/react-focus-guards": "1.0.0", + "@radix-ui/react-focus-scope": "1.0.0", + "@radix-ui/react-id": "1.0.0", + "@radix-ui/react-portal": "1.0.0", + "@radix-ui/react-presence": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-slot": "1.0.0", + "@radix-ui/react-use-controllable-state": "1.0.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.4" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.0.tgz", + "integrity": "sha512-3e7rn8FDMin4CgeL7Z/49smCA3rFYY3Ha2rUQ7HRWFadS5iCRw08ZgVT1LaNTCNqgvrUiyczLflrVrF0SRQtNA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.0.tgz", + "integrity": "sha512-0KaSv6sx787/hK3eF53iOkiSLwAGlFMx5lotrqD2pTjB18KbybKoEIgkNZTKC60YECDQTKGTRcDBILwZVqVKvA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.0.tgz", + "integrity": "sha512-1pVM9RfOQ+n/N5PJK33kRSKsr1glNxomxONs5c49MliinBY6Yw2Q995qfBUUo0/Mbg05B/sGA0gkgPI7kmSHBg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.0.tgz", + "integrity": "sha512-n7kDRfx+LB1zLueRDvZ1Pd0bxdJWDUZNQ/GWoxDn2prnuJKRdxsjulejX/ePkOsLi2tTm6P24mDqlMSgQpsT6g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.0", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0", + "@radix-ui/react-use-escape-keydown": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.0.tgz", + "integrity": "sha512-UagjDk4ijOAnGu4WMUPj9ahi7/zJJqNZ9ZAiGPp7waUWJO0O1aWXi/udPphI0IUjvrhBsZJGSN66dR2dsueLWQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.0.tgz", + "integrity": "sha512-C4SWtsULLGf/2L4oGeIHlvWQx7Rf+7cX/vKOAD2dXW0A1b5QXwi3wWeaEgW+wn+SEVrraMUk05vLU9fZZz5HbQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-primitive": "1.0.0", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.0.tgz", + "integrity": "sha512-Q6iAB/U7Tq3NTolBBQbHTgclPmGWE3OlktGGqrClPozSw4vkQ1DfQAOtzgRPecKsMdJINE05iaoDUG8tRzCBjw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.0.tgz", + "integrity": "sha512-a8qyFO/Xb99d8wQdu4o7qnigNjTPG123uADNecz0eX4usnQEj7o+cG4ZX4zkqq98NYekT7UoEQIjxBNWIFuqTA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.0.tgz", + "integrity": "sha512-A+6XEvN01NfVWiKu38ybawfHsBjWum42MRPnEuqPsBZ4eV7e/7K321B5VgYMPv3Xx5An6o1/l9ZuDBgmcmWK3w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0", + "@radix-ui/react-use-layout-effect": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.0.tgz", + "integrity": "sha512-EyXe6mnRlHZ8b6f4ilTDrXmkLShICIuOTTj0GX4w1rp+wSxf3+TD05u1UOITC8VsJ2a9nwHvdXtOXEOl0Cw/zQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.0.tgz", + "integrity": "sha512-3mrKauI/tWXo1Ll+gN5dHcxDPdm/Df1ufcDLCecn+pnCIVcdWE7CujXo8QaXOWRJyZyQWWbpB8eFwHzWXlv5mQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.0.tgz", + "integrity": "sha512-GZtyzoHz95Rhs6S63D2t/eqvdFCm7I+yHMLVQheKM7nBD8mbZIt+ct1jz4536MDnaOGKIxynJ8eHTkVGVVkoTg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.0.tgz", + "integrity": "sha512-FohDoZvk3mEXh9AWAVyRTYR4Sq7/gavuofglmiXB2g1aKyboUD4YtgWxKj8O5n+Uak52gXQ4wKz5IFST4vtJHg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.0.tgz", + "integrity": "sha512-JwfBCUIfhXRxKExgIqGa4CQsiMemo1Xt0W/B4ei3fpzpvPENKpMKQ8mZSB6Acj3ebrAEgi2xiQvcI1PAAodvyg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.0" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.0.tgz", + "integrity": "sha512-6Tpkq+R6LOlmQb1R5NNETLG0B4YP0wc+klfXafpUCj6JGyaUc8il7/kUZ7m59rGbXGczE9Bs+iz2qloqsZBduQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0" + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.4.tgz", + "integrity": "sha512-xGVKJJr0SJGQVirVFAUZ2k1QLyO6m+2fy0l8Qawbp5Jgrv3DeLalrfMNBFSlmz5kriGGzsVBtGVnf4pTKIhhWA==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.0.7.tgz", + "integrity": "sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", + "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", + "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cmdk": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-0.2.0.tgz", + "integrity": "sha512-JQpKvEOb86SnvMZbYaFKYhvzFntWBeSZdyii0rZPhKJj9uwJBxu4DaVYDrRN7r3mPop56oPhRw+JYWTKs66TYw==", + "dependencies": { + "@radix-ui/react-dialog": "1.0.0", + "command-score": "0.1.2" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/command-score": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/command-score/-/command-score-0.1.2.tgz", + "integrity": "sha512-VtDvQpIJBvBatnONUsPzXYFVKQQAhuf3XTNOAsdBxCNO/QCtUUd8LSgjn0GVarBkCad6aJCZfXgrjYbl/KRr7w==" + }, + "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==" + }, + "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==", + "engines": { + "node": ">=6" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "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==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/react": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", + "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", + "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.0" + }, + "peerDependencies": { + "react": "^18.2.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.4.tgz", + "integrity": "sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/regenerator-runtime": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", + "integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==" + }, + "node_modules/scheduler": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", + "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" + }, + "node_modules/use-callback-ref": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.0.tgz", + "integrity": "sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..33d31f0d1 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "@radix-ui/react-popover": "^1.0.7", + "cmdk": "^0.2.0" + } +} diff --git a/poetry.lock b/poetry.lock index 68e6c22bc..e7f0ae2ac 100644 --- a/poetry.lock +++ b/poetry.lock @@ -410,48 +410,6 @@ files = [ {file = "billiard-4.2.0.tar.gz", hash = "sha256:9a3c3184cb275aa17a732f93f65b20c525d3d9f253722d26a82194803ade5a2c"}, ] -[[package]] -name = "black" -version = "23.11.0" -description = "The uncompromising code formatter." -optional = false -python-versions = ">=3.8" -files = [ - {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"}, - {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"}, - {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"}, - {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"}, - {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"}, - {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"}, - {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"}, - {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"}, - {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"}, - {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"}, - {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"}, - {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"}, - {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"}, - {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"}, - {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"}, - {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"}, - {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"}, - {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"}, -] - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -packaging = ">=22.0" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} -typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - [[package]] name = "blinker" version = "1.7.0" @@ -8927,4 +8885,4 @@ local = ["ctransformers", "llama-cpp-python", "sentence-transformers"] [metadata] lock-version = "2.0" python-versions = ">=3.9,<3.11" -content-hash = "27f6f86ca62ee5cfdae333daae4f043a745ed2fff0d958a201b514fac36068d4" +content-hash = "ab18ac504d15c9a85e1fe6457df0bef60aeeb96ed78d7281fbe3eb7c9017c8f4" diff --git a/pyproject.toml b/pyproject.toml index 3481d3ef9..d561e38a7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ passlib = "^1.7.4" bcrypt = "^4.0.1" python-jose = "^3.3.0" metaphor-python = "^0.1.11" -pydantic = "^2.4.0" +pydantic = "^2.0.0" pydantic-settings = "^2.0.3" zep-python = { version = "^1.3.0", allow-prereleases = true } pywin32 = { version = "^306", markers = "sys_platform == 'win32'" } @@ -105,9 +105,8 @@ pgvector = "^0.2.3" [tool.poetry.group.dev.dependencies] types-redis = "^4.6.0.5" -black = "^23.10.0" ipykernel = "^6.21.2" -mypy = "^1.6.1" +mypy = "^1.1.1" ruff = "^0.1.5" httpx = "*" pytest = "^7.4.2" @@ -144,6 +143,7 @@ markers = ["async_test"] [tool.ruff] +exclude = ["src/backend/langflow/alembic/*"] line-length = 120 [build-system] diff --git a/src/backend/langflow/__main__.py b/src/backend/langflow/__main__.py index 1bdcf97f2..515613c23 100644 --- a/src/backend/langflow/__main__.py +++ b/src/backend/langflow/__main__.py @@ -11,7 +11,7 @@ import typer from dotenv import load_dotenv from langflow.main import setup_app from langflow.services.database.utils import session_getter -from langflow.services.getters import get_db_service, get_settings_service +from langflow.services.deps import get_db_service, get_settings_service from langflow.services.utils import initialize_services, initialize_settings_service from langflow.utils.logger import configure, logger from multiprocess import Process, cpu_count # type: ignore @@ -72,6 +72,7 @@ def update_settings( dev: bool = False, remove_api_keys: bool = False, components_path: Optional[Path] = None, + store: bool = True, ): """Update the settings from a config file.""" @@ -90,16 +91,15 @@ def update_settings( if components_path: logger.debug(f"Adding component path {components_path}") settings_service.settings.update_settings(COMPONENTS_PATH=components_path) + if not store: + logger.debug("Setting store to False") + settings_service.settings.update_settings(STORE=False) @app.command() def run( - host: str = typer.Option( - "127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST" - ), - workers: int = typer.Option( - 1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS" - ), + host: str = typer.Option("127.0.0.1", help="Host to bind the server to.", envvar="LANGFLOW_HOST"), + workers: int = typer.Option(1, help="Number of worker processes.", envvar="LANGFLOW_WORKERS"), timeout: int = typer.Option(300, help="Worker timeout in seconds."), port: int = typer.Option(7860, help="Port to listen on.", envvar="LANGFLOW_PORT"), components_path: Optional[Path] = typer.Option( @@ -107,32 +107,17 @@ def run( help="Path to the directory containing custom components.", envvar="LANGFLOW_COMPONENTS_PATH", ), - config: str = typer.Option( - Path(__file__).parent / "config.yaml", help="Path to the configuration file." - ), + config: str = typer.Option(Path(__file__).parent / "config.yaml", help="Path to the configuration file."), # .env file param - env_file: Path = typer.Option( - None, help="Path to the .env file containing environment variables." - ), - log_level: str = typer.Option( - "critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL" - ), - log_file: Path = typer.Option( - "logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE" - ), + env_file: Path = typer.Option(None, help="Path to the .env file containing environment variables."), + log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"), + log_file: Path = typer.Option("logs/langflow.log", help="Path to the log file.", envvar="LANGFLOW_LOG_FILE"), cache: Optional[str] = typer.Option( envvar="LANGFLOW_LANGCHAIN_CACHE", help="Type of cache to use. (InMemoryCache, SQLiteCache)", default=None, ), dev: bool = typer.Option(False, help="Run in development mode (may contain bugs)"), - # This variable does not work but is set by the .env file - # and works with Pydantic - # database_url: str = typer.Option( - # None, - # help="Database URL to connect to. If not provided, a local SQLite database will be used.", - # envvar="LANGFLOW_DATABASE_URL", - # ), path: str = typer.Option( None, help="Path to the frontend directory containing build files. This is for development purposes only.", @@ -153,6 +138,11 @@ def run( help="Run only the backend server without the frontend.", envvar="LANGFLOW_BACKEND_ONLY", ), + store: bool = typer.Option( + True, + help="Enables the store features.", + envvar="LANGFLOW_STORE", + ), ): """ Run the Langflow. @@ -171,6 +161,7 @@ def run( remove_api_keys=remove_api_keys, cache=cache, components_path=components_path, + store=store, ) # create path object if path is provided static_files_dir: Optional[Path] = Path(path) if path else None @@ -200,9 +191,7 @@ def run( def run_on_mac_or_linux(host, port, log_level, options, app, open_browser=True): - webapp_process = Process( - target=run_langflow, args=(host, port, log_level, options, app) - ) + webapp_process = Process(target=run_langflow, args=(host, port, log_level, options, app)) webapp_process.start() status_code = 0 while status_code != 200: @@ -278,9 +267,7 @@ def print_banner(host, port): ) # Create a panel with the title and the info text, and a border around it - panel = Panel( - f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False - ) + panel = Panel(f"{title}\n{info_text}", box=box.ROUNDED, border_style="blue", expand=False) # Print the banner with a separator line before and after rprint(panel) @@ -312,12 +299,8 @@ def run_langflow(host, port, log_level, options, app): @app.command() def superuser( username: str = typer.Option(..., prompt=True, help="Username for the superuser."), - password: str = typer.Option( - ..., prompt=True, hide_input=True, help="Password for the superuser." - ), - log_level: str = typer.Option( - "critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL" - ), + password: str = typer.Option(..., prompt=True, hide_input=True, help="Password for the superuser."), + log_level: str = typer.Option("critical", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"), ): """ Create a superuser. diff --git a/src/backend/langflow/alembic/env.py b/src/backend/langflow/alembic/env.py index e606036f1..283b24a6f 100644 --- a/src/backend/langflow/alembic/env.py +++ b/src/backend/langflow/alembic/env.py @@ -5,7 +5,7 @@ from sqlalchemy import pool from alembic import context -from langflow.services.database.manager import SQLModel +from langflow.services.database.service import SQLModel # this is the Alembic Config object, which provides # access to the values within the .ini file in use. diff --git a/src/backend/langflow/alembic/versions/7843803a87b5_store_updates.py b/src/backend/langflow/alembic/versions/7843803a87b5_store_updates.py new file mode 100644 index 000000000..ca82479bc --- /dev/null +++ b/src/backend/langflow/alembic/versions/7843803a87b5_store_updates.py @@ -0,0 +1,48 @@ +"""Store updates + +Revision ID: 7843803a87b5 +Revises: eb5866d51fd2 +Create Date: 2023-10-18 23:08:57.744906 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = "7843803a87b5" +down_revision: Union[str, None] = "eb5866d51fd2" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + try: + with op.batch_alter_table("flow", schema=None) as batch_op: + batch_op.add_column(sa.Column("is_component", sa.Boolean(), nullable=True)) + + with op.batch_alter_table("user", schema=None) as batch_op: + batch_op.add_column( + sa.Column( + "store_api_key", sqlmodel.sql.sqltypes.AutoString(), nullable=True + ) + ) + except Exception: + pass + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("user", schema=None) as batch_op: + batch_op.drop_column("store_api_key") + + with op.batch_alter_table("flow", schema=None) as batch_op: + batch_op.drop_column("is_component") + + # ### end Alembic commands ### diff --git a/src/backend/langflow/alembic/versions/f5ee9749d1a6_user_id_can_be_null_in_flow.py b/src/backend/langflow/alembic/versions/f5ee9749d1a6_user_id_can_be_null_in_flow.py new file mode 100644 index 000000000..60e19e69e --- /dev/null +++ b/src/backend/langflow/alembic/versions/f5ee9749d1a6_user_id_can_be_null_in_flow.py @@ -0,0 +1,45 @@ +"""User id can be null in Flow + +Revision ID: f5ee9749d1a6 +Revises: 7843803a87b5 +Create Date: 2023-10-18 23:12:27.297016 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +import sqlmodel + + +# revision identifiers, used by Alembic. +revision: str = "f5ee9749d1a6" +down_revision: Union[str, None] = "7843803a87b5" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + try: + with op.batch_alter_table("flow", schema=None) as batch_op: + batch_op.alter_column( + "user_id", existing_type=sa.CHAR(length=32), nullable=True + ) + except Exception: + pass + + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + try: + with op.batch_alter_table("flow", schema=None) as batch_op: + batch_op.alter_column( + "user_id", existing_type=sa.CHAR(length=32), nullable=False + ) + except Exception: + pass + + # ### end Alembic commands ### diff --git a/src/backend/langflow/api/router.py b/src/backend/langflow/api/router.py index dbaf20e75..b87a24ea3 100644 --- a/src/backend/langflow/api/router.py +++ b/src/backend/langflow/api/router.py @@ -5,7 +5,7 @@ from langflow.api.v1 import ( endpoints_router, validate_router, flows_router, - component_router, + store_router, users_router, api_key_router, login_router, @@ -17,7 +17,7 @@ router = APIRouter( router.include_router(chat_router) router.include_router(endpoints_router) router.include_router(validate_router) -router.include_router(component_router) +router.include_router(store_router) router.include_router(flows_router) router.include_router(users_router) router.include_router(api_key_router) diff --git a/src/backend/langflow/api/utils.py b/src/backend/langflow/api/utils.py index c519fffed..79a6b74e2 100644 --- a/src/backend/langflow/api/utils.py +++ b/src/backend/langflow/api/utils.py @@ -2,9 +2,7 @@ API_WORDS = ["api", "key", "token"] def has_api_terms(word: str): - return "api" in word and ( - "key" in word or ("token" in word and "tokens" not in word) - ) + return "api" in word and ("key" in word or ("token" in word and "tokens" not in word)) def remove_api_keys(flow: dict): @@ -14,11 +12,7 @@ def remove_api_keys(flow: dict): node_data = node.get("data").get("node") template = node_data.get("template") for value in template.values(): - if ( - isinstance(value, dict) - and has_api_terms(value["name"]) - and value.get("password") - ): + if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"): value["value"] = None return flow @@ -39,9 +33,7 @@ def build_input_keys_response(langchain_object, artifacts): input_keys_response["input_keys"][key] = value # If the object has memory, that memory will have a memory_variables attribute # memory variables should be removed from the input keys - if hasattr(langchain_object, "memory") and hasattr( - langchain_object.memory, "memory_variables" - ): + if hasattr(langchain_object, "memory") and hasattr(langchain_object.memory, "memory_variables"): # Remove memory variables from input keys input_keys_response["input_keys"] = { key: value @@ -51,9 +43,7 @@ def build_input_keys_response(langchain_object, artifacts): # Add memory variables to memory_keys input_keys_response["memory_keys"] = langchain_object.memory.memory_variables - if hasattr(langchain_object, "prompt") and hasattr( - langchain_object.prompt, "template" - ): + if hasattr(langchain_object, "prompt") and hasattr(langchain_object.prompt, "template"): input_keys_response["template"] = langchain_object.prompt.template return input_keys_response diff --git a/src/backend/langflow/api/v1/__init__.py b/src/backend/langflow/api/v1/__init__.py index 9335a4607..197b9730f 100644 --- a/src/backend/langflow/api/v1/__init__.py +++ b/src/backend/langflow/api/v1/__init__.py @@ -2,7 +2,7 @@ from langflow.api.v1.endpoints import router as endpoints_router from langflow.api.v1.validate import router as validate_router from langflow.api.v1.chat import router as chat_router from langflow.api.v1.flows import router as flows_router -from langflow.api.v1.components import router as component_router +from langflow.api.v1.store import router as store_router from langflow.api.v1.users import router as users_router from langflow.api.v1.api_key import router as api_key_router from langflow.api.v1.login import router as login_router @@ -10,7 +10,7 @@ from langflow.api.v1.login import router as login_router __all__ = [ "chat_router", "endpoints_router", - "component_router", + "store_router", "validate_router", "flows_router", "users_router", diff --git a/src/backend/langflow/api/v1/api_key.py b/src/backend/langflow/api/v1/api_key.py index 7f5916d06..fcd9c934f 100644 --- a/src/backend/langflow/api/v1/api_key.py +++ b/src/backend/langflow/api/v1/api_key.py @@ -1,7 +1,7 @@ from uuid import UUID from fastapi import APIRouter, HTTPException, Depends -from langflow.api.v1.schemas import ApiKeysResponse -from langflow.services.auth.utils import get_current_active_user +from langflow.api.v1.schemas import ApiKeysResponse, ApiKeyCreateRequest +from langflow.services.auth import utils as auth_utils from langflow.services.database.models.api_key.api_key import ( ApiKeyCreate, UnmaskedApiKeyRead, @@ -14,9 +14,17 @@ from langflow.services.database.models.api_key.crud import ( delete_api_key, ) from langflow.services.database.models.user.user import User -from langflow.services.getters import get_session +from langflow.services.deps import ( + get_session, + get_settings_service, +) +from typing import TYPE_CHECKING + + from sqlmodel import Session +if TYPE_CHECKING: + pass router = APIRouter(tags=["APIKey"], prefix="/api_key") @@ -24,7 +32,7 @@ router = APIRouter(tags=["APIKey"], prefix="/api_key") @router.get("/", response_model=ApiKeysResponse) def get_api_keys_route( db: Session = Depends(get_session), - current_user: User = Depends(get_current_active_user), + current_user: User = Depends(auth_utils.get_current_active_user), ): try: user_id = current_user.id @@ -38,7 +46,7 @@ def get_api_keys_route( @router.post("/", response_model=UnmaskedApiKeyRead) def create_api_key_route( req: ApiKeyCreate, - current_user: User = Depends(get_current_active_user), + current_user: User = Depends(auth_utils.get_current_active_user), db: Session = Depends(get_session), ): try: @@ -51,7 +59,7 @@ def create_api_key_route( @router.delete("/{api_key_id}") def delete_api_key_route( api_key_id: UUID, - current_user=Depends(get_current_active_user), + current_user=Depends(auth_utils.get_current_active_user), db: Session = Depends(get_session), ): try: @@ -59,3 +67,21 @@ def delete_api_key_route( return {"detail": "API Key deleted"} except Exception as e: raise HTTPException(status_code=400, detail=str(e)) from e + + +@router.post("/store") +def save_store_api_key( + api_key_request: ApiKeyCreateRequest, + current_user: User = Depends(auth_utils.get_current_active_user), + db: Session = Depends(get_session), + settings_service=Depends(get_settings_service), +): + try: + api_key = api_key_request.api_key + # Encrypt the API key + encrypted = auth_utils.encrypt_api_key(api_key, settings_service=settings_service) + current_user.store_api_key = encrypted + db.commit() + return {"detail": "API Key saved"} + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) from e diff --git a/src/backend/langflow/api/v1/base.py b/src/backend/langflow/api/v1/base.py index af64ccaf3..15bb4e17a 100644 --- a/src/backend/langflow/api/v1/base.py +++ b/src/backend/langflow/api/v1/base.py @@ -81,9 +81,7 @@ def validate_prompt(template: str): # Check if there are invalid characters in the input_variables input_variables = check_input_variables(input_variables) if any(var in INVALID_NAMES for var in input_variables): - raise ValueError( - f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. " - ) + raise ValueError(f"Invalid input variables. None of the variables can be named {', '.join(input_variables)}. ") try: PromptTemplate(template=template, input_variables=input_variables) @@ -134,9 +132,7 @@ def check_input_variables(input_variables: list): return input_variables -def build_error_message( - input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables -): +def build_error_message(input_variables, invalid_chars, wrong_variables, fixed_variables, empty_variables): input_variables_str = ", ".join([f"'{var}'" for var in input_variables]) error_string = f"Invalid input variables: {input_variables_str}. " diff --git a/src/backend/langflow/api/v1/callback.py b/src/backend/langflow/api/v1/callback.py index 787ca9680..a838a8750 100644 --- a/src/backend/langflow/api/v1/callback.py +++ b/src/backend/langflow/api/v1/callback.py @@ -1,19 +1,15 @@ import asyncio +from typing import Any, Dict, List, Optional from uuid import UUID from langchain.callbacks.base import AsyncCallbackHandler, BaseCallbackHandler - -from langflow.api.v1.schemas import ChatResponse, PromptResponse - - -from typing import Any, Dict, List, Optional -from langflow.services.getters import get_chat_service - - -from langflow.utils.util import remove_ansi_escape_codes from langchain.schema import AgentAction, AgentFinish from loguru import logger +from langflow.api.v1.schemas import ChatResponse, PromptResponse +from langflow.services.deps import get_chat_service +from langflow.utils.util import remove_ansi_escape_codes + # https://github.com/hwchase17/chat-langchain/blob/master/callback.py class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): @@ -26,18 +22,16 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): async def on_llm_new_token(self, token: str, **kwargs: Any) -> None: resp = ChatResponse(message=token, type="stream", intermediate_steps="") - await self.websocket.send_json(resp.dict()) + await self.websocket.send_json(resp.model_dump()) - async def on_tool_start( - self, serialized: Dict[str, Any], input_str: str, **kwargs: Any - ) -> Any: + async def on_tool_start(self, serialized: Dict[str, Any], input_str: str, **kwargs: Any) -> Any: """Run when tool starts running.""" resp = ChatResponse( message="", type="stream", intermediate_steps=f"Tool input: {input_str}", ) - await self.websocket.send_json(resp.dict()) + await self.websocket.send_json(resp.model_dump()) async def on_tool_end(self, output: str, **kwargs: Any) -> Any: """Run when tool ends running.""" @@ -68,7 +62,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): try: # This is to emulate the stream of tokens for resp in resps: - await self.websocket.send_json(resp.dict()) + await self.websocket.send_json(resp.model_dump()) except Exception as exc: logger.error(f"Error sending response: {exc}") @@ -94,7 +88,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): resp = PromptResponse( prompt=text, ) - await self.websocket.send_json(resp.dict()) + await self.websocket.send_json(resp.model_dump()) self.chat_service.chat_history.add_message(self.client_id, resp) async def on_agent_action(self, action: AgentAction, **kwargs: Any): @@ -105,10 +99,10 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): logs = log.split("\n") for log in logs: resp = ChatResponse(message="", type="stream", intermediate_steps=log) - await self.websocket.send_json(resp.dict()) + await self.websocket.send_json(resp.model_dump()) else: resp = ChatResponse(message="", type="stream", intermediate_steps=log) - await self.websocket.send_json(resp.dict()) + await self.websocket.send_json(resp.model_dump()) async def on_agent_finish(self, finish: AgentFinish, **kwargs: Any) -> Any: """Run on agent end.""" @@ -117,7 +111,7 @@ class AsyncStreamingLLMCallbackHandler(AsyncCallbackHandler): type="stream", intermediate_steps=finish.log, ) - await self.websocket.send_json(resp.dict()) + await self.websocket.send_json(resp.model_dump()) class StreamingLLMCallbackHandler(BaseCallbackHandler): @@ -132,5 +126,5 @@ class StreamingLLMCallbackHandler(BaseCallbackHandler): resp = ChatResponse(message=token, type="stream", intermediate_steps="") loop = asyncio.get_event_loop() - coroutine = self.websocket.send_json(resp.dict()) + coroutine = self.websocket.send_json(resp.model_dump()) asyncio.run_coroutine_threadsafe(coroutine, loop) diff --git a/src/backend/langflow/api/v1/chat.py b/src/backend/langflow/api/v1/chat.py index c43921ebb..51779b2c9 100644 --- a/src/backend/langflow/api/v1/chat.py +++ b/src/backend/langflow/api/v1/chat.py @@ -18,10 +18,10 @@ from langflow.services.auth.utils import ( ) from langflow.services.cache.utils import update_build_status from loguru import logger -from langflow.services.getters import get_chat_service, get_session, get_cache_service +from langflow.services.deps import get_chat_service, get_session, get_cache_service from sqlmodel import Session -from langflow.services.chat.manager import ChatService -from langflow.services.cache.manager import BaseCacheService +from langflow.services.chat.service import ChatService +from langflow.services.cache.service import BaseCacheService router = APIRouter(tags=["Chat"]) @@ -40,13 +40,9 @@ async def chat( user = await get_current_user_by_jwt(token, db) await websocket.accept() if not user: - await websocket.close( - code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized" - ) + await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized") if not user.is_active: - await websocket.close( - code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized" - ) + await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized") if client_id in chat_service.cache_service: await chat_service.handle_websocket(client_id, websocket) @@ -62,9 +58,7 @@ async def chat( logger.error(f"Error in chat websocket: {exc}") messsage = exc.detail if isinstance(exc, HTTPException) else str(exc) if "Could not validate credentials" in str(exc): - await websocket.close( - code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized" - ) + await websocket.close(code=status.WS_1008_POLICY_VIOLATION, reason="Unauthorized") else: await websocket.close(code=status.WS_1011_INTERNAL_ERROR, reason=messsage) @@ -106,15 +100,10 @@ async def init_build( @router.get("/build/{flow_id}/status", response_model=BuiltResponse) -async def build_status( - flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service) -): +async def build_status(flow_id: str, cache_service: "BaseCacheService" = Depends(get_cache_service)): """Check the flow_id is in the cache_service.""" try: - built = ( - flow_id in cache_service - and cache_service[flow_id]["status"] == BuildStatus.SUCCESS - ) + built = flow_id in cache_service and cache_service[flow_id]["status"] == BuildStatus.SUCCESS return BuiltResponse( built=built, @@ -181,9 +170,7 @@ async def stream_build( params = vertex._built_object_repr() valid = True logger.debug(f"Building node {str(vertex.vertex_type)}") - logger.debug( - f"Output: {params[:100]}{'...' if len(params) > 100 else ''}" - ) + logger.debug(f"Output: {params[:100]}{'...' if len(params) > 100 else ''}") if vertex.artifacts: # The artifacts will be prompt variables # passed to build_input_keys_response @@ -195,9 +182,7 @@ async def stream_build( valid = False update_build_status(cache_service, flow_id, BuildStatus.FAILURE) - vertex_id = ( - vertex.parent_node_id if vertex.parent_is_top_level else vertex.id - ) + vertex_id = vertex.parent_node_id if vertex.parent_is_top_level else vertex.id if vertex_id in graph.top_level_nodes: response = { "valid": valid, @@ -211,9 +196,7 @@ async def stream_build( langchain_object = graph.build() # Now we need to check the input_keys to send them to the client if hasattr(langchain_object, "input_keys"): - input_keys_response = build_input_keys_response( - langchain_object, artifacts - ) + input_keys_response = build_input_keys_response(langchain_object, artifacts) else: input_keys_response = { "input_keys": None, diff --git a/src/backend/langflow/api/v1/components.py b/src/backend/langflow/api/v1/components.py deleted file mode 100644 index d2b39dfd2..000000000 --- a/src/backend/langflow/api/v1/components.py +++ /dev/null @@ -1,77 +0,0 @@ -from datetime import timezone -from typing import List -from uuid import UUID -from langflow.services.database.models.component import Component, ComponentModel -from langflow.services.getters import get_session -from sqlmodel import Session, select -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.exc import IntegrityError -from datetime import datetime - - -COMPONENT_NOT_FOUND = "Component not found" -COMPONENT_ALREADY_EXISTS = "A component with the same id already exists." -COMPONENT_DELETED = "Component deleted" - - -router = APIRouter(prefix="/components", tags=["Components"]) - - -@router.post("/", response_model=Component) -def create_component(component: ComponentModel, db: Session = Depends(get_session)): - db_component = Component(**component.dict()) - try: - db.add(db_component) - db.commit() - db.refresh(db_component) - except IntegrityError as e: - db.rollback() - raise HTTPException( - status_code=400, - detail=COMPONENT_ALREADY_EXISTS, - ) from e - return db_component - - -@router.get("/{component_id}", response_model=Component) -def read_component(component_id: UUID, db: Session = Depends(get_session)): - if component := db.get(Component, component_id): - return component - else: - raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND) - - -@router.get("/", response_model=List[Component]) -def read_components(skip: int = 0, limit: int = 50, db: Session = Depends(get_session)): - query = select(Component) - query = query.offset(skip).limit(limit) - - return db.execute(query).fetchall() - - -@router.patch("/{component_id}", response_model=Component) -def update_component( - component_id: UUID, component: ComponentModel, db: Session = Depends(get_session) -): - db_component = db.get(Component, component_id) - if not db_component: - raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND) - component_data = component.dict(exclude_unset=True) - - for key, value in component_data.items(): - setattr(db_component, key, value) - - db_component.update_at = datetime.now(timezone.utc) - db.commit() - db.refresh(db_component) - return db_component - - -@router.delete("/{component_id}") -def delete_component(component_id: UUID, db: Session = Depends(get_session)): - component = db.get(Component, component_id) - if not component: - raise HTTPException(status_code=404, detail=COMPONENT_NOT_FOUND) - db.delete(component) - db.commit() - return {"detail": COMPONENT_DELETED} diff --git a/src/backend/langflow/api/v1/endpoints.py b/src/backend/langflow/api/v1/endpoints.py index cfad3952c..e61eb6e26 100644 --- a/src/backend/langflow/api/v1/endpoints.py +++ b/src/backend/langflow/api/v1/endpoints.py @@ -7,7 +7,7 @@ from langflow.services.cache.utils import save_uploaded_file from langflow.services.database.models.flow import Flow from langflow.processing.process import process_graph_cached, process_tweaks from langflow.services.database.models.user.user import User -from langflow.services.getters import ( +from langflow.services.deps import ( get_session_service, get_settings_service, get_task_service, @@ -27,7 +27,7 @@ from langflow.api.v1.schemas import ( ) -from langflow.services.getters import get_session +from langflow.services.deps import get_session try: from langflow.worker import process_graph_cached_task @@ -40,7 +40,7 @@ except ImportError: from sqlmodel import Session -from langflow.services.task.manager import TaskService +from langflow.services.task.service import TaskService # build router router = APIRouter(tags=["Base"]) diff --git a/src/backend/langflow/api/v1/flows.py b/src/backend/langflow/api/v1/flows.py index 9953db0db..5e4dde4e0 100644 --- a/src/backend/langflow/api/v1/flows.py +++ b/src/backend/langflow/api/v1/flows.py @@ -1,6 +1,10 @@ from typing import List from uuid import UUID + +import orjson +from fastapi import APIRouter, Depends, File, HTTPException, UploadFile from fastapi.encoders import jsonable_encoder +from sqlmodel import Session from langflow.api.utils import remove_api_keys from langflow.api.v1.schemas import FlowListCreate, FlowListRead @@ -12,13 +16,7 @@ from langflow.services.database.models.flow import ( FlowUpdate, ) from langflow.services.database.models.user.user import User -from langflow.services.getters import get_session -from langflow.services.getters import get_settings_service -import orjson -from sqlmodel import Session -from fastapi import APIRouter, Depends, HTTPException - -from fastapi import File, UploadFile +from langflow.services.deps import get_session, get_settings_service # build router router = APIRouter(prefix="/flows", tags=["Flows"]) @@ -46,7 +44,6 @@ def create_flow( @router.get("/", response_model=list[FlowRead], status_code=200) def read_flows( *, - session: Session = Depends(get_session), current_user: User = Depends(get_current_active_user), ): """Read all flows.""" @@ -65,12 +62,7 @@ def read_flow( current_user: User = Depends(get_current_active_user), ): """Read a flow.""" - if user_flow := ( - session.query(Flow) - .filter(Flow.id == flow_id) - .filter(Flow.user_id == current_user.id) - .first() - ): + if user_flow := (session.query(Flow).filter(Flow.id == flow_id).filter(Flow.user_id == current_user.id).first()): return user_flow else: raise HTTPException(status_code=404, detail="Flow not found") @@ -169,5 +161,5 @@ async def download_file( current_user: User = Depends(get_current_active_user), ): """Download all flows as a file.""" - flows = read_flows(session=session, current_user=current_user) + flows = read_flows(current_user=current_user) return FlowListRead(flows=flows) diff --git a/src/backend/langflow/api/v1/login.py b/src/backend/langflow/api/v1/login.py index 4dfc723b5..6b22a7990 100644 --- a/src/backend/langflow/api/v1/login.py +++ b/src/backend/langflow/api/v1/login.py @@ -2,7 +2,7 @@ from sqlmodel import Session from fastapi import APIRouter, Depends, HTTPException, status from fastapi.security import OAuth2PasswordRequestForm -from langflow.services.getters import get_session +from langflow.services.deps import get_session from langflow.api.v1.schemas import Token from langflow.services.auth.utils import ( authenticate_user, @@ -12,7 +12,7 @@ from langflow.services.auth.utils import ( get_current_active_user, ) -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service router = APIRouter(tags=["Login"]) @@ -44,9 +44,7 @@ async def login_to_get_access_token( @router.get("/auto_login") -async def auto_login( - db: Session = Depends(get_session), settings_service=Depends(get_settings_service) -): +async def auto_login(db: Session = Depends(get_session), settings_service=Depends(get_settings_service)): if settings_service.auth_settings.AUTO_LOGIN: return create_user_longterm_token(db) @@ -60,9 +58,7 @@ async def auto_login( @router.post("/refresh") -async def refresh_token( - token: str, current_user: Session = Depends(get_current_active_user) -): +async def refresh_token(token: str, current_user: Session = Depends(get_current_active_user)): if token: return create_refresh_token(token) else: diff --git a/src/backend/langflow/api/v1/schemas.py b/src/backend/langflow/api/v1/schemas.py index 54bd31659..99a8b08cb 100644 --- a/src/backend/langflow/api/v1/schemas.py +++ b/src/backend/langflow/api/v1/schemas.py @@ -151,9 +151,7 @@ class StreamData(BaseModel): data: dict def __str__(self) -> str: - return ( - f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n" - ) + return f"event: {self.event}\ndata: {orjson_dumps(self.data, indent_2=False)}\n\n" class CustomComponentCode(BaseModel): @@ -200,3 +198,7 @@ class Token(BaseModel): access_token: str refresh_token: str token_type: str + + +class ApiKeyCreateRequest(BaseModel): + api_key: str diff --git a/src/backend/langflow/api/v1/store.py b/src/backend/langflow/api/v1/store.py new file mode 100644 index 000000000..0ab50d0c0 --- /dev/null +++ b/src/backend/langflow/api/v1/store.py @@ -0,0 +1,195 @@ +import warnings +from typing import Annotated, List, Optional, Union +from uuid import UUID + +from fastapi import APIRouter, Depends, HTTPException, Query + +from langflow.services.auth import utils as auth_utils +from langflow.services.database.models.user.user import User +from langflow.services.deps import get_settings_service, get_store_service +from langflow.services.store.exceptions import CustomException +from langflow.services.store.schema import ( + CreateComponentResponse, + DownloadComponentResponse, + ListComponentResponseModel, + StoreComponentCreate, + TagResponse, + UsersLikesResponse, +) +from langflow.services.store.service import StoreService +from langflow.services.store.utils import get_lf_version_from_pypi + +router = APIRouter(prefix="/store", tags=["Components Store"]) + + +def get_user_store_api_key( + user: User = Depends(auth_utils.get_current_active_user), + settings_service=Depends(get_settings_service), +): + if not user.store_api_key: + raise HTTPException(status_code=400, detail="You must have a store API key set.") + decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service) + return decrypted + + +def get_optional_user_store_api_key( + user: User = Depends(auth_utils.get_current_active_user), + settings_service=Depends(get_settings_service), +): + if not user.store_api_key: + return None + decrypted = auth_utils.decrypt_api_key(user.store_api_key, settings_service) + return decrypted + + +@router.get("/check/") +def check_if_store_is_enabled( + settings_service=Depends(get_settings_service), +): + return { + "enabled": settings_service.settings.STORE, + } + + +@router.get("/check/api_key") +async def check_if_store_has_api_key( + api_key: Optional[str] = Depends(get_optional_user_store_api_key), + store_service: StoreService = Depends(get_store_service), +): + if api_key is None: + return {"has_api_key": False, "is_valid": False} + + try: + is_valid = await store_service.check_api_key(api_key) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + + return {"has_api_key": api_key is not None, "is_valid": is_valid} + + +@router.post("/components/", response_model=CreateComponentResponse, status_code=201) +async def share_component( + component: StoreComponentCreate, + store_service: StoreService = Depends(get_store_service), + store_api_Key: str = Depends(get_user_store_api_key), +): + try: + # Verify if this is the latest version of Langflow + # If not, raise an error + if not component.last_tested_version: + # Get the local version of Langflow + from langflow import __version__ as current_version + + component.last_tested_version = current_version + langflow_version = get_lf_version_from_pypi() + if langflow_version is None: + raise HTTPException( + status_code=500, + detail="Unable to verify the latest version of Langflow", + ) + elif langflow_version != component.last_tested_version: + # If the user is using an older version of Langflow, we need to raise an error + # raise ValueError( + warnings.warn( + f"Your version of Langflow ({component.last_tested_version}) is outdated." + f" Please update to the latest version ({langflow_version}) and try again." + ) + + result = await store_service.upload(store_api_Key, component) + return result + except Exception as exc: + raise HTTPException(status_code=400, detail=str(exc)) + + +@router.get("/components/", response_model=ListComponentResponseModel) +async def get_components( + search: Annotated[Optional[str], Query()] = None, + private: Annotated[Optional[bool], Query()] = None, + is_component: Annotated[Optional[bool], Query()] = None, + tags: Annotated[Optional[list[str]], Query()] = None, + sort: Annotated[Union[list[str], None], Query()] = None, + liked: Annotated[bool, Query()] = False, + filter_by_user: Annotated[bool, Query()] = False, + page: int = 1, + limit: int = 10, + store_service: StoreService = Depends(get_store_service), + store_api_Key: Optional[str] = Depends(get_optional_user_store_api_key), +): + try: + return await store_service.get_list_component_response_model( + search=search, + private=private, + is_component=is_component, + tags=tags, + sort=sort, + liked=liked, + filter_by_user=filter_by_user, + page=page, + limit=limit, + store_api_key=store_api_Key, + ) + except CustomException as exc: + raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) from exc + + +@router.get("/components/{component_id}", response_model=DownloadComponentResponse) +async def download_component( + component_id: UUID, + store_service: StoreService = Depends(get_store_service), + store_api_Key: str = Depends(get_user_store_api_key), +): + try: + component = await store_service.download(store_api_Key, component_id) + except CustomException as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) from exc + + if component is None: + raise HTTPException(status_code=400, detail="Component not found") + + return component + + +@router.get("/tags", response_model=List[TagResponse]) +async def get_tags( + store_service: StoreService = Depends(get_store_service), +): + try: + return await store_service.get_tags() + except CustomException as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) + + +@router.get("/users/likes", response_model=List[UsersLikesResponse]) +async def get_list_of_components_liked_by_user( + store_service: StoreService = Depends(get_store_service), + store_api_Key: str = Depends(get_user_store_api_key), +): + try: + return await store_service.get_user_likes(store_api_Key) + except CustomException as exc: + raise HTTPException(status_code=400, detail=str(exc)) from exc + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) + + +@router.post("/users/likes/{component_id}", response_model=UsersLikesResponse) +async def like_component( + component_id: UUID, + store_service: StoreService = Depends(get_store_service), + store_api_Key: str = Depends(get_user_store_api_key), +): + try: + result = await store_service.like_component(store_api_Key, str(component_id)) + likes_count = await store_service.get_component_likes_count(str(component_id), store_api_Key) + + return UsersLikesResponse(likes_count=likes_count, liked_by_user=result) + except CustomException as exc: + raise HTTPException(status_code=exc.status_code, detail=str(exc)) from exc + except Exception as exc: + raise HTTPException(status_code=500, detail=str(exc)) diff --git a/src/backend/langflow/api/v1/users.py b/src/backend/langflow/api/v1/users.py index 73c7346d9..22bab69de 100644 --- a/src/backend/langflow/api/v1/users.py +++ b/src/backend/langflow/api/v1/users.py @@ -13,7 +13,7 @@ from sqlalchemy.exc import IntegrityError from sqlmodel import Session, select from fastapi import APIRouter, Depends, HTTPException -from langflow.services.getters import get_session, get_settings_service +from langflow.services.deps import get_session, get_settings_service from langflow.services.auth.utils import ( get_current_active_superuser, get_current_active_user, @@ -46,9 +46,7 @@ def add_user( session.refresh(new_user) except IntegrityError as e: session.rollback() - raise HTTPException( - status_code=400, detail="This username is unavailable." - ) from e + raise HTTPException(status_code=400, detail="This username is unavailable.") from e return new_user @@ -96,14 +94,10 @@ def patch_user( Update an existing user's data. """ if not user.is_superuser and user.id != user_id: - raise HTTPException( - status_code=403, detail="You don't have the permission to update this user" - ) + raise HTTPException(status_code=403, detail="You don't have the permission to update this user") if user_update.password: if not user.is_superuser: - raise HTTPException( - status_code=400, detail="You can't change your password here" - ) + raise HTTPException(status_code=400, detail="You can't change your password here") user_update.password = get_password_hash(user_update.password) if user_db := get_user_by_id(session, user_id): @@ -123,16 +117,12 @@ def reset_password( Reset a user's password. """ if user_id != user.id: - raise HTTPException( - status_code=400, detail="You can't change another user's password" - ) + raise HTTPException(status_code=400, detail="You can't change another user's password") if not user: raise HTTPException(status_code=404, detail="User not found") if verify_password(user_update.password, user.password): - raise HTTPException( - status_code=400, detail="You can't use your current password" - ) + raise HTTPException(status_code=400, detail="You can't use your current password") new_password = get_password_hash(user_update.password) user.password = new_password session.commit() @@ -151,13 +141,9 @@ def delete_user( Delete a user from the database. """ if current_user.id == user_id: - raise HTTPException( - status_code=400, detail="You can't delete your own user account" - ) + raise HTTPException(status_code=400, detail="You can't delete your own user account") elif not current_user.is_superuser: - raise HTTPException( - status_code=403, detail="You don't have the permission to delete this user" - ) + raise HTTPException(status_code=403, detail="You don't have the permission to delete this user") user_db = session.query(User).filter(User.id == user_id).first() if not user_db: diff --git a/src/backend/langflow/api/v1/validate.py b/src/backend/langflow/api/v1/validate.py index 65fb66bd2..c1ef04e54 100644 --- a/src/backend/langflow/api/v1/validate.py +++ b/src/backend/langflow/api/v1/validate.py @@ -41,9 +41,7 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest): add_new_variables_to_template(input_variables, prompt_request) - remove_old_variables_from_template( - old_custom_fields, input_variables, prompt_request - ) + remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request) update_input_variables_field(input_variables, prompt_request) @@ -58,19 +56,12 @@ def post_validate_prompt(prompt_request: ValidatePromptRequest): def get_old_custom_fields(prompt_request): try: - if ( - len(prompt_request.frontend_node.custom_fields) == 1 - and prompt_request.name == "" - ): + if len(prompt_request.frontend_node.custom_fields) == 1 and prompt_request.name == "": # If there is only one custom field and the name is empty string # then we are dealing with the first prompt request after the node was created - prompt_request.name = list( - prompt_request.frontend_node.custom_fields.keys() - )[0] + prompt_request.name = list(prompt_request.frontend_node.custom_fields.keys())[0] - old_custom_fields = prompt_request.frontend_node.custom_fields[ - prompt_request.name - ].copy() + old_custom_fields = prompt_request.frontend_node.custom_fields[prompt_request.name].copy() except KeyError: old_custom_fields = [] prompt_request.frontend_node.custom_fields[prompt_request.name] = [] @@ -92,40 +83,26 @@ def add_new_variables_to_template(input_variables, prompt_request): ) if variable in prompt_request.frontend_node.template: # Set the new field with the old value - template_field.value = prompt_request.frontend_node.template[variable][ - "value" - ] + template_field.value = prompt_request.frontend_node.template[variable]["value"] prompt_request.frontend_node.template[variable] = template_field.to_dict() # Check if variable is not already in the list before appending - if ( - variable - not in prompt_request.frontend_node.custom_fields[prompt_request.name] - ): - prompt_request.frontend_node.custom_fields[prompt_request.name].append( - variable - ) + if variable not in prompt_request.frontend_node.custom_fields[prompt_request.name]: + prompt_request.frontend_node.custom_fields[prompt_request.name].append(variable) except Exception as exc: logger.exception(exc) raise HTTPException(status_code=500, detail=str(exc)) from exc -def remove_old_variables_from_template( - old_custom_fields, input_variables, prompt_request -): +def remove_old_variables_from_template(old_custom_fields, input_variables, prompt_request): for variable in old_custom_fields: if variable not in input_variables: try: # Remove the variable from custom_fields associated with the given name - if ( - variable - in prompt_request.frontend_node.custom_fields[prompt_request.name] - ): - prompt_request.frontend_node.custom_fields[ - prompt_request.name - ].remove(variable) + if variable in prompt_request.frontend_node.custom_fields[prompt_request.name]: + prompt_request.frontend_node.custom_fields[prompt_request.name].remove(variable) # Remove the variable from the template prompt_request.frontend_node.template.pop(variable, None) @@ -137,6 +114,4 @@ def remove_old_variables_from_template( def update_input_variables_field(input_variables, prompt_request): if "input_variables" in prompt_request.frontend_node.template: - prompt_request.frontend_node.template["input_variables"][ - "value" - ] = input_variables + prompt_request.frontend_node.template["input_variables"]["value"] = input_variables diff --git a/src/backend/langflow/components/agents/OpenAIConversationalAgent.py b/src/backend/langflow/components/agents/OpenAIConversationalAgent.py index 2df026b59..eb53a89c0 100644 --- a/src/backend/langflow/components/agents/OpenAIConversationalAgent.py +++ b/src/backend/langflow/components/agents/OpenAIConversationalAgent.py @@ -72,7 +72,9 @@ class ConversationalAgent(CustomComponent): extra_prompt_messages=[MessagesPlaceholder(variable_name=memory_key)], ) agent = OpenAIFunctionsAgent( - llm=llm, tools=tools, prompt=prompt # type: ignore + llm=llm, + tools=tools, + prompt=prompt, # type: ignore ) return AgentExecutor( agent=agent, diff --git a/src/backend/langflow/components/chains/PromptRunner.py b/src/backend/langflow/components/chains/PromptRunner.py index 15c2743a0..496be610e 100644 --- a/src/backend/langflow/components/chains/PromptRunner.py +++ b/src/backend/langflow/components/chains/PromptRunner.py @@ -18,9 +18,7 @@ class PromptRunner(CustomComponent): "code": {"show": False}, } - def build( - self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {} - ) -> Document: + def build(self, llm: BaseLLM, prompt: PromptTemplate, inputs: dict = {}) -> Document: chain = prompt | llm # The input is an empty dict because the prompt is already filled result = chain.invoke(input=inputs) diff --git a/src/backend/langflow/components/documentloaders/FileLoader.py b/src/backend/langflow/components/documentloaders/FileLoader.py index 04f43d60b..072603a96 100644 --- a/src/backend/langflow/components/documentloaders/FileLoader.py +++ b/src/backend/langflow/components/documentloaders/FileLoader.py @@ -122,9 +122,7 @@ class FileLoaderComponent(CustomComponent): beta = True def build_config(self): - loader_options = ["Automatic"] + [ - loader_info["name"] for loader_info in loaders_info - ] + loader_options = ["Automatic"] + [loader_info["name"] for loader_info in loaders_info] file_types = [] suffixes = [] @@ -214,9 +212,7 @@ class FileLoaderComponent(CustomComponent): if isinstance(selected_loader_info, dict): loader_import: str = selected_loader_info["import"] else: - raise ValueError( - f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}" - ) + raise ValueError(f"Loader info for {loader} is not a dict\nLoader info:\n{selected_loader_info}") module_name, class_name = loader_import.rsplit(".", 1) try: @@ -224,9 +220,7 @@ class FileLoaderComponent(CustomComponent): loader_module = __import__(module_name, fromlist=[class_name]) loader_instance = getattr(loader_module, class_name) except ImportError as e: - raise ValueError( - f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}" - ) from e + raise ValueError(f"Loader {loader} could not be imported\nLoader info:\n{selected_loader_info}") from e result = loader_instance(file_path=file_path) return result.load() diff --git a/src/backend/langflow/components/retrievers/MetalRetriever.py b/src/backend/langflow/components/retrievers/MetalRetriever.py index b105cd24f..88393f26d 100644 --- a/src/backend/langflow/components/retrievers/MetalRetriever.py +++ b/src/backend/langflow/components/retrievers/MetalRetriever.py @@ -18,9 +18,7 @@ class MetalRetrieverComponent(CustomComponent): "code": {"show": False}, } - def build( - self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None - ) -> BaseRetriever: + def build(self, api_key: str, client_id: str, index_id: str, params: Optional[dict] = None) -> BaseRetriever: try: metal = Metal(api_key=api_key, client_id=client_id, index_id=index_id) except Exception as e: diff --git a/src/backend/langflow/components/toolkits/Metaphor.py b/src/backend/langflow/components/toolkits/Metaphor.py index 558224190..0f9f23334 100644 --- a/src/backend/langflow/components/toolkits/Metaphor.py +++ b/src/backend/langflow/components/toolkits/Metaphor.py @@ -1,18 +1,17 @@ from typing import List, Union -from langflow import CustomComponent -from metaphor_python import Metaphor # type: ignore -from langchain.tools import Tool from langchain.agents import tool from langchain.agents.agent_toolkits.base import BaseToolkit +from langchain.tools import Tool +from metaphor_python import Metaphor # type: ignore + +from langflow import CustomComponent class MetaphorToolkit(CustomComponent): display_name: str = "Metaphor" description: str = "Metaphor Toolkit" - documentation = ( - "https://python.langchain.com/docs/integrations/tools/metaphor_search" - ) + documentation = "https://python.langchain.com/docs/integrations/tools/metaphor_search" beta: bool = True # api key should be password = True field_config = { @@ -33,9 +32,7 @@ class MetaphorToolkit(CustomComponent): @tool def search(query: str): """Call search engine with a query.""" - return client.search( - query, use_autoprompt=use_autoprompt, num_results=search_num_results - ) + return client.search(query, use_autoprompt=use_autoprompt, num_results=search_num_results) @tool def get_contents(ids: List[str]): diff --git a/src/backend/langflow/components/utilities/GetRequest.py b/src/backend/langflow/components/utilities/GetRequest.py index 1417a79c5..627d0804a 100644 --- a/src/backend/langflow/components/utilities/GetRequest.py +++ b/src/backend/langflow/components/utilities/GetRequest.py @@ -30,9 +30,7 @@ class GetRequest(CustomComponent): }, } - def get_document( - self, session: requests.Session, url: str, headers: Optional[dict], timeout: int - ) -> Document: + def get_document(self, session: requests.Session, url: str, headers: Optional[dict], timeout: int) -> Document: try: response = session.get(url, headers=headers, timeout=int(timeout)) try: diff --git a/src/backend/langflow/components/utilities/JSONDocumentBuilder.py b/src/backend/langflow/components/utilities/JSONDocumentBuilder.py index 2a166aa4a..b5f3b1263 100644 --- a/src/backend/langflow/components/utilities/JSONDocumentBuilder.py +++ b/src/backend/langflow/components/utilities/JSONDocumentBuilder.py @@ -11,8 +11,8 @@ # - **Document:** The Document containing the JSON object. -from langflow import CustomComponent from langchain.schema import Document +from langflow import CustomComponent from langflow.services.database.models.base import orjson_dumps @@ -20,10 +20,8 @@ class JSONDocumentBuilder(CustomComponent): display_name: str = "JSON Document Builder" description: str = "Build a Document containing a JSON object using a key and another Document page content." output_types: list[str] = ["Document"] - beta: bool = True - documentation: str = ( - "https://docs.langflow.org/components/utilities#json-document-builder" - ) + beta = True + documentation: str = "https://docs.langflow.org/components/utilities#json-document-builder" field_config = { "key": {"display_name": "Key"}, @@ -38,18 +36,11 @@ class JSONDocumentBuilder(CustomComponent): documents = None if isinstance(document, list): documents = [ - Document( - page_content=orjson_dumps({key: doc.page_content}, indent_2=False) - ) - for doc in document + Document(page_content=orjson_dumps({key: doc.page_content}, indent_2=False)) for doc in document ] elif isinstance(document, Document): - documents = Document( - page_content=orjson_dumps({key: document.page_content}, indent_2=False) - ) + documents = Document(page_content=orjson_dumps({key: document.page_content}, indent_2=False)) else: - raise TypeError( - f"Expected Document or list of Documents, got {type(document)}" - ) + raise TypeError(f"Expected Document or list of Documents, got {type(document)}") self.repr_value = documents return documents diff --git a/src/backend/langflow/components/utilities/PostRequest.py b/src/backend/langflow/components/utilities/PostRequest.py index fc61618ba..cbecac535 100644 --- a/src/backend/langflow/components/utilities/PostRequest.py +++ b/src/backend/langflow/components/utilities/PostRequest.py @@ -65,16 +65,12 @@ class PostRequest(CustomComponent): if not isinstance(document, list) and isinstance(document, Document): documents: list[Document] = [document] - elif isinstance(document, list) and all( - isinstance(doc, Document) for doc in document - ): + elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document): documents = document else: raise ValueError("document must be a Document or a list of Documents") with requests.Session() as session: - documents = [ - self.post_document(session, doc, url, headers) for doc in documents - ] + documents = [self.post_document(session, doc, url, headers) for doc in documents] self.repr_value = documents return documents diff --git a/src/backend/langflow/components/utilities/UpdateRequest.py b/src/backend/langflow/components/utilities/UpdateRequest.py index 2e0e82b2a..1e6355b2c 100644 --- a/src/backend/langflow/components/utilities/UpdateRequest.py +++ b/src/backend/langflow/components/utilities/UpdateRequest.py @@ -39,9 +39,7 @@ class UpdateRequest(CustomComponent): ) -> Document: try: if method == "PATCH": - response = session.patch( - url, headers=headers, data=document.page_content - ) + response = session.patch(url, headers=headers, data=document.page_content) elif method == "PUT": response = session.put(url, headers=headers, data=document.page_content) else: @@ -78,17 +76,12 @@ class UpdateRequest(CustomComponent): if not isinstance(document, list) and isinstance(document, Document): documents: list[Document] = [document] - elif isinstance(document, list) and all( - isinstance(doc, Document) for doc in document - ): + elif isinstance(document, list) and all(isinstance(doc, Document) for doc in document): documents = document else: raise ValueError("document must be a Document or a list of Documents") with requests.Session() as session: - documents = [ - self.update_document(session, doc, url, headers, method) - for doc in documents - ] + documents = [self.update_document(session, doc, url, headers, method) for doc in documents] self.repr_value = documents return documents diff --git a/src/backend/langflow/components/vectorstores/Chroma.py b/src/backend/langflow/components/vectorstores/Chroma.py index a94c3c0a7..dd65e26c8 100644 --- a/src/backend/langflow/components/vectorstores/Chroma.py +++ b/src/backend/langflow/components/vectorstores/Chroma.py @@ -86,8 +86,7 @@ class ChromaComponent(CustomComponent): if chroma_server_host is not None: chroma_settings = chromadb.config.Settings( - chroma_server_cors_allow_origins=chroma_server_cors_allow_origins - or None, + chroma_server_cors_allow_origins=chroma_server_cors_allow_origins or None, chroma_server_host=chroma_server_host, chroma_server_port=chroma_server_port or None, chroma_server_grpc_port=chroma_server_grpc_port or None, @@ -104,6 +103,4 @@ class ChromaComponent(CustomComponent): client_settings=chroma_settings, ) - return Chroma( - persist_directory=persist_directory, client_settings=chroma_settings - ) + return Chroma(persist_directory=persist_directory, client_settings=chroma_settings) diff --git a/src/backend/langflow/components/vectorstores/Vectara.py b/src/backend/langflow/components/vectorstores/Vectara.py index 4f0891a2a..1f0d10179 100644 --- a/src/backend/langflow/components/vectorstores/Vectara.py +++ b/src/backend/langflow/components/vectorstores/Vectara.py @@ -1,19 +1,17 @@ from typing import Optional, Union -from langflow import CustomComponent +from langchain.schema import BaseRetriever, Document from langchain.vectorstores import Vectara -from langchain.schema import Document from langchain.vectorstores.base import VectorStore -from langchain.schema import BaseRetriever + +from langflow import CustomComponent class VectaraComponent(CustomComponent): display_name: str = "Vectara" description: str = "Implementation of Vector Store using Vectara" - documentation = ( - "https://python.langchain.com/docs/integrations/vectorstores/vectara" - ) - beta: bool = True + documentation = "https://python.langchain.com/docs/integrations/vectorstores/vectara" + beta = True # api key should be password = True field_config = { "vectara_customer_id": {"display_name": "Vectara Customer ID"}, diff --git a/src/backend/langflow/components/vectorstores/pgvector.py b/src/backend/langflow/components/vectorstores/pgvector.py index eecf4d187..4e0c2eb4d 100644 --- a/src/backend/langflow/components/vectorstores/pgvector.py +++ b/src/backend/langflow/components/vectorstores/pgvector.py @@ -14,9 +14,7 @@ class PostgresqlVectorComponent(CustomComponent): display_name: str = "PGVector" description: str = "Implementation of Vector Store using PostgreSQL" - documentation = ( - "https://python.langchain.com/docs/integrations/vectorstores/pgvector" - ) + documentation = "https://python.langchain.com/docs/integrations/vectorstores/pgvector" beta = True def build_config(self): diff --git a/src/backend/langflow/field_typing/__init__.py b/src/backend/langflow/field_typing/__init__.py index ceba9fded..11765bd03 100644 --- a/src/backend/langflow/field_typing/__init__.py +++ b/src/backend/langflow/field_typing/__init__.py @@ -1,40 +1,25 @@ -# LANGCHAIN_BASE_TYPES = { -# "Chain": Chain, -# "AgentExecutor": AgentExecutor, -# "Tool": Tool, -# "BaseLLM": BaseLLM, -# "PromptTemplate": PromptTemplate, -# "BaseLoader": BaseLoader, -# "Document": Document, -# "TextSplitter": TextSplitter, -# "VectorStore": VectorStore, -# "Embeddings": Embeddings, -# "BaseRetriever": BaseRetriever, -# "BaseOutputParser": BaseOutputParser, -# "BaseMemory": BaseMemory, -# "BaseChatMemory": BaseChatMemory, -# } from .constants import ( - Tool, - PromptTemplate, - ChatPromptTemplate, - BasePromptTemplate, - Chain, + AgentExecutor, BaseChatMemory, + BaseLanguageModel, BaseLLM, BaseLoader, BaseMemory, BaseOutputParser, + BasePromptTemplate, BaseRetriever, - VectorStore, - Embeddings, - TextSplitter, - Document, - AgentExecutor, - NestedDict, - Data, - BaseLanguageModel, Callable, + Chain, + ChatPromptTemplate, + Data, + Document, + Embeddings, + NestedDict, + Object, + PromptTemplate, + TextSplitter, + Tool, + VectorStore, ) __all__ = [ @@ -55,6 +40,7 @@ __all__ = [ "TextSplitter", "Document", "AgentExecutor", + "Object", "Callable", "BasePromptTemplate", "ChatPromptTemplate", diff --git a/src/backend/langflow/field_typing/constants.py b/src/backend/langflow/field_typing/constants.py index 4e508ba61..646af0bac 100644 --- a/src/backend/langflow/field_typing/constants.py +++ b/src/backend/langflow/field_typing/constants.py @@ -1,21 +1,26 @@ +from typing import Callable, Dict, Union + from langchain.agents.agent import AgentExecutor from langchain.chains.base import Chain from langchain.document_loaders.base import BaseLoader -from langchain.llms.base import BaseLLM, BaseLanguageModel +from langchain.llms.base import BaseLanguageModel, BaseLLM from langchain.memory.chat_memory import BaseChatMemory -from langchain.prompts import PromptTemplate, ChatPromptTemplate, BasePromptTemplate +from langchain.prompts import BasePromptTemplate, ChatPromptTemplate, PromptTemplate from langchain.schema import BaseOutputParser, BaseRetriever, Document from langchain.schema.embeddings import Embeddings from langchain.schema.memory import BaseMemory from langchain.text_splitter import TextSplitter from langchain.tools import Tool from langchain.vectorstores.base import VectorStore -from typing import Union, Dict, Callable # Type alias for more complex dicts NestedDict = Dict[str, Union[str, Dict]] +class Object: + pass + + class Data: pass @@ -50,5 +55,6 @@ CUSTOM_COMPONENT_SUPPORTED_TYPES = { "dict": dict, "NestedDict": NestedDict, "Data": Data, + "Object": Object, "Callable": Callable, } diff --git a/src/backend/langflow/graph/edge/base.py b/src/backend/langflow/graph/edge/base.py index 82714e395..f9d77741b 100644 --- a/src/backend/langflow/graph/edge/base.py +++ b/src/backend/langflow/graph/edge/base.py @@ -8,9 +8,7 @@ if TYPE_CHECKING: class SourceHandle(BaseModel): - baseClasses: List[str] = Field( - ..., description="List of base classes for the source handle." - ) + baseClasses: List[str] = Field(..., description="List of base classes for the source handle.") dataType: str = Field(..., description="Data type for the source handle.") id: str = Field(..., description="Unique identifier for the source handle.") @@ -18,9 +16,7 @@ class SourceHandle(BaseModel): class TargetHandle(BaseModel): fieldName: str = Field(..., description="Field name for the target handle.") id: str = Field(..., description="Unique identifier for the target handle.") - inputTypes: Optional[List[str]] = Field( - None, description="List of input types for the target handle." - ) + inputTypes: Optional[List[str]] = Field(None, description="List of input types for the target handle.") type: str = Field(..., description="Type of the target handle.") @@ -49,23 +45,17 @@ class Edge: def validate_handles(self) -> None: if self.target_handle.inputTypes is None: - self.valid_handles = ( - self.target_handle.type in self.source_handle.baseClasses - ) + self.valid_handles = self.target_handle.type in self.source_handle.baseClasses else: self.valid_handles = ( - any( - baseClass in self.target_handle.inputTypes - for baseClass in self.source_handle.baseClasses - ) + any(baseClass in self.target_handle.inputTypes for baseClass in self.source_handle.baseClasses) or self.target_handle.type in self.source_handle.baseClasses ) if not self.valid_handles: logger.debug(self.source_handle) logger.debug(self.target_handle) raise ValueError( - f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " - f"has invalid handles" + f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has invalid handles" ) def __setstate__(self, state): @@ -87,11 +77,7 @@ class Edge: # Both lists contain strings and sometimes a string contains the value we are # looking for e.g. comgin_out=["Chain"] and target_reqs=["LLMChain"] # so we need to check if any of the strings in source_types is in target_reqs - self.valid = any( - output in target_req - for output in self.source_types - for target_req in self.target_reqs - ) + self.valid = any(output in target_req for output in self.source_types for target_req in self.target_reqs) # Get what type of input the target node is expecting self.matched_type = next( @@ -103,8 +89,7 @@ class Edge: logger.debug(self.source_types) logger.debug(self.target_reqs) raise ValueError( - f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " - f"has no matched type" + f"Edge between {self.source.vertex_type} and {self.target.vertex_type} " f"has no matched type" ) def __repr__(self) -> str: @@ -117,8 +102,4 @@ class Edge: return hash(self.__repr__()) def __eq__(self, __value: object) -> bool: - return ( - self.__repr__() == __value.__repr__() - if isinstance(__value, Edge) - else False - ) + return self.__repr__() == __value.__repr__() if isinstance(__value, Edge) else False diff --git a/src/backend/langflow/graph/graph/base.py b/src/backend/langflow/graph/graph/base.py index 83976ae49..aeb7ec91d 100644 --- a/src/backend/langflow/graph/graph/base.py +++ b/src/backend/langflow/graph/graph/base.py @@ -104,9 +104,7 @@ class Graph: return for node in self.nodes: if not self._validate_node(node): - raise ValueError( - f"{node.vertex_type} is not connected to any other components" - ) + raise ValueError(f"{node.vertex_type} is not connected to any other components") def _validate_node(self, node: Vertex) -> bool: """Validates a node.""" @@ -119,9 +117,7 @@ class Graph: def get_nodes_with_target(self, node: Vertex) -> List[Vertex]: """Returns the nodes connected to a node.""" - connected_nodes: List[Vertex] = [ - edge.source for edge in self.edges if edge.target == node - ] + connected_nodes: List[Vertex] = [edge.source for edge in self.edges if edge.target == node] return connected_nodes def build(self) -> Chain: @@ -149,9 +145,7 @@ class Graph: def dfs(node): if state[node] == 1: # We have a cycle - raise ValueError( - "Graph contains a cycle, cannot perform topological sort" - ) + raise ValueError("Graph contains a cycle, cannot perform topological sort") if state[node] == 0: state[node] = 1 for edge in node.edges: @@ -245,7 +239,5 @@ class Graph: def __repr__(self): node_ids = [node.id for node in self.nodes] - edges_repr = "\n".join( - [f"{edge.source.id} --> {edge.target.id}" for edge in self.edges] - ) + edges_repr = "\n".join([f"{edge.source.id} --> {edge.target.id}" for edge in self.edges]) return f"Graph:\nNodes: {node_ids}\nConnections:\n{edges_repr}" diff --git a/src/backend/langflow/graph/graph/constants.py b/src/backend/langflow/graph/graph/constants.py index c9fea48b5..abfc2970f 100644 --- a/src/backend/langflow/graph/graph/constants.py +++ b/src/backend/langflow/graph/graph/constants.py @@ -47,10 +47,7 @@ class VertexTypesDict(LazyLoadDictBase): **{t: types.DocumentLoaderVertex for t in documentloader_creator.to_list()}, **{t: types.TextSplitterVertex for t in textsplitter_creator.to_list()}, **{t: types.OutputParserVertex for t in output_parser_creator.to_list()}, - **{ - t: types.CustomComponentVertex - for t in custom_component_creator.to_list() - }, + **{t: types.CustomComponentVertex for t in custom_component_creator.to_list()}, **{t: types.RetrieverVertex for t in retriever_creator.to_list()}, } diff --git a/src/backend/langflow/graph/graph/utils.py b/src/backend/langflow/graph/graph/utils.py index 71b81fea1..a3299739e 100644 --- a/src/backend/langflow/graph/graph/utils.py +++ b/src/backend/langflow/graph/graph/utils.py @@ -28,23 +28,14 @@ def ungroup_node(group_node_data, base_flow): g_edges = flow["data"]["edges"] # Redirect edges to the correct proxy node - updated_edges = get_updated_edges( - base_flow, g_nodes, g_edges, group_node_data["id"] - ) + updated_edges = get_updated_edges(base_flow, g_nodes, g_edges, group_node_data["id"]) # Update template values update_template(template, g_nodes) - nodes = [ - n for n in base_flow["nodes"] if n["id"] != group_node_data["id"] - ] + g_nodes + nodes = [n for n in base_flow["nodes"] if n["id"] != group_node_data["id"]] + g_nodes edges = ( - [ - e - for e in base_flow["edges"] - if e["target"] != group_node_data["id"] - and e["source"] != group_node_data["id"] - ] + [e for e in base_flow["edges"] if e["target"] != group_node_data["id"] and e["source"] != group_node_data["id"]] + g_edges + updated_edges ) @@ -66,11 +57,7 @@ def process_flow(flow_object): if node_id in processed_nodes: return - if ( - node.get("data") - and node["data"].get("node") - and node["data"]["node"].get("flow") - ): + if node.get("data") and node["data"].get("node") and node["data"]["node"].get("flow"): process_flow(node["data"]["node"]["flow"]["data"]) new_nodes = ungroup_node(node["data"], cloned_flow) # Add new nodes to the queue for future processing @@ -108,26 +95,16 @@ def update_template(template, g_nodes): if node_index != -1: display_name = None show = g_nodes[node_index]["data"]["node"]["template"][field]["show"] - advanced = g_nodes[node_index]["data"]["node"]["template"][field][ - "advanced" - ] + advanced = g_nodes[node_index]["data"]["node"]["template"][field]["advanced"] if "display_name" in g_nodes[node_index]["data"]["node"]["template"][field]: - display_name = g_nodes[node_index]["data"]["node"]["template"][field][ - "display_name" - ] + display_name = g_nodes[node_index]["data"]["node"]["template"][field]["display_name"] else: - display_name = g_nodes[node_index]["data"]["node"]["template"][field][ - "name" - ] + display_name = g_nodes[node_index]["data"]["node"]["template"][field]["name"] g_nodes[node_index]["data"]["node"]["template"][field] = value g_nodes[node_index]["data"]["node"]["template"][field]["show"] = show - g_nodes[node_index]["data"]["node"]["template"][field][ - "advanced" - ] = advanced - g_nodes[node_index]["data"]["node"]["template"][field][ - "display_name" - ] = display_name + g_nodes[node_index]["data"]["node"]["template"][field]["advanced"] = advanced + g_nodes[node_index]["data"]["node"]["template"][field]["display_name"] = display_name def update_target_handle(new_edge, g_nodes, group_node_id): diff --git a/src/backend/langflow/graph/vertex/base.py b/src/backend/langflow/graph/vertex/base.py index 88a4db26e..5862ca3ae 100644 --- a/src/backend/langflow/graph/vertex/base.py +++ b/src/backend/langflow/graph/vertex/base.py @@ -51,9 +51,7 @@ class Vertex: self.params.pop(target_param, None) continue - if target_param in self.params and not is_basic_type( - self.params[target_param] - ): + if target_param in self.params and not is_basic_type(self.params[target_param]): # edge.source.params = {} edge.source._build_params() edge.source._built_object = UnbuiltObject() @@ -99,29 +97,17 @@ class Vertex: def _parse_data(self) -> None: self.data = self._data["data"] self.output = self.data["node"]["base_classes"] - template_dicts = { - key: value - for key, value in self.data["node"]["template"].items() - if isinstance(value, dict) - } + template_dicts = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)} self.required_inputs = [ - template_dicts[key]["type"] - for key, value in template_dicts.items() - if value["required"] + template_dicts[key]["type"] for key, value in template_dicts.items() if value["required"] ] self.optional_inputs = [ - template_dicts[key]["type"] - for key, value in template_dicts.items() - if not value["required"] + template_dicts[key]["type"] for key, value in template_dicts.items() if not value["required"] ] # Add the template_dicts[key]["input_types"] to the optional_inputs self.optional_inputs.extend( - [ - input_type - for value in template_dicts.values() - for input_type in value.get("input_types", []) - ] + [input_type for value in template_dicts.values() for input_type in value.get("input_types", [])] ) template_dict = self.data["node"]["template"] @@ -160,11 +146,7 @@ class Vertex: # and use that as the value for the param # If the type is "str", then we need to get the value of the "value" key # and use that as the value for the param - template_dict = { - key: value - for key, value in self.data["node"]["template"].items() - if isinstance(value, dict) - } + template_dict = {key: value for key, value in self.data["node"]["template"].items() if isinstance(value, dict)} params = self.params.copy() if self.params else {} for edge in self.edges: @@ -209,11 +191,7 @@ class Vertex: # before passing it to the build method _value = value.get("value") if isinstance(_value, list): - params[key] = { - k: v - for item in value.get("value", []) - for k, v in item.items() - } + params[key] = {k: v for item in value.get("value", []) for k, v in item.items()} elif isinstance(_value, dict): params[key] = _value elif value.get("type") == "int" and value.get("value") is not None: @@ -304,9 +282,7 @@ class Vertex: self._extend_params_list_with_result(key, result) self.params[key] = result - def _build_list_of_nodes_and_update_params( - self, key, nodes: List["Vertex"], user_id=None - ): + def _build_list_of_nodes_and_update_params(self, key, nodes: List["Vertex"], user_id=None): """ Iterates over a list of nodes, builds each and updates the params dictionary. """ @@ -358,9 +334,7 @@ class Vertex: self._update_built_object_and_artifacts(result) except Exception as exc: logger.exception(exc) - raise ValueError( - f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}" - ) from exc + raise ValueError(f"Error building node {self.vertex_type}(ID:{self.id}): {str(exc)}") from exc def _update_built_object_and_artifacts(self, result): """ @@ -408,8 +382,4 @@ class Vertex: def _built_object_repr(self): # Add a message with an emoji, stars for sucess, - return ( - "Built sucessfully ✨" - if self._built_object is not None - else "Failed to build 😵‍💫" - ) + return "Built sucessfully ✨" if self._built_object is not None else "Failed to build 😵‍💫" diff --git a/src/backend/langflow/graph/vertex/types.py b/src/backend/langflow/graph/vertex/types.py index 1b63bc062..5fe6f8d31 100644 --- a/src/backend/langflow/graph/vertex/types.py +++ b/src/backend/langflow/graph/vertex/types.py @@ -107,11 +107,9 @@ class DocumentLoaderVertex(Vertex): # show how many documents are in the list? if self._built_object: - avg_length = sum( - len(doc.page_content) - for doc in self._built_object - if hasattr(doc, "page_content") - ) / len(self._built_object) + avg_length = sum(len(doc.page_content) for doc in self._built_object if hasattr(doc, "page_content")) / len( + self._built_object + ) return f"""{self.vertex_type}({len(self._built_object)} documents) \nAvg. Document Length (characters): {int(avg_length)} Documents: {self._built_object[:3]}...""" @@ -184,9 +182,7 @@ class TextSplitterVertex(Vertex): # show how many documents are in the list? if self._built_object: - avg_length = sum(len(doc.page_content) for doc in self._built_object) / len( - self._built_object - ) + avg_length = sum(len(doc.page_content) for doc in self._built_object) / len(self._built_object) return f"""{self.vertex_type}({len(self._built_object)} documents) \nAvg. Document Length (characters): {int(avg_length)} \nDocuments: {self._built_object[:3]}...""" @@ -205,6 +201,8 @@ class ChainVertex(Vertex): **kwargs, ) -> Any: if not self._built or force: + # Temporarily remove the code from the params + self.params.pop("code", None) # Check if the chain requires a PromptVertex # Temporarily remove "code" from the params @@ -234,27 +232,18 @@ class PromptVertex(Vertex): **kwargs, ) -> Any: if not self._built or force: - if ( - "input_variables" not in self.params - or self.params["input_variables"] is None - ): + if "input_variables" not in self.params or self.params["input_variables"] is None: self.params["input_variables"] = [] # Check if it is a ZeroShotPrompt and needs a tool if "ShotPrompt" in self.vertex_type: - tools = ( - [tool_node.build(user_id=user_id) for tool_node in tools] - if tools is not None - else [] - ) + tools = [tool_node.build(user_id=user_id) for tool_node in tools] if tools is not None else [] # flatten the list of tools if it is a list of lists # first check if it is a list if tools and isinstance(tools, list) and isinstance(tools[0], list): tools = flatten_list(tools) self.params["tools"] = tools prompt_params = [ - key - for key, value in self.params.items() - if isinstance(value, str) and key != "format_instructions" + key for key, value in self.params.items() if isinstance(value, str) and key != "format_instructions" ] else: prompt_params = ["template"] @@ -264,9 +253,7 @@ class PromptVertex(Vertex): prompt_text = self.params[param] variables = extract_input_variables_from_prompt(prompt_text) self.params["input_variables"].extend(variables) - self.params["input_variables"] = list( - set(self.params["input_variables"]) - ) + self.params["input_variables"] = list(set(self.params["input_variables"])) elif isinstance(self.params, dict): self.params.pop("input_variables", None) @@ -274,11 +261,7 @@ class PromptVertex(Vertex): return self._built_object def _built_object_repr(self): - if ( - not self.artifacts - or self._built_object is None - or not hasattr(self._built_object, "format") - ): + if not self.artifacts or self._built_object is None or not hasattr(self._built_object, "format"): return super()._built_object_repr() # We'll build the prompt with the artifacts # to show the user what the prompt looks like @@ -288,9 +271,7 @@ class PromptVertex(Vertex): # so the prompt format doesn't break artifacts.pop("handle_keys", None) try: - if not hasattr(self._built_object, "template") and hasattr( - self._built_object, "prompt" - ): + if not hasattr(self._built_object, "template") and hasattr(self._built_object, "prompt"): template = self._built_object.prompt.template else: template = self._built_object.template @@ -298,11 +279,7 @@ class PromptVertex(Vertex): if value: replace_key = "{" + key + "}" template = template.replace(replace_key, value) - return ( - template - if isinstance(template, str) - else f"{self.vertex_type}({template})" - ) + return template if isinstance(template, str) else f"{self.vertex_type}({template})" except KeyError: return str(self._built_object) diff --git a/src/backend/langflow/interface/agents/base.py b/src/backend/langflow/interface/agents/base.py index 74f07cd1e..9c0210b27 100644 --- a/src/backend/langflow/interface/agents/base.py +++ b/src/backend/langflow/interface/agents/base.py @@ -5,7 +5,7 @@ from langchain.agents import types from langflow.custom.customs import get_custom_nodes from langflow.interface.agents.custom import CUSTOM_AGENTS from langflow.interface.base import LangChainTypeCreator -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.agents import AgentFrontendNode from loguru import logger @@ -42,9 +42,7 @@ class AgentCreator(LangChainTypeCreator): add_function=True, method_name=self.from_method_nodes[name], ) - return build_template_from_class( - name, self.type_to_loader_dict, add_function=True - ) + return build_template_from_class(name, self.type_to_loader_dict, add_function=True) except ValueError as exc: raise ValueError("Agent not found") from exc except AttributeError as exc: @@ -56,15 +54,8 @@ class AgentCreator(LangChainTypeCreator): names = [] settings_service = get_settings_service() for _, agent in self.type_to_loader_dict.items(): - agent_name = ( - agent.function_name() - if hasattr(agent, "function_name") - else agent.__name__ - ) - if ( - agent_name in settings_service.settings.AGENTS - or settings_service.settings.DEV - ): + agent_name = agent.function_name() if hasattr(agent, "function_name") else agent.__name__ + if agent_name in settings_service.settings.AGENTS or settings_service.settings.DEV: names.append(agent_name) return names diff --git a/src/backend/langflow/interface/agents/custom.py b/src/backend/langflow/interface/agents/custom.py index a66b18e0b..b9da48b73 100644 --- a/src/backend/langflow/interface/agents/custom.py +++ b/src/backend/langflow/interface/agents/custom.py @@ -68,7 +68,8 @@ class JsonAgent(CustomAgentExecutor): prompt=prompt, ) agent = ZeroShotAgent( - llm_chain=llm_chain, allowed_tools=tool_names # type: ignore + llm_chain=llm_chain, + allowed_tools=tool_names, # type: ignore ) return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True) @@ -92,11 +93,7 @@ class CSVAgent(CustomAgentExecutor): @classmethod def from_toolkit_and_llm( - cls, - path: str, - llm: BaseLanguageModel, - pandas_kwargs: Optional[dict] = None, - **kwargs: Any + cls, path: str, llm: BaseLanguageModel, pandas_kwargs: Optional[dict] = None, **kwargs: Any ): import pandas as pd # type: ignore @@ -117,7 +114,9 @@ class CSVAgent(CustomAgentExecutor): ) tool_names = {tool.name for tool in tools} agent = ZeroShotAgent( - llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore + llm_chain=llm_chain, + allowed_tools=tool_names, + **kwargs, # type: ignore ) return cls.from_agent_and_tools(agent=agent, tools=tools, verbose=True) @@ -141,9 +140,7 @@ class VectorStoreAgent(CustomAgentExecutor): super().__init__(*args, **kwargs) @classmethod - def from_toolkit_and_llm( - cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any - ): + def from_toolkit_and_llm(cls, llm: BaseLanguageModel, vectorstoreinfo: VectorStoreInfo, **kwargs: Any): """Construct a vectorstore agent from an LLM and tools.""" toolkit = VectorStoreToolkit(vectorstore_info=vectorstoreinfo, llm=llm) @@ -156,11 +153,11 @@ class VectorStoreAgent(CustomAgentExecutor): ) tool_names = {tool.name for tool in tools} agent = ZeroShotAgent( - llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore - ) - return AgentExecutor.from_agent_and_tools( - agent=agent, tools=tools, verbose=True, handle_parsing_errors=True + llm_chain=llm_chain, + allowed_tools=tool_names, + **kwargs, # type: ignore ) + return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True) def run(self, *args, **kwargs): return super().run(*args, **kwargs) @@ -181,9 +178,7 @@ class SQLAgent(CustomAgentExecutor): super().__init__(*args, **kwargs) @classmethod - def from_toolkit_and_llm( - cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any - ): + def from_toolkit_and_llm(cls, llm: BaseLanguageModel, database_uri: str, **kwargs: Any): """Construct an SQL agent from an LLM and tools.""" db = SQLDatabase.from_uri(database_uri) toolkit = SQLDatabaseToolkit(db=db, llm=llm) @@ -201,9 +196,7 @@ class SQLAgent(CustomAgentExecutor): llmchain = LLMChain( llm=llm, - prompt=PromptTemplate( - template=QUERY_CHECKER, input_variables=["query", "dialect"] - ), + prompt=PromptTemplate(template=QUERY_CHECKER, input_variables=["query", "dialect"]), ) tools = [ @@ -226,7 +219,9 @@ class SQLAgent(CustomAgentExecutor): ) tool_names = {tool.name for tool in tools} # type: ignore agent = ZeroShotAgent( - llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore + llm_chain=llm_chain, + allowed_tools=tool_names, + **kwargs, # type: ignore ) return AgentExecutor.from_agent_and_tools( agent=agent, @@ -257,10 +252,7 @@ class VectorStoreRouterAgent(CustomAgentExecutor): @classmethod def from_toolkit_and_llm( - cls, - llm: BaseLanguageModel, - vectorstoreroutertoolkit: VectorStoreRouterToolkit, - **kwargs: Any + cls, llm: BaseLanguageModel, vectorstoreroutertoolkit: VectorStoreRouterToolkit, **kwargs: Any ): """Construct a vector store router agent from an LLM and tools.""" @@ -276,11 +268,11 @@ class VectorStoreRouterAgent(CustomAgentExecutor): ) tool_names = {tool.name for tool in tools} agent = ZeroShotAgent( - llm_chain=llm_chain, allowed_tools=tool_names, **kwargs # type: ignore - ) - return AgentExecutor.from_agent_and_tools( - agent=agent, tools=tools, verbose=True, handle_parsing_errors=True + llm_chain=llm_chain, + allowed_tools=tool_names, + **kwargs, # type: ignore ) + return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True) def run(self, *args, **kwargs): return super().run(*args, **kwargs) diff --git a/src/backend/langflow/interface/base.py b/src/backend/langflow/interface/base.py index 13dd05619..bc9db0a93 100644 --- a/src/backend/langflow/interface/base.py +++ b/src/backend/langflow/interface/base.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from typing import Any, Dict, List, Optional, Type, Union from langchain.chains.base import Chain from langchain.agents import AgentExecutor -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from pydantic import BaseModel from langflow.template.field.base import TemplateField @@ -30,13 +30,8 @@ class LangChainTypeCreator(BaseModel, ABC): settings_service = get_settings_service() if self.name_docs_dict is None: try: - type_settings = getattr( - settings_service.settings, self.type_name.upper() - ) - self.name_docs_dict = { - name: value_dict["documentation"] - for name, value_dict in type_settings.items() - } + type_settings = getattr(settings_service.settings, self.type_name.upper()) + self.name_docs_dict = {name: value_dict["documentation"] for name, value_dict in type_settings.items()} except AttributeError as exc: logger.error(f"Error getting settings for {self.type_name}: {exc}") diff --git a/src/backend/langflow/interface/chains/base.py b/src/backend/langflow/interface/chains/base.py index 3f324643d..8017902bd 100644 --- a/src/backend/langflow/interface/chains/base.py +++ b/src/backend/langflow/interface/chains/base.py @@ -3,7 +3,7 @@ from typing import Any, ClassVar, Dict, List, Optional, Type from langflow.custom.customs import get_custom_nodes from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.chains import ChainFrontendNode from loguru import logger @@ -33,8 +33,7 @@ class ChainCreator(LangChainTypeCreator): if self.type_dict is None: settings_service = get_settings_service() self.type_dict: dict[str, Any] = { - chain_name: import_class(f"langchain.chains.{chain_name}") - for chain_name in chains.__all__ + chain_name: import_class(f"langchain.chains.{chain_name}") for chain_name in chains.__all__ } from langflow.interface.chains.custom import CUSTOM_CHAINS @@ -45,8 +44,7 @@ class ChainCreator(LangChainTypeCreator): self.type_dict = { name: chain for name, chain in self.type_dict.items() - if name in settings_service.settings.CHAINS - or settings_service.settings.DEV + if name in settings_service.settings.CHAINS or settings_service.settings.DEV } return self.type_dict @@ -61,9 +59,7 @@ class ChainCreator(LangChainTypeCreator): method_name=self.from_method_nodes[name], add_function=True, ) - return build_template_from_class( - name, self.type_to_loader_dict, add_function=True - ) + return build_template_from_class(name, self.type_to_loader_dict, add_function=True) except ValueError as exc: raise ValueError(f"Chain {name} not found: {exc}") from exc except AttributeError as exc: @@ -73,11 +69,7 @@ class ChainCreator(LangChainTypeCreator): def to_list(self) -> List[str]: names = [] for _, chain in self.type_to_loader_dict.items(): - chain_name = ( - chain.function_name() - if hasattr(chain, "function_name") - else chain.__name__ - ) + chain_name = chain.function_name() if hasattr(chain, "function_name") else chain.__name__ names.append(chain_name) return names diff --git a/src/backend/langflow/interface/chains/custom.py b/src/backend/langflow/interface/chains/custom.py index edc8a06c0..01aad73c3 100644 --- a/src/backend/langflow/interface/chains/custom.py +++ b/src/backend/langflow/interface/chains/custom.py @@ -41,9 +41,7 @@ class BaseCustomConversationChain(ConversationChain): values["template"] = values["template"].format(**format_dict) values["template"] = values["template"] - values["input_variables"] = extract_input_variables_from_prompt( - values["template"] - ) + values["input_variables"] = extract_input_variables_from_prompt(values["template"]) values["prompt"].template = values["template"] values["prompt"].input_variables = values["input_variables"] return values @@ -54,9 +52,7 @@ class SeriesCharacterChain(BaseCustomConversationChain): character: str series: str - template: Optional[ - str - ] = """I want you to act like {character} from {series}. + template: Optional[str] = """I want you to act like {character} from {series}. I want you to respond and answer like {character}. do not write any explanations. only answer like {character}. You must know all of the knowledge of {character}. Current conversation: @@ -71,9 +67,7 @@ Human: {input} class MidJourneyPromptChain(BaseCustomConversationChain): """MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts.""" - template: Optional[ - str - ] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program. + template: Optional[str] = """I want you to act as a prompt generator for Midjourney's artificial intelligence program. Your job is to provide detailed and creative descriptions that will inspire unique and interesting images from the AI. Keep in mind that the AI is capable of understanding a wide range of language and can interpret abstract concepts, so feel free to be as imaginative and descriptive as possible. For example, you could describe a scene from a futuristic city, or a surreal landscape filled with strange creatures. @@ -87,9 +81,7 @@ class MidJourneyPromptChain(BaseCustomConversationChain): class TimeTravelGuideChain(BaseCustomConversationChain): - template: Optional[ - str - ] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information. + template: Optional[str] = """I want you to act as my time travel guide. You are helpful and creative. I will provide you with the historical period or future time I want to visit and you will suggest the best events, sights, or people to experience. Provide the suggestions and any necessary information. Current conversation: {history} Human: {input} diff --git a/src/backend/langflow/interface/custom/code_parser.py b/src/backend/langflow/interface/custom/code_parser.py index d42f82635..ce869d263 100644 --- a/src/backend/langflow/interface/custom/code_parser.py +++ b/src/backend/langflow/interface/custom/code_parser.py @@ -1,8 +1,8 @@ import ast import inspect import traceback +from typing import Any, Dict, List, Type, Union -from typing import Dict, Any, List, Type, Union from fastapi import HTTPException from langflow.interface.custom.schema import CallableCodeDetails, ClassCodeDetails @@ -104,7 +104,7 @@ class CodeParser: func.args = self.parse_function_args(node) func.body = self.parse_function_body(node) - return func.dict() + return func.model_dump() def parse_function_args(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: """ @@ -127,22 +127,14 @@ class CodeParser: num_defaults = len(node.args.defaults) num_missing_defaults = num_args - num_defaults missing_defaults = [None] * num_missing_defaults - default_values = [ - ast.unparse(default).strip("'") if default else None - for default in node.args.defaults - ] + default_values = [ast.unparse(default).strip("'") if default else None for default in node.args.defaults] # Now check all default values to see if there # are any "None" values in the middle - default_values = [ - None if value == "None" else value for value in default_values - ] + default_values = [None if value == "None" else value for value in default_values] defaults = missing_defaults + default_values - args = [ - self.parse_arg(arg, default) - for arg, default in zip(node.args.args, defaults) - ] + args = [self.parse_arg(arg, default) for arg, default in zip(node.args.args, defaults)] return args def parse_varargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: @@ -160,17 +152,11 @@ class CodeParser: """ Parses the keyword-only arguments of a function or method node. """ - kw_defaults = [None] * ( - len(node.args.kwonlyargs) - len(node.args.kw_defaults) - ) + [ - ast.unparse(default) if default else None - for default in node.args.kw_defaults + kw_defaults = [None] * (len(node.args.kwonlyargs) - len(node.args.kw_defaults)) + [ + ast.unparse(default) if default else None for default in node.args.kw_defaults ] - args = [ - self.parse_arg(arg, default) - for arg, default in zip(node.args.kwonlyargs, kw_defaults) - ] + args = [self.parse_arg(arg, default) for arg, default in zip(node.args.kwonlyargs, kw_defaults)] return args def parse_kwargs(self, node: ast.FunctionDef) -> List[Dict[str, Any]]: @@ -247,16 +233,14 @@ class CodeParser: else: class_details.methods.append(method) - self.data["classes"].append(class_details.dict()) + self.data["classes"].append(class_details.model_dump()) def parse_global_vars(self, node: ast.Assign) -> None: """ Extracts global variables from the code. """ global_var = { - "targets": [ - t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets - ], + "targets": [t.id if hasattr(t, "id") else ast.dump(t) for t in node.targets], "value": ast.unparse(node.value), } self.data["global_vars"].append(global_var) diff --git a/src/backend/langflow/interface/custom/component.py b/src/backend/langflow/interface/custom/component.py index 2e88b1f26..5a476ff58 100644 --- a/src/backend/langflow/interface/custom/component.py +++ b/src/backend/langflow/interface/custom/component.py @@ -1,9 +1,10 @@ import ast from typing import Any, ClassVar, Optional + from fastapi import HTTPException -from langflow.utils import validate from langflow.interface.custom.code_parser import CodeParser +from langflow.utils import validate class ComponentCodeNullError(HTTPException): @@ -16,9 +17,7 @@ class ComponentFunctionEntrypointNameNullError(HTTPException): class Component: ERROR_CODE_NULL: ClassVar[str] = "Python code must be provided." - ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[ - str - ] = "The name of the entrypoint function must be provided." + ERROR_FUNCTION_ENTRYPOINT_NAME_NULL: ClassVar[str] = "The name of the entrypoint function must be provided." code: Optional[str] = None _function_entrypoint_name: str = "build" diff --git a/src/backend/langflow/interface/custom/constants.py b/src/backend/langflow/interface/custom/constants.py index 6df72500c..d3e8dd3a9 100644 --- a/src/backend/langflow/interface/custom/constants.py +++ b/src/backend/langflow/interface/custom/constants.py @@ -17,6 +17,7 @@ from langflow.field_typing import ( AgentExecutor, NestedDict, Data, + Object, ) diff --git a/src/backend/langflow/interface/custom/custom_component.py b/src/backend/langflow/interface/custom/custom_component.py index b1066eab0..87b8e33d6 100644 --- a/src/backend/langflow/interface/custom/custom_component.py +++ b/src/backend/langflow/interface/custom/custom_component.py @@ -1,17 +1,16 @@ from typing import Any, Callable, ClassVar, List, Optional, Union from uuid import UUID + +import yaml from fastapi import HTTPException from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.interface.custom.component import Component from langflow.interface.custom.directory_reader import DirectoryReader -from langflow.services.getters import get_db_service from langflow.interface.custom.utils import extract_inner_type, extract_union_types - -from langflow.utils import validate - -from langflow.services.database.utils import session_getter from langflow.services.database.models.flow import Flow -import yaml +from langflow.services.database.utils import session_getter +from langflow.services.deps import get_db_service +from langflow.utils import validate class CustomComponent(Component): @@ -54,9 +53,9 @@ class CustomComponent(Component): reader = DirectoryReader("", False) for type_hint in TYPE_HINT_LIST: - if reader._is_type_hint_used_in_args( + if reader._is_type_hint_used_in_args(type_hint, code) and not reader._is_type_hint_imported( type_hint, code - ) and not reader._is_type_hint_imported(type_hint, code): + ): error_detail = { "error": "Type hint Error", "traceback": f"Type hint '{type_hint}' is used but not imported in the code.", @@ -75,20 +74,14 @@ class CustomComponent(Component): return "" tree = self.get_code_tree(self.code) - component_classes = [ - cls - for cls in tree["classes"] - if self.code_class_base_inheritance in cls["bases"] - ] + component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]] if not component_classes: return "" # Assume the first Component class is the one we're interested in component_class = component_classes[0] build_methods = [ - method - for method in component_class["methods"] - if method["name"] == self.function_entrypoint_name + method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name ] if not build_methods: @@ -104,8 +97,7 @@ class CustomComponent(Component): detail={ "error": "Type hint Error", "traceback": ( - "Prompt type is not supported in the build method." - " Try using PromptTemplate instead." + "Prompt type is not supported in the build method." " Try using PromptTemplate instead." ), }, ) @@ -120,20 +112,14 @@ class CustomComponent(Component): return [] tree = self.get_code_tree(self.code) - component_classes = [ - cls - for cls in tree["classes"] - if self.code_class_base_inheritance in cls["bases"] - ] + component_classes = [cls for cls in tree["classes"] if self.code_class_base_inheritance in cls["bases"]] if not component_classes: return [] # Assume the first Component class is the one we're interested in component_class = component_classes[0] build_methods = [ - method - for method in component_class["methods"] - if method["name"] == self.function_entrypoint_name + method for method in component_class["methods"] if method["name"] == self.function_entrypoint_name ] if not build_methods: @@ -191,8 +177,7 @@ class CustomComponent(Component): return validate.create_function(self.code, self.function_entrypoint_name) def load_flow(self, flow_id: str, tweaks: Optional[dict] = None) -> Any: - from langflow.processing.process import build_sorted_vertices - from langflow.processing.process import process_tweaks + from langflow.processing.process import build_sorted_vertices, process_tweaks db_service = get_db_service() with session_getter(db_service) as session: @@ -229,11 +214,7 @@ class CustomComponent(Component): if flow_id: flow = session.query(Flow).get(flow_id) elif flow_name: - flow = ( - session.query(Flow) - .filter(Flow.name == flow_name) - .filter(Flow.user_id == self.user_id) - ).first() + flow = (session.query(Flow).filter(Flow.name == flow_name).filter(Flow.user_id == self.user_id)).first() else: raise ValueError("Either flow_name or flow_id must be provided") diff --git a/src/backend/langflow/interface/custom/directory_reader.py b/src/backend/langflow/interface/custom/directory_reader.py index 01b11a4a6..e80f0bd28 100644 --- a/src/backend/langflow/interface/custom/directory_reader.py +++ b/src/backend/langflow/interface/custom/directory_reader.py @@ -76,9 +76,7 @@ class DirectoryReader: for menu in data["menu"] ] filtered = [menu for menu in items if menu["components"]] - logger.debug( - f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}' - ) + logger.debug(f'Filtered components {"with errors" if with_errors else ""}: {len(filtered)}') return {"menu": filtered} def validate_code(self, file_content): @@ -111,9 +109,7 @@ class DirectoryReader: Walk through the directory path and return a list of all .py files. """ if not (safe_path := self.get_safe_path()): - raise CustomComponentPathValueError( - f"The path needs to start with '{self.base_path}'." - ) + raise CustomComponentPathValueError(f"The path needs to start with '{self.base_path}'.") file_list = [] for root, _, files in os.walk(safe_path): @@ -158,9 +154,7 @@ class DirectoryReader: for node in ast.walk(module): if isinstance(node, ast.FunctionDef): for arg in node.args.args: - if self._is_type_hint_in_arg_annotation( - arg.annotation, type_hint_name - ): + if self._is_type_hint_in_arg_annotation(arg.annotation, type_hint_name): return True except SyntaxError: # Returns False if the code is not valid Python @@ -178,16 +172,14 @@ class DirectoryReader: and annotation.value.id == type_hint_name ) - def is_type_hint_used_but_not_imported( - self, type_hint_name: str, code: str - ) -> bool: + def is_type_hint_used_but_not_imported(self, type_hint_name: str, code: str) -> bool: """ Check if a type hint is used but not imported in the given code. """ try: - return self._is_type_hint_used_in_args( + return self._is_type_hint_used_in_args(type_hint_name, code) and not self._is_type_hint_imported( type_hint_name, code - ) and not self._is_type_hint_imported(type_hint_name, code) + ) except SyntaxError: # Returns True if there's something wrong with the code # TODO : Find a better way to handle this @@ -208,9 +200,9 @@ class DirectoryReader: return False, "Syntax error" elif not self.validate_build(file_content): return False, "Missing build function" - elif self._is_type_hint_used_in_args( + elif self._is_type_hint_used_in_args("Optional", file_content) and not self._is_type_hint_imported( "Optional", file_content - ) and not self._is_type_hint_imported("Optional", file_content): + ): return ( False, "Type hint 'Optional' is used but not imported in the code.", @@ -226,9 +218,7 @@ class DirectoryReader: from the .py files in the directory. """ response = {"menu": []} - logger.debug( - "-------------------- Building component menu list --------------------" - ) + logger.debug("-------------------- Building component menu list --------------------") for file_path in file_paths: menu_name = os.path.basename(os.path.dirname(file_path)) @@ -248,9 +238,7 @@ class DirectoryReader: # first check if it's already CamelCase if "_" in component_name: - component_name_camelcase = " ".join( - word.title() for word in component_name.split("_") - ) + component_name_camelcase = " ".join(word.title() for word in component_name.split("_")) else: component_name_camelcase = component_name @@ -266,7 +254,5 @@ class DirectoryReader: logger.debug(f"Component info: {component_info}") if menu_result not in response["menu"]: response["menu"].append(menu_result) - logger.debug( - "-------------------- Component menu list built --------------------" - ) + logger.debug("-------------------- Component menu list built --------------------") return response diff --git a/src/backend/langflow/interface/custom_lists.py b/src/backend/langflow/interface/custom_lists.py index 5a22d989f..9ad34edc2 100644 --- a/src/backend/langflow/interface/custom_lists.py +++ b/src/backend/langflow/interface/custom_lists.py @@ -46,34 +46,26 @@ toolkit_type_to_cls_dict: dict[str, Any] = { # Memories memory_type_to_cls_dict: dict[str, Any] = { - memory_name: import_class(f"langchain.memory.{memory_name}") - for memory_name in memory.__all__ + memory_name: import_class(f"langchain.memory.{memory_name}") for memory_name in memory.__all__ } # Wrappers -wrapper_type_to_cls_dict: dict[str, Any] = { - wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper] -} +wrapper_type_to_cls_dict: dict[str, Any] = {wrapper.__name__: wrapper for wrapper in [requests.RequestsWrapper]} # Embeddings embedding_type_to_cls_dict: dict[str, Any] = { - embedding_name: import_class(f"langchain.embeddings.{embedding_name}") - for embedding_name in embeddings.__all__ + embedding_name: import_class(f"langchain.embeddings.{embedding_name}") for embedding_name in embeddings.__all__ } # Document Loaders documentloaders_type_to_cls_dict: dict[str, Any] = { - documentloader_name: import_class( - f"langchain.document_loaders.{documentloader_name}" - ) + documentloader_name: import_class(f"langchain.document_loaders.{documentloader_name}") for documentloader_name in document_loaders.__all__ } # Text Splitters -textsplitter_type_to_cls_dict: dict[str, Any] = dict( - inspect.getmembers(text_splitter, inspect.isclass) -) +textsplitter_type_to_cls_dict: dict[str, Any] = dict(inspect.getmembers(text_splitter, inspect.isclass)) # merge CUSTOM_AGENTS and CUSTOM_CHAINS CUSTOM_NODES = {**CUSTOM_AGENTS, **CUSTOM_CHAINS} # type: ignore diff --git a/src/backend/langflow/interface/document_loaders/base.py b/src/backend/langflow/interface/document_loaders/base.py index e2c379b67..6142d2fa2 100644 --- a/src/backend/langflow/interface/document_loaders/base.py +++ b/src/backend/langflow/interface/document_loaders/base.py @@ -1,7 +1,7 @@ from typing import Dict, List, Optional, Type from langflow.interface.base import LangChainTypeCreator -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.documentloaders import DocumentLoaderFrontNode from langflow.interface.custom_lists import documentloaders_type_to_cls_dict @@ -35,8 +35,7 @@ class DocumentLoaderCreator(LangChainTypeCreator): return [ documentloader.__name__ for documentloader in self.type_to_loader_dict.values() - if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS - or settings_service.settings.DEV + if documentloader.__name__ in settings_service.settings.DOCUMENTLOADERS or settings_service.settings.DEV ] diff --git a/src/backend/langflow/interface/embeddings/base.py b/src/backend/langflow/interface/embeddings/base.py index d280cf1c1..834ea61fa 100644 --- a/src/backend/langflow/interface/embeddings/base.py +++ b/src/backend/langflow/interface/embeddings/base.py @@ -2,7 +2,7 @@ from typing import Dict, List, Optional, Type from langflow.interface.base import LangChainTypeCreator from langflow.interface.custom_lists import embedding_type_to_cls_dict -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.embeddings import EmbeddingFrontendNode @@ -37,8 +37,7 @@ class EmbeddingCreator(LangChainTypeCreator): return [ embedding.__name__ for embedding in self.type_to_loader_dict.values() - if embedding.__name__ in settings_service.settings.EMBEDDINGS - or settings_service.settings.DEV + if embedding.__name__ in settings_service.settings.EMBEDDINGS or settings_service.settings.DEV ] diff --git a/src/backend/langflow/interface/importing/utils.py b/src/backend/langflow/interface/importing/utils.py index 44d72df42..6b28d792e 100644 --- a/src/backend/langflow/interface/importing/utils.py +++ b/src/backend/langflow/interface/importing/utils.py @@ -104,10 +104,7 @@ def import_prompt(prompt: str) -> Type[PromptTemplate]: def import_wrapper(wrapper: str) -> Any: """Import wrapper from wrapper name""" - if ( - isinstance(wrapper_creator.type_dict, dict) - and wrapper in wrapper_creator.type_dict - ): + if isinstance(wrapper_creator.type_dict, dict) and wrapper in wrapper_creator.type_dict: return wrapper_creator.type_dict.get(wrapper) diff --git a/src/backend/langflow/interface/initialize/llm.py b/src/backend/langflow/interface/initialize/llm.py index eaed04b77..05219d7d3 100644 --- a/src/backend/langflow/interface/initialize/llm.py +++ b/src/backend/langflow/interface/initialize/llm.py @@ -2,8 +2,6 @@ def initialize_vertexai(class_object, params): if credentials_path := params.get("credentials"): from google.oauth2 import service_account # type: ignore - credentials_object = service_account.Credentials.from_service_account_file( - filename=credentials_path - ) + credentials_object = service_account.Credentials.from_service_account_file(filename=credentials_path) params["credentials"] = credentials_object return class_object(**params) diff --git a/src/backend/langflow/interface/initialize/loading.py b/src/backend/langflow/interface/initialize/loading.py index 24a551ff7..c3021531e 100644 --- a/src/backend/langflow/interface/initialize/loading.py +++ b/src/backend/langflow/interface/initialize/loading.py @@ -1,40 +1,37 @@ import json +from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Type + import orjson -from typing import Any, Callable, Dict, Sequence, Type, TYPE_CHECKING -from langchain.schema import Document from langchain.agents import agent as agent_module from langchain.agents.agent import AgentExecutor from langchain.agents.agent_toolkits.base import BaseToolkit from langchain.agents.tools import BaseTool +from langchain.chains.base import Chain +from langchain.document_loaders.base import BaseLoader +from langchain.schema import Document +from langchain.vectorstores.base import VectorStore +from loguru import logger +from pydantic import ValidationError + +from langflow.interface.custom_lists import CUSTOM_NODES +from langflow.interface.importing.utils import ( + get_function, + get_function_custom, + import_by_type, +) from langflow.interface.initialize.llm import initialize_vertexai from langflow.interface.initialize.utils import ( handle_format_kwargs, handle_node_type, handle_partial_variables, ) - from langflow.interface.initialize.vector_store import vecstore_initializer - -from pydantic import ValidationError - -from langflow.interface.importing.utils import ( - get_function, - get_function_custom, - import_by_type, -) -from langflow.interface.custom_lists import CUSTOM_NODES -from langflow.interface.agents.base import agent_creator -from langflow.interface.toolkits.base import toolkits_creator -from langflow.interface.chains.base import chain_creator from langflow.interface.output_parsers.base import output_parser_creator from langflow.interface.retrievers.base import retriever_creator -from langflow.interface.wrappers.base import wrapper_creator +from langflow.interface.toolkits.base import toolkits_creator from langflow.interface.utils import load_file_into_dict +from langflow.interface.wrappers.base import wrapper_creator from langflow.utils import validate -from langchain.chains.base import Chain -from langchain.vectorstores.base import VectorStore -from langchain.document_loaders.base import BaseLoader -from loguru import logger if TYPE_CHECKING: from langflow import CustomComponent @@ -44,15 +41,10 @@ def build_vertex_in_params(params: Dict) -> Dict: from langflow.graph.vertex.base import Vertex # If any of the values in params is a Vertex, we will build it - return { - key: value.build() if isinstance(value, Vertex) else value - for key, value in params.items() - } + return {key: value.build() if isinstance(value, Vertex) else value for key, value in params.items()} -def instantiate_class( - node_type: str, base_type: str, params: Dict, user_id=None -) -> Any: +def instantiate_class(node_type: str, base_type: str, params: Dict, user_id=None) -> Any: """Instantiate class from module type and key, and params""" params = convert_params_to_sets(params) params = convert_kwargs(params) @@ -64,9 +56,7 @@ def instantiate_class( return custom_node(**params) logger.debug(f"Instantiating {node_type} of type {base_type}") class_object = import_by_type(_type=base_type, name=node_type) - return instantiate_based_on_type( - class_object, base_type, node_type, params, user_id=user_id - ) + return instantiate_based_on_type(class_object, base_type, node_type, params, user_id=user_id) def convert_params_to_sets(params): @@ -194,9 +184,7 @@ def instantiate_memory(node_type, class_object, params): # I want to catch a specific attribute error that happens # when the object does not have a cursor attribute except Exception as exc: - if "object has no attribute 'cursor'" in str( - exc - ) or 'object has no field "conn"' in str(exc): + if "object has no attribute 'cursor'" in str(exc) or 'object has no field "conn"' in str(exc): raise AttributeError( ( "Failed to build connection to database." @@ -218,6 +206,8 @@ def instantiate_retriever(node_type, class_object, params): def instantiate_chains(node_type, class_object: Type[Chain], params: Dict): + from langflow.interface.chains.base import chain_creator + if "retriever" in params and hasattr(params["retriever"], "as_retriever"): params["retriever"] = params["retriever"].as_retriever() if node_type in chain_creator.from_method_nodes: @@ -230,14 +220,14 @@ def instantiate_chains(node_type, class_object: Type[Chain], params: Dict): def instantiate_agent(node_type, class_object: Type[agent_module.Agent], params: Dict): + from langflow.interface.agents.base import agent_creator + if node_type in agent_creator.from_method_nodes: method = agent_creator.from_method_nodes[node_type] if class_method := getattr(class_object, method, None): agent = class_method(**params) tools = params.get("tools", []) - return AgentExecutor.from_agent_and_tools( - agent=agent, tools=tools, handle_parsing_errors=True - ) + return AgentExecutor.from_agent_and_tools(agent=agent, tools=tools, handle_parsing_errors=True) return load_agent_executor(class_object, params) @@ -290,11 +280,7 @@ def instantiate_embedding(node_type, class_object, params: Dict): try: return class_object(**params) except ValidationError: - params = { - key: value - for key, value in params.items() - if key in class_object.model_fields - } + params = {key: value for key, value in params.items() if key in class_object.model_fields} return class_object(**params) @@ -306,9 +292,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict): if "texts" in params: params["documents"] = params.pop("texts") if "documents" in params: - params["documents"] = [ - doc for doc in params["documents"] if isinstance(doc, Document) - ] + params["documents"] = [doc for doc in params["documents"] if isinstance(doc, Document)] if initializer := vecstore_initializer.get(class_object.__name__): vecstore = initializer(class_object, params) else: @@ -323,9 +307,7 @@ def instantiate_vectorstore(class_object: Type[VectorStore], params: Dict): return vecstore -def instantiate_documentloader( - node_type: str, class_object: Type[BaseLoader], params: Dict -): +def instantiate_documentloader(node_type: str, class_object: Type[BaseLoader], params: Dict): if "file_filter" in params: # file_filter will be a string but we need a function # that will be used to filter the files using file_filter @@ -334,17 +316,13 @@ def instantiate_documentloader( # in x and if it is, we will return True file_filter = params.pop("file_filter") extensions = file_filter.split(",") - params["file_filter"] = lambda x: any( - extension.strip() in x for extension in extensions - ) + params["file_filter"] = lambda x: any(extension.strip() in x for extension in extensions) metadata = params.pop("metadata", None) if metadata and isinstance(metadata, str): try: metadata = orjson.loads(metadata) except json.JSONDecodeError as exc: - raise ValueError( - "The metadata you provided is not a valid JSON string." - ) from exc + raise ValueError("The metadata you provided is not a valid JSON string.") from exc if node_type == "WebBaseLoader": if web_path := params.pop("web_path", None): @@ -377,16 +355,12 @@ def instantiate_textsplitter( "Try changing the chunk_size of the Text Splitter." ) from exc - if ( - "separator_type" in params and params["separator_type"] == "Text" - ) or "separator_type" not in params: + if ("separator_type" in params and params["separator_type"] == "Text") or "separator_type" not in params: params.pop("separator_type", None) # separators might come in as an escaped string like \\n # so we need to convert it to a string if "separators" in params: - params["separators"] = ( - params["separators"].encode().decode("unicode-escape") - ) + params["separators"] = params["separators"].encode().decode("unicode-escape") text_splitter = class_object(**params) else: from langchain.text_splitter import Language @@ -413,8 +387,7 @@ def replace_zero_shot_prompt_with_prompt_template(nodes): tools = [ tool for tool in nodes - if tool["type"] != "chatOutputNode" - and "Tool" in tool["data"]["node"]["base_classes"] + if tool["type"] != "chatOutputNode" and "Tool" in tool["data"]["node"]["base_classes"] ] node["data"] = build_prompt_template(prompt=node["data"], tools=tools) break @@ -428,9 +401,7 @@ def load_agent_executor(agent_class: type[agent_module.Agent], params, **kwargs) # agent has hidden args for memory. might need to be support # memory = params["memory"] # if allowed_tools is not a list or set, make it a list - if not isinstance(allowed_tools, (list, set)) and isinstance( - allowed_tools, BaseTool - ): + if not isinstance(allowed_tools, (list, set)) and isinstance(allowed_tools, BaseTool): allowed_tools = [allowed_tools] tool_names = [tool.name for tool in allowed_tools] # Agent class requires an output_parser but Agent classes @@ -458,10 +429,7 @@ def build_prompt_template(prompt, tools): format_instructions = prompt["node"]["template"]["format_instructions"]["value"] tool_strings = "\n".join( - [ - f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" - for tool in tools - ] + [f"{tool['data']['node']['name']}: {tool['data']['node']['description']}" for tool in tools] ) tool_names = ", ".join([tool["data"]["node"]["name"] for tool in tools]) format_instructions = format_instructions.format(tool_names=tool_names) diff --git a/src/backend/langflow/interface/initialize/utils.py b/src/backend/langflow/interface/initialize/utils.py index 199626de5..4aa3fc52b 100644 --- a/src/backend/langflow/interface/initialize/utils.py +++ b/src/backend/langflow/interface/initialize/utils.py @@ -30,9 +30,7 @@ def check_tools_in_params(params: Dict): def instantiate_from_template(class_object, params: Dict): - from_template_params = { - "template": params.pop("prompt", params.pop("template", "")) - } + from_template_params = {"template": params.pop("prompt", params.pop("template", ""))} if not from_template_params.get("template"): raise ValueError("Prompt template is required") return class_object.from_template(**from_template_params) @@ -48,9 +46,7 @@ def handle_format_kwargs(prompt, params: Dict): def handle_partial_variables(prompt, format_kwargs: Dict): partial_variables = format_kwargs.copy() - partial_variables = { - key: value for key, value in partial_variables.items() if value - } + partial_variables = {key: value for key, value in partial_variables.items() if value} # Remove handle_keys otherwise LangChain raises an error partial_variables.pop("handle_keys", None) if partial_variables and hasattr(prompt, "partial"): @@ -62,9 +58,7 @@ def handle_variable(params: Dict, input_variable: str, format_kwargs: Dict): variable = params[input_variable] if isinstance(variable, str): format_kwargs[input_variable] = variable - elif isinstance(variable, BaseOutputParser) and hasattr( - variable, "get_format_instructions" - ): + elif isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions"): format_kwargs[input_variable] = variable.get_format_instructions() elif is_instance_of_list_or_document(variable): format_kwargs = format_document(variable, input_variable, format_kwargs) @@ -107,8 +101,7 @@ def try_to_load_json(content): def needs_handle_keys(variable): return is_instance_of_list_or_document(variable) or ( - isinstance(variable, BaseOutputParser) - and hasattr(variable, "get_format_instructions") + isinstance(variable, BaseOutputParser) and hasattr(variable, "get_format_instructions") ) diff --git a/src/backend/langflow/interface/initialize/vector_store.py b/src/backend/langflow/interface/initialize/vector_store.py index 0e9b573a4..0b5ade7c8 100644 --- a/src/backend/langflow/interface/initialize/vector_store.py +++ b/src/backend/langflow/interface/initialize/vector_store.py @@ -17,9 +17,7 @@ import orjson def docs_in_params(params: dict) -> bool: """Check if params has documents OR texts and one of them is not an empty list, If any of them is not an empty list, return True, else return False""" - return ("documents" in params and params["documents"]) or ( - "texts" in params and params["texts"] - ) + return ("documents" in params and params["documents"]) or ("texts" in params and params["texts"]) def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dict): @@ -31,9 +29,7 @@ def initialize_mongodb(class_object: Type[MongoDBAtlasVectorSearch], params: dic from pymongo import MongoClient import certifi - client: MongoClient = MongoClient( - MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where() - ) + client: MongoClient = MongoClient(MONGODB_ATLAS_CLUSTER_URI, tlsCAFile=certifi.where()) db_name = params.pop("db_name", None) collection_name = params.pop("collection_name", None) if not db_name or not collection_name: @@ -141,9 +137,7 @@ def initialize_pinecone(class_object: Type[Pinecone], params: dict): pinecone_env = os.getenv("PINECONE_ENV") if pinecone_api_key is None or pinecone_env is None: - raise ValueError( - "Pinecone API key and environment must be provided in the params" - ) + raise ValueError("Pinecone API key and environment must be provided in the params") # initialize pinecone pinecone.init( @@ -177,19 +171,13 @@ def initialize_chroma(class_object: Type[Chroma], params: dict): import chromadb # type: ignore settings_params = { - key: params[key] - for key, value_ in params.items() - if key.startswith("chroma_server_") and value_ + key: params[key] for key, value_ in params.items() if key.startswith("chroma_server_") and value_ } chroma_settings = chromadb.config.Settings(**settings_params) params["client_settings"] = chroma_settings else: # remove all chroma_server_ keys from params - params = { - key: value - for key, value in params.items() - if not key.startswith("chroma_server_") - } + params = {key: value for key, value in params.items() if not key.startswith("chroma_server_")} persist = params.pop("persist", False) if not docs_in_params(params): diff --git a/src/backend/langflow/interface/listing.py b/src/backend/langflow/interface/listing.py index aa72e568e..79a7335d4 100644 --- a/src/backend/langflow/interface/listing.py +++ b/src/backend/langflow/interface/listing.py @@ -1,4 +1,4 @@ -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.utils.lazy_load import LazyLoadDictBase diff --git a/src/backend/langflow/interface/llms/base.py b/src/backend/langflow/interface/llms/base.py index ffb7fa2f2..4b0654a1a 100644 --- a/src/backend/langflow/interface/llms/base.py +++ b/src/backend/langflow/interface/llms/base.py @@ -2,7 +2,7 @@ from typing import Dict, List, Optional, Type from langflow.interface.base import LangChainTypeCreator from langflow.interface.custom_lists import llm_type_to_cls_dict -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.llms import LLMFrontendNode from loguru import logger @@ -38,8 +38,7 @@ class LLMCreator(LangChainTypeCreator): return [ llm.__name__ for llm in self.type_to_loader_dict.values() - if llm.__name__ in settings_service.settings.LLMS - or settings_service.settings.DEV + if llm.__name__ in settings_service.settings.LLMS or settings_service.settings.DEV ] diff --git a/src/backend/langflow/interface/memories/base.py b/src/backend/langflow/interface/memories/base.py index 140c53666..7508d6d64 100644 --- a/src/backend/langflow/interface/memories/base.py +++ b/src/backend/langflow/interface/memories/base.py @@ -2,7 +2,7 @@ from typing import ClassVar, Dict, List, Optional, Type from langflow.interface.base import LangChainTypeCreator from langflow.interface.custom_lists import memory_type_to_cls_dict -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.base import FrontendNode from langflow.template.frontend_node.memories import MemoryFrontendNode @@ -53,8 +53,7 @@ class MemoryCreator(LangChainTypeCreator): return [ memory.__name__ for memory in self.type_to_loader_dict.values() - if memory.__name__ in settings_service.settings.MEMORIES - or settings_service.settings.DEV + if memory.__name__ in settings_service.settings.MEMORIES or settings_service.settings.DEV ] diff --git a/src/backend/langflow/interface/output_parsers/base.py b/src/backend/langflow/interface/output_parsers/base.py index 39269c2b4..3ae1e9d7b 100644 --- a/src/backend/langflow/interface/output_parsers/base.py +++ b/src/backend/langflow/interface/output_parsers/base.py @@ -4,7 +4,7 @@ from langchain import output_parsers from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.output_parsers import OutputParserFrontendNode from loguru import logger @@ -26,17 +26,14 @@ class OutputParserCreator(LangChainTypeCreator): if self.type_dict is None: settings_service = get_settings_service() self.type_dict = { - output_parser_name: import_class( - f"langchain.output_parsers.{output_parser_name}" - ) + output_parser_name: import_class(f"langchain.output_parsers.{output_parser_name}") # if output_parser_name is not lower case it is a class for output_parser_name in output_parsers.__all__ } self.type_dict = { name: output_parser for name, output_parser in self.type_dict.items() - if name in settings_service.settings.OUTPUT_PARSERS - or settings_service.settings.DEV + if name in settings_service.settings.OUTPUT_PARSERS or settings_service.settings.DEV } return self.type_dict diff --git a/src/backend/langflow/interface/prompts/base.py b/src/backend/langflow/interface/prompts/base.py index 29d3e8ba8..164ea2b10 100644 --- a/src/backend/langflow/interface/prompts/base.py +++ b/src/backend/langflow/interface/prompts/base.py @@ -5,7 +5,7 @@ from langchain import prompts from langflow.custom.customs import get_custom_nodes from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.prompts import PromptFrontendNode from loguru import logger @@ -36,8 +36,7 @@ class PromptCreator(LangChainTypeCreator): self.type_dict = { name: prompt for name, prompt in self.type_dict.items() - if name in settings_service.settings.PROMPTS - or settings_service.settings.DEV + if name in settings_service.settings.PROMPTS or settings_service.settings.DEV } return self.type_dict diff --git a/src/backend/langflow/interface/prompts/custom.py b/src/backend/langflow/interface/prompts/custom.py index eb6731cf5..202fbe409 100644 --- a/src/backend/langflow/interface/prompts/custom.py +++ b/src/backend/langflow/interface/prompts/custom.py @@ -42,17 +42,13 @@ class BaseCustomPrompt(PromptTemplate): values["template"] = values["template"].format(**format_dict) values["template"] = values["template"] - values["input_variables"] = extract_input_variables_from_prompt( - values["template"] - ) + values["input_variables"] = extract_input_variables_from_prompt(values["template"]) return values class SeriesCharacterPrompt(BaseCustomPrompt): # Add a very descriptive description for the prompt generator - description: Optional[ - str - ] = "A prompt that asks the AI to act like a character from a series." + description: Optional[str] = "A prompt that asks the AI to act like a character from a series." character: str series: str template: str = """I want you to act like {character} from {series}. @@ -68,6 +64,4 @@ Human: {input} input_variables: List[str] = ["character", "series"] -CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = { - "SeriesCharacterPrompt": SeriesCharacterPrompt -} +CUSTOM_PROMPTS: Dict[str, Type[BaseCustomPrompt]] = {"SeriesCharacterPrompt": SeriesCharacterPrompt} diff --git a/src/backend/langflow/interface/retrievers/base.py b/src/backend/langflow/interface/retrievers/base.py index 44d3ad692..2439708a3 100644 --- a/src/backend/langflow/interface/retrievers/base.py +++ b/src/backend/langflow/interface/retrievers/base.py @@ -4,7 +4,7 @@ from langchain import retrievers from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.retrievers import RetrieverFrontendNode from loguru import logger @@ -42,9 +42,7 @@ class RetrieverCreator(LangChainTypeCreator): method_name=self.from_method_nodes[name], ) else: - return build_template_from_class( - name, type_to_cls_dict=self.type_to_loader_dict - ) + return build_template_from_class(name, type_to_cls_dict=self.type_to_loader_dict) except ValueError as exc: raise ValueError(f"Retriever {name} not found") from exc except AttributeError as exc: @@ -56,8 +54,7 @@ class RetrieverCreator(LangChainTypeCreator): return [ retriever for retriever in self.type_to_loader_dict.keys() - if retriever in settings_service.settings.RETRIEVERS - or settings_service.settings.DEV + if retriever in settings_service.settings.RETRIEVERS or settings_service.settings.DEV ] diff --git a/src/backend/langflow/interface/run.py b/src/backend/langflow/interface/run.py index b482f8c50..624f250c8 100644 --- a/src/backend/langflow/interface/run.py +++ b/src/backend/langflow/interface/run.py @@ -4,9 +4,7 @@ from loguru import logger from uuid import UUID -def build_sorted_vertices( - data_graph, user_id: Optional[Union[str, UUID]] = None -) -> Tuple[Graph, Dict]: +def build_sorted_vertices(data_graph, user_id: Optional[Union[str, UUID]] = None) -> Tuple[Graph, Dict]: """ Build langchain object from data_graph. """ diff --git a/src/backend/langflow/interface/text_splitters/base.py b/src/backend/langflow/interface/text_splitters/base.py index 4f337817f..29c77a12f 100644 --- a/src/backend/langflow/interface/text_splitters/base.py +++ b/src/backend/langflow/interface/text_splitters/base.py @@ -1,7 +1,7 @@ from typing import Dict, List, Optional, Type from langflow.interface.base import LangChainTypeCreator -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.textsplitters import TextSplittersFrontendNode from langflow.interface.custom_lists import textsplitter_type_to_cls_dict @@ -35,8 +35,7 @@ class TextSplitterCreator(LangChainTypeCreator): return [ textsplitter.__name__ for textsplitter in self.type_to_loader_dict.values() - if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS - or settings_service.settings.DEV + if textsplitter.__name__ in settings_service.settings.TEXTSPLITTERS or settings_service.settings.DEV ] diff --git a/src/backend/langflow/interface/toolkits/base.py b/src/backend/langflow/interface/toolkits/base.py index 73ca4852f..7c57579d1 100644 --- a/src/backend/langflow/interface/toolkits/base.py +++ b/src/backend/langflow/interface/toolkits/base.py @@ -4,7 +4,7 @@ from langchain.agents import agent_toolkits from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class, import_module -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from loguru import logger from langflow.utils.util import build_template_from_class @@ -32,13 +32,10 @@ class ToolkitCreator(LangChainTypeCreator): if self.type_dict is None: settings_service = get_settings_service() self.type_dict = { - toolkit_name: import_class( - f"langchain.agents.agent_toolkits.{toolkit_name}" - ) + toolkit_name: import_class(f"langchain.agents.agent_toolkits.{toolkit_name}") # if toolkit_name is not lower case it is a class for toolkit_name in agent_toolkits.__all__ - if not toolkit_name.islower() - and toolkit_name in settings_service.settings.TOOLKITS + if not toolkit_name.islower() and toolkit_name in settings_service.settings.TOOLKITS } return self.type_dict @@ -61,9 +58,7 @@ class ToolkitCreator(LangChainTypeCreator): def get_create_function(self, name: str) -> Callable: if loader_name := self.create_functions.get(name): - return import_module( - f"from langchain.agents.agent_toolkits import {loader_name[0]}" - ) + return import_module(f"from langchain.agents.agent_toolkits import {loader_name[0]}") else: raise ValueError("Toolkit not found") diff --git a/src/backend/langflow/interface/tools/base.py b/src/backend/langflow/interface/tools/base.py index e74d49384..56112bf30 100644 --- a/src/backend/langflow/interface/tools/base.py +++ b/src/backend/langflow/interface/tools/base.py @@ -15,7 +15,7 @@ from langflow.interface.tools.constants import ( OTHER_TOOLS, ) from langflow.interface.tools.util import get_tool_params -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.field.base import TemplateField from langflow.template.template.base import Template @@ -32,9 +32,7 @@ TOOL_INPUTS = { placeholder="", value="", ), - "llm": TemplateField( - field_type="BaseLanguageModel", required=True, is_list=False, show=True - ), + "llm": TemplateField(field_type="BaseLanguageModel", required=True, is_list=False, show=True), "func": TemplateField( field_type="Callable", required=True, @@ -81,10 +79,7 @@ class ToolCreator(LangChainTypeCreator): tool_name = tool_params.get("name") or tool - if ( - tool_name in settings_service.settings.TOOLS - or settings_service.settings.DEV - ): + if tool_name in settings_service.settings.TOOLS or settings_service.settings.DEV: if tool_name == "JsonSpec": tool_params["path"] = tool_params.pop("dict_") # type: ignore all_tools[tool_name] = { diff --git a/src/backend/langflow/interface/tools/util.py b/src/backend/langflow/interface/tools/util.py index 8e4f582c1..7c8020aa9 100644 --- a/src/backend/langflow/interface/tools/util.py +++ b/src/backend/langflow/interface/tools/util.py @@ -21,16 +21,12 @@ def get_func_tool_params(func, **kwargs) -> Union[Dict, None]: for keyword in tool.keywords: if keyword.arg == "name": try: - tool_params["name"] = ast.literal_eval( - keyword.value - ) + tool_params["name"] = ast.literal_eval(keyword.value) except ValueError: break elif keyword.arg == "description": try: - tool_params["description"] = ast.literal_eval( - keyword.value - ) + tool_params["description"] = ast.literal_eval(keyword.value) except ValueError: continue @@ -43,9 +39,7 @@ def get_func_tool_params(func, **kwargs) -> Union[Dict, None]: else: # get the class object from the return statement try: - class_obj = eval( - compile(ast.Expression(tool), "", "eval") - ) + class_obj = eval(compile(ast.Expression(tool), "", "eval")) except Exception: return None diff --git a/src/backend/langflow/interface/types.py b/src/backend/langflow/interface/types.py index 3172eacec..6338f9398 100644 --- a/src/backend/langflow/interface/types.py +++ b/src/backend/langflow/interface/types.py @@ -7,6 +7,8 @@ from typing import Any, Dict, List, Optional, Union from uuid import UUID from fastapi import HTTPException +from loguru import logger + from langflow.api.utils import get_new_key from langflow.field_typing.constants import CUSTOM_COMPONENT_SUPPORTED_TYPES from langflow.interface.agents.base import agent_creator @@ -33,7 +35,6 @@ from langflow.template.field.base import TemplateField from langflow.template.frontend_node.constants import CLASSES_TO_REMOVE from langflow.template.frontend_node.custom_components import CustomComponentFrontendNode from langflow.utils.util import get_base_classes -from loguru import logger # Used to get the base_classes list diff --git a/src/backend/langflow/interface/utilities/base.py b/src/backend/langflow/interface/utilities/base.py index a404417dc..cfebf83a4 100644 --- a/src/backend/langflow/interface/utilities/base.py +++ b/src/backend/langflow/interface/utilities/base.py @@ -1,14 +1,13 @@ from typing import Dict, List, Optional, Type from langchain import utilities +from loguru import logger from langflow.custom.customs import get_custom_nodes from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class -from langflow.services.getters import get_settings_service - +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.utilities import UtilitiesFrontendNode -from loguru import logger from langflow.utils.util import build_template_from_class @@ -41,8 +40,7 @@ class UtilityCreator(LangChainTypeCreator): self.type_dict = { name: utility for name, utility in self.type_dict.items() - if name in settings_service.settings.UTILITIES - or settings_service.settings.DEV + if name in settings_service.settings.UTILITIES or settings_service.settings.DEV } return self.type_dict diff --git a/src/backend/langflow/interface/utils.py b/src/backend/langflow/interface/utils.py index b28a660bf..6f7ec9329 100644 --- a/src/backend/langflow/interface/utils.py +++ b/src/backend/langflow/interface/utils.py @@ -10,7 +10,7 @@ from langchain.base_language import BaseLanguageModel from PIL.Image import Image from loguru import logger from langflow.services.chat.config import ChatConfig -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service def load_file_into_dict(file_path: str) -> dict: @@ -43,9 +43,7 @@ def try_setting_streaming_options(langchain_object): llm = None if hasattr(langchain_object, "llm"): llm = langchain_object.llm - elif hasattr(langchain_object, "llm_chain") and hasattr( - langchain_object.llm_chain, "llm" - ): + elif hasattr(langchain_object, "llm_chain") and hasattr(langchain_object.llm_chain, "llm"): llm = langchain_object.llm_chain.llm if isinstance(llm, BaseLanguageModel): @@ -79,9 +77,7 @@ def set_langchain_cache(settings): if cache_type := os.getenv("LANGFLOW_LANGCHAIN_CACHE"): try: - cache_class = import_class( - f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}" - ) + cache_class = import_class(f"langchain.cache.{cache_type or settings.LANGCHAIN_CACHE}") logger.debug(f"Setting up LLM caching with {cache_class.__name__}") langchain.llm_cache = cache_class() diff --git a/src/backend/langflow/interface/vector_store/base.py b/src/backend/langflow/interface/vector_store/base.py index 786031a6b..d04689469 100644 --- a/src/backend/langflow/interface/vector_store/base.py +++ b/src/backend/langflow/interface/vector_store/base.py @@ -4,7 +4,7 @@ from langchain import vectorstores from langflow.interface.base import LangChainTypeCreator from langflow.interface.importing.utils import import_class -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.template.frontend_node.vectorstores import VectorStoreFrontendNode from loguru import logger @@ -22,9 +22,7 @@ class VectorstoreCreator(LangChainTypeCreator): def type_to_loader_dict(self) -> Dict: if self.type_dict is None: self.type_dict: dict[str, Any] = { - vectorstore_name: import_class( - f"langchain.vectorstores.{vectorstore_name}" - ) + vectorstore_name: import_class(f"langchain.vectorstores.{vectorstore_name}") for vectorstore_name in vectorstores.__all__ } return self.type_dict @@ -48,8 +46,7 @@ class VectorstoreCreator(LangChainTypeCreator): return [ vectorstore for vectorstore in self.type_to_loader_dict.keys() - if vectorstore in settings_service.settings.VECTORSTORES - or settings_service.settings.DEV + if vectorstore in settings_service.settings.VECTORSTORES or settings_service.settings.DEV ] diff --git a/src/backend/langflow/interface/wrappers/base.py b/src/backend/langflow/interface/wrappers/base.py index e0e762e99..38d61af78 100644 --- a/src/backend/langflow/interface/wrappers/base.py +++ b/src/backend/langflow/interface/wrappers/base.py @@ -16,8 +16,7 @@ class WrapperCreator(LangChainTypeCreator): def type_to_loader_dict(self) -> Dict: if self.type_dict is None: self.type_dict = { - wrapper.__name__: wrapper - for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase] + wrapper.__name__: wrapper for wrapper in [requests.TextRequestsWrapper, sql_database.SQLDatabase] } return self.type_dict diff --git a/src/backend/langflow/main.py b/src/backend/langflow/main.py index 9caa157d0..d93bd8004 100644 --- a/src/backend/langflow/main.py +++ b/src/backend/langflow/main.py @@ -1,19 +1,16 @@ from pathlib import Path from typing import Optional -from fastapi import FastAPI +from urllib.parse import urlencode + +from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles from langflow.api import router - - from langflow.interface.utils import setup_llm_caching -from langflow.services.utils import initialize_services from langflow.services.plugins.langfuse import LangfuseInstance -from langflow.services.utils import ( - teardown_services, -) +from langflow.services.utils import initialize_services, teardown_services from langflow.utils.logger import configure @@ -23,7 +20,6 @@ def create_app(): configure() app = FastAPI() - origins = ["*"] app.add_middleware( @@ -34,6 +30,16 @@ def create_app(): allow_headers=["*"], ) + @app.middleware("http") + async def flatten_query_string_lists(request: Request, call_next): + flattened = [] + for key, value in request.query_params.multi_items(): + flattened.extend((key, entry) for entry in value.split(",")) + + request.scope["query_string"] = urlencode(flattened, doseq=True).encode("utf-8") + + return await call_next(request) + @app.get("/health") def health(): return {"status": "ok"} @@ -78,9 +84,7 @@ def get_static_files_dir(): return frontend_path / "frontend" -def setup_app( - static_files_dir: Optional[Path] = None, backend_only: bool = False -) -> FastAPI: +def setup_app(static_files_dir: Optional[Path] = None, backend_only: bool = False) -> FastAPI: """Setup the FastAPI app.""" # get the directory of the current file if not static_files_dir: @@ -96,6 +100,7 @@ def setup_app( if __name__ == "__main__": import uvicorn + from langflow.__main__ import get_number_of_workers configure() diff --git a/src/backend/langflow/processing/base.py b/src/backend/langflow/processing/base.py index 6c56a59dd..d1896398e 100644 --- a/src/backend/langflow/processing/base.py +++ b/src/backend/langflow/processing/base.py @@ -34,9 +34,7 @@ def get_langfuse_callback(trace_id): if langfuse := LangfuseInstance.get(): logger.debug("Langfuse credentials found") try: - trace = langfuse.trace( - CreateTrace(name="langflow-" + trace_id, id=trace_id) - ) + trace = langfuse.trace(CreateTrace(name="langflow-" + trace_id, id=trace_id)) return trace.getNewHandler() except Exception as exc: logger.error(f"Error initializing langfuse callback: {exc}") @@ -44,9 +42,7 @@ def get_langfuse_callback(trace_id): return None -def flush_langfuse_callback_if_present( - callbacks: List[Union[BaseCallbackHandler, "CallbackHandler"]] -): +def flush_langfuse_callback_if_present(callbacks: List[Union[BaseCallbackHandler, "CallbackHandler"]]): """ If langfuse callback is present, run callback.langfuse.flush() """ @@ -88,15 +84,9 @@ async def get_result_and_steps(langchain_object, inputs: Union[dict, str], **kwa # if langfuse callback is present, run callback.langfuse.flush() flush_langfuse_callback_if_present(callbacks) - intermediate_steps = ( - output.get("intermediate_steps", []) if isinstance(output, dict) else [] - ) + intermediate_steps = output.get("intermediate_steps", []) if isinstance(output, dict) else [] - result = ( - output.get(langchain_object.output_keys[0]) - if isinstance(output, dict) - else output - ) + result = output.get(langchain_object.output_keys[0]) if isinstance(output, dict) else output try: thought = format_actions(intermediate_steps) if intermediate_steps else "" except Exception as exc: diff --git a/src/backend/langflow/processing/process.py b/src/backend/langflow/processing/process.py index 4c96e2fde..951e196fb 100644 --- a/src/backend/langflow/processing/process.py +++ b/src/backend/langflow/processing/process.py @@ -6,7 +6,7 @@ from langflow.interface.run import ( get_memory_key, update_memory_keys, ) -from langflow.services.getters import get_session_service +from langflow.services.deps import get_session_service from loguru import logger from langflow.graph import Graph from langchain.chains.base import Chain @@ -112,9 +112,7 @@ def load_langchain_object( logger.debug("Loaded LangChain object") if langchain_object is None: - raise ValueError( - "There was an error loading the langchain_object. Please, check all the nodes and try again." - ) + raise ValueError("There was an error loading the langchain_object. Please, check all the nodes and try again.") return langchain_object, artifacts, session_id @@ -164,9 +162,7 @@ async def process_graph_cached( if clear_cache: session_service.clear_session(session_id) if session_id is None: - session_id = session_service.generate_key( - session_id=session_id, data_graph=data_graph - ) + session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph) # Load the graph using SessionService graph, artifacts = session_service.load_session(session_id, data_graph) built_object = graph.build() @@ -179,9 +175,7 @@ async def process_graph_cached( return Result(result=result, session_id=session_id) -def load_flow_from_json( - flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True -): +def load_flow_from_json(flow: Union[Path, str, dict], tweaks: Optional[dict] = None, build=True): """ Load flow from a JSON file or a JSON object. @@ -198,9 +192,7 @@ def load_flow_from_json( elif isinstance(flow, dict): flow_graph = flow else: - raise TypeError( - "Input must be either a file path (str) or a JSON object (dict)" - ) + raise TypeError("Input must be either a file path (str) or a JSON object (dict)") graph_data = flow_graph["data"] if tweaks is not None: @@ -226,18 +218,14 @@ def load_flow_from_json( return graph -def validate_input( - graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]] -) -> List[Dict[str, Any]]: +def validate_input(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> List[Dict[str, Any]]: if not isinstance(graph_data, dict) or not isinstance(tweaks, dict): raise ValueError("graph_data and tweaks should be dictionaries") nodes = graph_data.get("data", {}).get("nodes") or graph_data.get("nodes") if not isinstance(nodes, list): - raise ValueError( - "graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key" - ) + raise ValueError("graph_data should contain a list of nodes under 'data' key or directly under 'nodes' key") return nodes @@ -246,9 +234,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None: template_data = node.get("data", {}).get("node", {}).get("template") if not isinstance(template_data, dict): - logger.warning( - f"Template data for node {node.get('id')} should be a dictionary" - ) + logger.warning(f"Template data for node {node.get('id')} should be a dictionary") return for tweak_name, tweak_value in node_tweaks.items(): @@ -257,9 +243,7 @@ def apply_tweaks(node: Dict[str, Any], node_tweaks: Dict[str, Any]) -> None: template_data[tweak_name][key] = tweak_value -def process_tweaks( - graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]] -) -> Dict[str, Any]: +def process_tweaks(graph_data: Dict[str, Any], tweaks: Dict[str, Dict[str, Any]]) -> Dict[str, Any]: """ This function is used to tweak the graph data using the node id and the tweaks dict. @@ -280,8 +264,6 @@ def process_tweaks( if node_tweaks := tweaks.get(node_id): apply_tweaks(node, node_tweaks) else: - logger.warning( - "Each node should be a dictionary with an 'id' key of type str" - ) + logger.warning("Each node should be a dictionary with an 'id' key of type str") return graph_data diff --git a/src/backend/langflow/server.py b/src/backend/langflow/server.py index 3a2943444..9fe432744 100644 --- a/src/backend/langflow/server.py +++ b/src/backend/langflow/server.py @@ -10,11 +10,7 @@ class LangflowApplication(BaseApplication): super().__init__() def load_config(self): - config = { - key: value - for key, value in self.options.items() - if key in self.cfg.settings and value is not None - } + config = {key: value for key, value in self.options.items() if key in self.cfg.settings and value is not None} for key, value in config.items(): self.cfg.set(key.lower(), value) diff --git a/src/backend/langflow/services/auth/service.py b/src/backend/langflow/services/auth/service.py index 5b0acf8c6..8beef8dcb 100644 --- a/src/backend/langflow/services/auth/service.py +++ b/src/backend/langflow/services/auth/service.py @@ -2,7 +2,7 @@ from langflow.services.base import Service from typing import TYPE_CHECKING if TYPE_CHECKING: - from langflow.services.settings.manager import SettingsService + from langflow.services.settings.service import SettingsService class AuthService(Service): diff --git a/src/backend/langflow/services/auth/utils.py b/src/backend/langflow/services/auth/utils.py index 7cc91c117..505e1888c 100644 --- a/src/backend/langflow/services/auth/utils.py +++ b/src/backend/langflow/services/auth/utils.py @@ -12,19 +12,16 @@ from langflow.services.database.models.user.crud import ( get_user_by_username, update_user_last_login_at, ) -from langflow.services.getters import get_session, get_settings_service +from langflow.services.deps import get_session, get_settings_service from sqlmodel import Session +from cryptography.fernet import Fernet oauth2_login = OAuth2PasswordBearer(tokenUrl="api/v1/login", auto_error=False) API_KEY_NAME = "x-api-key" -api_key_query = APIKeyQuery( - name=API_KEY_NAME, scheme_name="API key query", auto_error=False -) -api_key_header = APIKeyHeader( - name=API_KEY_NAME, scheme_name="API key header", auto_error=False -) +api_key_query = APIKeyQuery(name=API_KEY_NAME, scheme_name="API key query", auto_error=False) +api_key_header = APIKeyHeader(name=API_KEY_NAME, scheme_name="API key header", auto_error=False) # Source: https://github.com/mrtolkien/fastapi_simple_security/blob/master/fastapi_simple_security/security_api_key.py @@ -141,23 +138,17 @@ def get_current_active_user(current_user: Annotated[User, Depends(get_current_us return current_user -def get_current_active_superuser( - current_user: Annotated[User, Depends(get_current_user)] -) -> User: +def get_current_active_superuser(current_user: Annotated[User, Depends(get_current_user)]) -> User: if not current_user.is_active: raise HTTPException(status_code=401, detail="Inactive user") if not current_user.is_superuser: - raise HTTPException( - status_code=400, detail="The user doesn't have enough privileges" - ) + raise HTTPException(status_code=400, detail="The user doesn't have enough privileges") return current_user def verify_password(plain_password, hashed_password): settings_service = get_settings_service() - return settings_service.auth_settings.pwd_context.verify( - plain_password, hashed_password - ) + return settings_service.auth_settings.pwd_context.verify(plain_password, hashed_password) def get_password_hash(password): @@ -246,22 +237,16 @@ def get_user_id_from_token(token: str) -> UUID: return UUID(int=0) -def create_user_tokens( - user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False -) -> dict: +def create_user_tokens(user_id: UUID, db: Session = Depends(get_session), update_last_login: bool = False) -> dict: settings_service = get_settings_service() - access_token_expires = timedelta( - minutes=settings_service.auth_settings.ACCESS_TOKEN_EXPIRE_MINUTES - ) + access_token_expires = timedelta(minutes=settings_service.auth_settings.ACCESS_TOKEN_EXPIRE_MINUTES) access_token = create_token( data={"sub": str(user_id)}, expires_delta=access_token_expires, ) - refresh_token_expires = timedelta( - minutes=settings_service.auth_settings.REFRESH_TOKEN_EXPIRE_MINUTES - ) + refresh_token_expires = timedelta(minutes=settings_service.auth_settings.REFRESH_TOKEN_EXPIRE_MINUTES) refresh_token = create_token( data={"sub": str(user_id), "type": "rf"}, expires_delta=refresh_token_expires, @@ -291,9 +276,7 @@ def create_refresh_token(refresh_token: str, db: Session = Depends(get_session)) token_type: str = payload.get("type") # type: ignore if user_id is None or token_type is None: - raise HTTPException( - status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token" - ) + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid refresh token") return create_user_tokens(user_id, db) @@ -304,9 +287,7 @@ def create_refresh_token(refresh_token: str, db: Session = Depends(get_session)) ) from e -def authenticate_user( - username: str, password: str, db: Session = Depends(get_session) -) -> Optional[User]: +def authenticate_user(username: str, password: str, db: Session = Depends(get_session)) -> Optional[User]: user = get_user_by_username(db, username) if not user: @@ -318,3 +299,33 @@ def authenticate_user( raise HTTPException(status_code=400, detail="Inactive user") return user if verify_password(password, user.password) else None + + +def add_padding(s): + # Calculate the number of padding characters needed + padding_needed = 4 - len(s) % 4 + return s + "=" * padding_needed + + +def get_fernet(settings_service=Depends(get_settings_service)): + SECRET_KEY = settings_service.auth_settings.SECRET_KEY + # It's important that your secret key is 32 url-safe base64-encoded bytes + padded_secret_key = add_padding(SECRET_KEY) + fernet = Fernet(padded_secret_key) + return fernet + + +def encrypt_api_key(api_key: str, settings_service=Depends(get_settings_service)): + fernet = get_fernet(settings_service) + # Two-way encryption + encrypted_key = fernet.encrypt(api_key.encode()) + return encrypted_key + + +def decrypt_api_key(encrypted_api_key: str, settings_service=Depends(get_settings_service)): + fernet = get_fernet(settings_service) + # Two-way decryption + if isinstance(encrypted_api_key, str): + encrypted_api_key = encrypted_api_key.encode() + decrypted_key = fernet.decrypt(encrypted_api_key).decode() + return decrypted_key diff --git a/src/backend/langflow/services/cache/__init__.py b/src/backend/langflow/services/cache/__init__.py index 3b122aa9e..bf3a7c5ee 100644 --- a/src/backend/langflow/services/cache/__init__.py +++ b/src/backend/langflow/services/cache/__init__.py @@ -1,9 +1,9 @@ -from . import factory, manager -from langflow.services.cache.manager import InMemoryCache +from . import factory, service +from langflow.services.cache.service import InMemoryCache __all__ = [ "factory", - "manager", + "service", "InMemoryCache", ] diff --git a/src/backend/langflow/services/cache/factory.py b/src/backend/langflow/services/cache/factory.py index f00ab239f..10e657bc5 100644 --- a/src/backend/langflow/services/cache/factory.py +++ b/src/backend/langflow/services/cache/factory.py @@ -1,10 +1,10 @@ -from langflow.services.cache.manager import InMemoryCache, RedisCache, BaseCacheService +from langflow.services.cache.service import InMemoryCache, RedisCache, BaseCacheService from langflow.services.factory import ServiceFactory from langflow.utils.logger import logger from typing import TYPE_CHECKING if TYPE_CHECKING: - from langflow.services.settings.manager import SettingsService + from langflow.services.settings.service import SettingsService class CacheServiceFactory(ServiceFactory): @@ -26,9 +26,7 @@ class CacheServiceFactory(ServiceFactory): if redis_cache.is_connected(): logger.debug("Redis cache is connected") return redis_cache - logger.warning( - "Redis cache is not connected, falling back to in-memory cache" - ) + logger.warning("Redis cache is not connected, falling back to in-memory cache") return InMemoryCache() elif settings_service.settings.CACHE_TYPE == "memory": diff --git a/src/backend/langflow/services/cache/manager.py b/src/backend/langflow/services/cache/service.py similarity index 94% rename from src/backend/langflow/services/cache/manager.py rename to src/backend/langflow/services/cache/service.py index da76a2b5c..3ee8d001b 100644 --- a/src/backend/langflow/services/cache/manager.py +++ b/src/backend/langflow/services/cache/service.py @@ -68,10 +68,7 @@ class InMemoryCache(BaseCacheService, Service): Retrieve an item from the cache without acquiring the lock. """ if item := self._cache.get(key): - if ( - self.expiration_time is None - or time.time() - item["time"] < self.expiration_time - ): + if self.expiration_time is None or time.time() - item["time"] < self.expiration_time: # Move the key to the end to make it recently used self._cache.move_to_end(key) # Check if the value is pickled @@ -118,11 +115,7 @@ class InMemoryCache(BaseCacheService, Service): """ with self._lock: existing_value = self._get_without_lock(key) - if ( - existing_value is not None - and isinstance(existing_value, dict) - and isinstance(value, dict) - ): + if existing_value is not None and isinstance(existing_value, dict) and isinstance(value, dict): existing_value.update(value) value = existing_value @@ -276,9 +269,7 @@ class RedisCache(BaseCacheService, Service): if not result: raise ValueError("RedisCache could not set the value.") except TypeError as exc: - raise TypeError( - "RedisCache only accepts values that can be pickled. " - ) from exc + raise TypeError("RedisCache only accepts values that can be pickled. ") from exc def upsert(self, key, value): """ @@ -290,11 +281,7 @@ class RedisCache(BaseCacheService, Service): value: The value to insert or update. """ existing_value = self.get(key) - if ( - existing_value is not None - and isinstance(existing_value, dict) - and isinstance(value, dict) - ): + if existing_value is not None and isinstance(existing_value, dict) and isinstance(value, dict): existing_value.update(value) value = existing_value diff --git a/src/backend/langflow/services/cache/utils.py b/src/backend/langflow/services/cache/utils.py index 6443d2ea7..a6c62d7ec 100644 --- a/src/backend/langflow/services/cache/utils.py +++ b/src/backend/langflow/services/cache/utils.py @@ -83,9 +83,7 @@ def clear_old_cache_files(max_cache_size: int = 3): cache_files = list(cache_dir.glob("*.dill")) if len(cache_files) > max_cache_size: - cache_files_sorted_by_mtime = sorted( - cache_files, key=lambda x: x.stat().st_mtime, reverse=True - ) + cache_files_sorted_by_mtime = sorted(cache_files, key=lambda x: x.stat().st_mtime, reverse=True) for cache_file in cache_files_sorted_by_mtime[max_cache_size:]: with contextlib.suppress(OSError): diff --git a/src/backend/langflow/services/chat/factory.py b/src/backend/langflow/services/chat/factory.py index 54af7fcca..337488e0f 100644 --- a/src/backend/langflow/services/chat/factory.py +++ b/src/backend/langflow/services/chat/factory.py @@ -1,4 +1,4 @@ -from langflow.services.chat.manager import ChatService +from langflow.services.chat.service import ChatService from langflow.services.factory import ServiceFactory diff --git a/src/backend/langflow/services/chat/manager.py b/src/backend/langflow/services/chat/service.py similarity index 92% rename from src/backend/langflow/services/chat/manager.py rename to src/backend/langflow/services/chat/service.py index 59488d431..1287f08ba 100644 --- a/src/backend/langflow/services/chat/manager.py +++ b/src/backend/langflow/services/chat/service.py @@ -1,20 +1,20 @@ -from collections import defaultdict +import asyncio import uuid +from collections import defaultdict +from typing import Any, Dict, List + +import orjson from fastapi import WebSocket, status -from starlette.websockets import WebSocketState from langflow.api.v1.schemas import ChatMessage, ChatResponse, FileResponse from langflow.interface.utils import pil_to_base64 +from langflow.services import ServiceType, service_manager from langflow.services.base import Service from langflow.services.chat.cache import Subject from langflow.services.chat.utils import process_graph from loguru import logger +from starlette.websockets import WebSocketState from .cache import cache_service -import asyncio -from typing import Any, Dict, List - -from langflow.services import service_manager, ServiceType -import orjson class ChatHistory(Subject): @@ -59,9 +59,7 @@ class ChatService(Service): """Send the last chat message to the client.""" client_id = self.chat_cache.current_client_id if client_id in self.active_connections: - chat_response = self.chat_history.get_history( - client_id, filter_messages=False - )[-1] + chat_response = self.chat_history.get_history(client_id, filter_messages=False)[-1] if chat_response.is_bot: # Process FileResponse if isinstance(chat_response, FileResponse): @@ -88,9 +86,7 @@ class ChatService(Service): data_type=self.last_cached_object_dict["type"], ) - self.chat_history.add_message( - self.chat_cache.current_client_id, chat_response - ) + self.chat_history.add_message(self.chat_cache.current_client_id, chat_response) async def connect(self, client_id: str, websocket: WebSocket): self.active_connections[client_id] = websocket @@ -108,7 +104,7 @@ class ChatService(Service): async def send_json(self, client_id: str, message: ChatMessage): websocket = self.active_connections[client_id] - await websocket.send_json(message.dict()) + await websocket.send_json(message.model_dump()) async def close_connection(self, client_id: str, code: int, reason: str): if websocket := self.active_connections[client_id]: @@ -121,9 +117,7 @@ class ChatService(Service): if "after sending" in str(exc): logger.error(f"Error closing connection: {exc}") - async def process_message( - self, client_id: str, payload: Dict, langchain_object: Any - ): + async def process_message(self, client_id: str, payload: Dict, langchain_object: Any): # Process the graph data and chat message chat_inputs = payload.pop("inputs", {}) chatkey = payload.pop("chatKey", None) @@ -197,7 +191,7 @@ class ChatService(Service): try: chat_history = self.chat_history.get_history(client_id) # iterate and make BaseModel into dict - chat_history = [chat.dict() for chat in chat_history] + chat_history = [chat.model_dump() for chat in chat_history] await websocket.send_json(chat_history) while True: @@ -211,15 +205,11 @@ class ChatService(Service): continue with self.chat_cache.set_client_id(client_id): - if langchain_object := self.cache_service.get(client_id).get( - "result" - ): + if langchain_object := self.cache_service.get(client_id).get("result"): await self.process_message(client_id, payload, langchain_object) else: - raise RuntimeError( - f"Could not find a build result for client_id {client_id}" - ) + raise RuntimeError(f"Could not find a build result for client_id {client_id}") except Exception as exc: # Handle any exceptions that might occur logger.exception(f"Error handling websocket: {exc}") diff --git a/src/backend/langflow/services/chat/utils.py b/src/backend/langflow/services/chat/utils.py index 85b86a801..604f4f5a5 100644 --- a/src/backend/langflow/services/chat/utils.py +++ b/src/backend/langflow/services/chat/utils.py @@ -15,9 +15,7 @@ async def process_graph( if langchain_object is None: # Raise user facing error - raise ValueError( - "There was an error loading the langchain_object. Please, check all the nodes and try again." - ) + raise ValueError("There was an error loading the langchain_object. Please, check all the nodes and try again.") # Generate result and thought try: diff --git a/src/backend/langflow/services/database/factory.py b/src/backend/langflow/services/database/factory.py index 3726f520b..57bf1668d 100644 --- a/src/backend/langflow/services/database/factory.py +++ b/src/backend/langflow/services/database/factory.py @@ -1,9 +1,9 @@ from typing import TYPE_CHECKING -from langflow.services.database.manager import DatabaseService +from langflow.services.database.service import DatabaseService from langflow.services.factory import ServiceFactory if TYPE_CHECKING: - from langflow.services.settings.manager import SettingsService + from langflow.services.settings.service import SettingsService class DatabaseServiceFactory(ServiceFactory): diff --git a/src/backend/langflow/services/database/models/api_key/crud.py b/src/backend/langflow/services/database/models/api_key/crud.py index 0e0ae6137..806848218 100644 --- a/src/backend/langflow/services/database/models/api_key/crud.py +++ b/src/backend/langflow/services/database/models/api_key/crud.py @@ -18,9 +18,7 @@ def get_api_keys(session: Session, user_id: UUID) -> List[ApiKeyRead]: return [ApiKeyRead.from_orm(api_key) for api_key in api_keys] -def create_api_key( - session: Session, api_key_create: ApiKeyCreate, user_id: UUID -) -> UnmaskedApiKeyRead: +def create_api_key(session: Session, api_key_create: ApiKeyCreate, user_id: UUID) -> UnmaskedApiKeyRead: # Generate a random API key with 32 bytes of randomness generated_api_key = f"sk-{secrets.token_urlsafe(32)}" diff --git a/src/backend/langflow/services/database/models/flow/flow.py b/src/backend/langflow/services/database/models/flow/flow.py index b544766d8..310969364 100644 --- a/src/backend/langflow/services/database/models/flow/flow.py +++ b/src/backend/langflow/services/database/models/flow/flow.py @@ -15,6 +15,7 @@ class FlowBase(SQLModelSerializable): name: str = Field(index=True) description: Optional[str] = Field(index=True, nullable=True, default=None) data: Optional[Dict] = Field(default=None, nullable=True) + is_component: Optional[bool] = Field(default=False, nullable=True) @field_validator("data") def validate_json(v): @@ -35,7 +36,7 @@ class FlowBase(SQLModelSerializable): class Flow(FlowBase, table=True): id: UUID = Field(default_factory=uuid4, primary_key=True, unique=True) data: Optional[Dict] = Field(default=None, sa_column=Column(JSON)) - user_id: UUID = Field(index=True, foreign_key="user.id") + user_id: UUID = Field(index=True, foreign_key="user.id", nullable=True) user: "User" = Relationship(back_populates="flows") diff --git a/src/backend/langflow/services/database/models/user/crud.py b/src/backend/langflow/services/database/models/user/crud.py index 6f0e019f6..c7239bcee 100644 --- a/src/backend/langflow/services/database/models/user/crud.py +++ b/src/backend/langflow/services/database/models/user/crud.py @@ -3,7 +3,7 @@ from typing import Union from uuid import UUID from fastapi import Depends, HTTPException, status from langflow.services.database.models.user.user import User, UserUpdate -from langflow.services.getters import get_session +from langflow.services.deps import get_session from sqlalchemy.exc import IntegrityError from sqlmodel import Session from typing import Optional @@ -19,9 +19,7 @@ def get_user_by_id(db: Session, id: UUID) -> Union[User, None]: return db.query(User).filter(User.id == id).first() -def update_user( - user_db: Optional[User], user: UserUpdate, db: Session = Depends(get_session) -) -> User: +def update_user(user_db: Optional[User], user: UserUpdate, db: Session = Depends(get_session)) -> User: if not user_db: raise HTTPException(status_code=404, detail="User not found") @@ -37,9 +35,7 @@ def update_user( changed = True if not changed: - raise HTTPException( - status_code=status.HTTP_304_NOT_MODIFIED, detail="Nothing to update" - ) + raise HTTPException(status_code=status.HTTP_304_NOT_MODIFIED, detail="Nothing to update") user_db.updated_at = datetime.now(timezone.utc) flag_modified(user_db, "updated_at") diff --git a/src/backend/langflow/services/database/models/user/user.py b/src/backend/langflow/services/database/models/user/user.py index f3248736e..50525d025 100644 --- a/src/backend/langflow/services/database/models/user/user.py +++ b/src/backend/langflow/services/database/models/user/user.py @@ -25,6 +25,7 @@ class User(SQLModelSerializable, table=True): back_populates="user", sa_relationship_kwargs={"cascade": "delete"}, ) + store_api_key: str = Field(default=None, nullable=True) flows: list["Flow"] = Relationship(back_populates="user") diff --git a/src/backend/langflow/services/database/manager.py b/src/backend/langflow/services/database/service.py similarity index 82% rename from src/backend/langflow/services/database/manager.py rename to src/backend/langflow/services/database/service.py index ce5de7095..48e576e81 100644 --- a/src/backend/langflow/services/database/manager.py +++ b/src/backend/langflow/services/database/service.py @@ -3,15 +3,17 @@ from typing import TYPE_CHECKING from langflow.services.base import Service from langflow.services.database.models.user.crud import get_user_by_username from langflow.services.database.utils import Result, TableResults -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service +from langflow.services.utils import teardown_superuser from sqlalchemy import inspect import sqlalchemy as sa from sqlalchemy.exc import OperationalError from sqlmodel import SQLModel, Session, create_engine from loguru import logger from alembic.config import Config -from alembic import command +from alembic import command, util from langflow.services.database import models # noqa +import time if TYPE_CHECKING: from sqlalchemy.engine import Engine @@ -32,10 +34,7 @@ class DatabaseService(Service): def _create_engine(self) -> "Engine": """Create the engine for the database.""" settings_service = get_settings_service() - if ( - settings_service.settings.DATABASE_URL - and settings_service.settings.DATABASE_URL.startswith("sqlite") - ): + if settings_service.settings.DATABASE_URL and settings_service.settings.DATABASE_URL.startswith("sqlite"): connect_args = {"check_same_thread": False} else: connect_args = {} @@ -47,9 +46,7 @@ class DatabaseService(Service): def __exit__(self, exc_type, exc_value, traceback): if exc_type is not None: # If an exception has been raised - logger.error( - f"Session rollback because of exception: {exc_type.__name__} {exc_value}" - ) + logger.error(f"Session rollback because of exception: {exc_type.__name__} {exc_value}") self._session.rollback() else: self._session.commit() @@ -97,9 +94,7 @@ class DatabaseService(Service): expected_columns = list(model.model_fields.keys()) try: - available_columns = [ - col["name"] for col in inspector.get_columns(table) - ] + available_columns = [col["name"] for col in inspector.get_columns(table)] except sa.exc.NoSuchTableError: logger.error(f"Missing table: {table}") return False @@ -148,17 +143,29 @@ class DatabaseService(Service): alembic_cfg = Config() alembic_cfg.set_main_option("script_location", str(self.script_location)) alembic_cfg.set_main_option("sqlalchemy.url", self.database_url) - command.upgrade(alembic_cfg, "head") + try: + command.check(alembic_cfg) + except Exception as exc: + if isinstance(exc, util.exc.CommandError) or isinstance(exc, util.exc.AutogenerateDiffsDetected): + command.upgrade(alembic_cfg, "head") + + # We should check the schema health after running migrations + try: + command.check(alembic_cfg) + except util.exc.AutogenerateDiffsDetected: + # downgrade to base and upgrade again + logger.warning("Autogenerate diffs detected, downgrading and upgrading") + command.downgrade(alembic_cfg, "-1") + # wait for the database to be ready + time.sleep(5) + command.upgrade(alembic_cfg, "head") def run_migrations_test(self): # This method is used for testing purposes only # We will check that all models are in the database # and that the database is up to date with all columns sql_models = [models.Flow, models.User, models.ApiKey] - return [ - TableResults(sql_model.__tablename__, self.check_table(sql_model)) - for sql_model in sql_models - ] + return [TableResults(sql_model.__tablename__, self.check_table(sql_model)) for sql_model in sql_models] def check_table(self, model): results = [] @@ -166,9 +173,7 @@ class DatabaseService(Service): table_name = model.__tablename__ expected_columns = list(model.__fields__.keys()) try: - available_columns = [ - col["name"] for col in inspector.get_columns(table_name) - ] + available_columns = [col["name"] for col in inspector.get_columns(table_name)] results.append(Result(name=table_name, type="table", success=True)) except sa.exc.NoSuchTableError: logger.error(f"Missing table: {table_name}") @@ -199,9 +204,7 @@ class DatabaseService(Service): try: table.create(self.engine, checkfirst=True) except OperationalError as oe: - logger.warning( - f"Table {table} already exists, skipping. Exception: {oe}" - ) + logger.warning(f"Table {table} already exists, skipping. Exception: {oe}") except Exception as exc: logger.error(f"Error creating table {table}: {exc}") raise RuntimeError(f"Error creating table {table}") from exc @@ -213,9 +216,7 @@ class DatabaseService(Service): if table not in table_names: logger.error("Something went wrong creating the database and tables.") logger.error("Please check your database settings.") - raise RuntimeError( - "Something went wrong creating the database and tables." - ) + raise RuntimeError("Something went wrong creating the database and tables.") logger.debug("Database and tables created successfully") @@ -225,14 +226,8 @@ class DatabaseService(Service): settings_service = get_settings_service() # remove the default superuser if auto_login is enabled # using the SUPERUSER to get the user - if settings_service.auth_settings.AUTO_LOGIN: - logger.debug("Removing default superuser") - username = settings_service.auth_settings.SUPERUSER - with Session(self.engine) as session: - user = get_user_by_username(session, username) - session.delete(user) - session.commit() - logger.debug("Default superuser removed") + with Session(self.engine) as session: + teardown_superuser(settings_service, session) except Exception as exc: logger.error(f"Error tearing down database: {exc}") diff --git a/src/backend/langflow/services/database/utils.py b/src/backend/langflow/services/database/utils.py index b457b70e2..aae567e91 100644 --- a/src/backend/langflow/services/database/utils.py +++ b/src/backend/langflow/services/database/utils.py @@ -6,16 +6,14 @@ from alembic.util.exc import CommandError from sqlmodel import Session if TYPE_CHECKING: - from langflow.services.database.manager import DatabaseService + from langflow.services.database.service import DatabaseService def initialize_database(): logger.debug("Initializing database") from langflow.services import service_manager, ServiceType - database_service: "DatabaseService" = service_manager.get( - ServiceType.DATABASE_SERVICE - ) + database_service: "DatabaseService" = service_manager.get(ServiceType.DATABASE_SERVICE) try: database_service.create_db_and_tables() except Exception as exc: @@ -41,9 +39,7 @@ def initialize_database(): # This means there's wrong revision in the DB # We need to delete the alembic_version table # and run the migrations again - logger.warning( - "Wrong revision in DB, deleting alembic_version table and running migrations again" - ) + logger.warning("Wrong revision in DB, deleting alembic_version table and running migrations again") with session_getter(database_service) as session: session.execute("DROP TABLE alembic_version") database_service.run_migrations() diff --git a/src/backend/langflow/services/getters.py b/src/backend/langflow/services/deps.py similarity index 69% rename from src/backend/langflow/services/getters.py rename to src/backend/langflow/services/deps.py index e88b998b5..776c0b2c4 100644 --- a/src/backend/langflow/services/getters.py +++ b/src/backend/langflow/services/deps.py @@ -1,16 +1,18 @@ -from langflow.services import ServiceType, service_manager from typing import TYPE_CHECKING, Generator +from langflow.services import ServiceType, service_manager if TYPE_CHECKING: - from langflow.services.database.manager import DatabaseService - from langflow.services.settings.manager import SettingsService - from langflow.services.cache.manager import BaseCacheService - from langflow.services.session.manager import SessionService - from langflow.services.task.manager import TaskService - from langflow.services.chat.manager import ChatService from sqlmodel import Session + from langflow.services.cache.service import BaseCacheService + from langflow.services.chat.service import ChatService + from langflow.services.database.service import DatabaseService + from langflow.services.session.service import SessionService + from langflow.services.settings.service import SettingsService + from langflow.services.store.service import StoreService + from langflow.services.task.service import TaskService + def get_settings_service() -> "SettingsService": try: @@ -46,3 +48,7 @@ def get_task_service() -> "TaskService": def get_chat_service() -> "ChatService": return service_manager.get(ServiceType.CHAT_SERVICE) + + +def get_store_service() -> "StoreService": + return service_manager.get(ServiceType.STORE_SERVICE) diff --git a/src/backend/langflow/services/manager.py b/src/backend/langflow/services/manager.py index 10fd6b699..fce0e9106 100644 --- a/src/backend/langflow/services/manager.py +++ b/src/backend/langflow/services/manager.py @@ -53,15 +53,10 @@ class ServiceManager: self._create_service(dependency) # Collect the dependent services - dependent_services = { - dep.value: self.services[dep] - for dep in self.dependencies.get(service_name, []) - } + dependent_services = {dep.value: self.services[dep] for dep in self.dependencies.get(service_name, [])} # Create the actual service - self.services[service_name] = self.factories[service_name].create( - **dependent_services - ) + self.services[service_name] = self.factories[service_name].create(**dependent_services) self.services[service_name].set_ready() def _validate_service_creation(self, service_name: ServiceType): @@ -69,9 +64,7 @@ class ServiceManager: Validate whether the service can be created. """ if service_name not in self.factories: - raise ValueError( - f"No factory registered for the service class '{service_name.name}'" - ) + raise ValueError(f"No factory registered for the service class '{service_name.name}'") def update(self, service_name: ServiceType): """ @@ -144,9 +137,7 @@ def initialize_session_service(): initialize_settings_service() - service_manager.register_factory( - cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE] - ) + service_manager.register_factory(cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE]) service_manager.register_factory( session_service_factory.SessionServiceFactory(), diff --git a/src/backend/langflow/services/plugins/langfuse.py b/src/backend/langflow/services/plugins/langfuse.py index 7a1f60a48..103cfe260 100644 --- a/src/backend/langflow/services/plugins/langfuse.py +++ b/src/backend/langflow/services/plugins/langfuse.py @@ -1,4 +1,4 @@ -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.utils.logger import logger ### Temporary implementation @@ -23,10 +23,7 @@ class LangfuseInstance: settings_manager = get_settings_service() - if ( - settings_manager.settings.LANGFUSE_PUBLIC_KEY - and settings_manager.settings.LANGFUSE_SECRET_KEY - ): + if settings_manager.settings.LANGFUSE_PUBLIC_KEY and settings_manager.settings.LANGFUSE_SECRET_KEY: logger.debug("Langfuse credentials found") cls._instance = Langfuse( public_key=settings_manager.settings.LANGFUSE_PUBLIC_KEY, diff --git a/src/backend/langflow/services/schema.py b/src/backend/langflow/services/schema.py index 8b3b41fcb..b899923fe 100644 --- a/src/backend/langflow/services/schema.py +++ b/src/backend/langflow/services/schema.py @@ -14,3 +14,4 @@ class ServiceType(str, Enum): CHAT_SERVICE = "chat_service" SESSION_SERVICE = "session_service" TASK_SERVICE = "task_service" + STORE_SERVICE = "store_service" diff --git a/src/backend/langflow/services/session/factory.py b/src/backend/langflow/services/session/factory.py index 9abe025a8..beb0bd6bd 100644 --- a/src/backend/langflow/services/session/factory.py +++ b/src/backend/langflow/services/session/factory.py @@ -1,9 +1,9 @@ from typing import TYPE_CHECKING -from langflow.services.session.manager import SessionService +from langflow.services.session.service import SessionService from langflow.services.factory import ServiceFactory if TYPE_CHECKING: - from langflow.services.cache.manager import BaseCacheService + from langflow.services.cache.service import BaseCacheService class SessionServiceFactory(ServiceFactory): diff --git a/src/backend/langflow/services/session/manager.py b/src/backend/langflow/services/session/service.py similarity index 100% rename from src/backend/langflow/services/session/manager.py rename to src/backend/langflow/services/session/service.py diff --git a/src/backend/langflow/services/session/utils.py b/src/backend/langflow/services/session/utils.py index 374d85540..1d62508a3 100644 --- a/src/backend/langflow/services/session/utils.py +++ b/src/backend/langflow/services/session/utils.py @@ -3,6 +3,4 @@ import string def session_id_generator(size=6): - return "".join( - random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=size) - ) + return "".join(random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=size)) diff --git a/src/backend/langflow/services/settings/__init__.py b/src/backend/langflow/services/settings/__init__.py index 2191bf2cc..3c76ca0de 100644 --- a/src/backend/langflow/services/settings/__init__.py +++ b/src/backend/langflow/services/settings/__init__.py @@ -1,3 +1,3 @@ -from . import factory, manager +from . import factory, service -__all__ = ["factory", "manager"] +__all__ = ["factory", "service"] diff --git a/src/backend/langflow/services/settings/auth.py b/src/backend/langflow/services/settings/auth.py index 08553186e..92a696cc5 100644 --- a/src/backend/langflow/services/settings/auth.py +++ b/src/backend/langflow/services/settings/auth.py @@ -2,17 +2,13 @@ import secrets from pathlib import Path from typing import Optional +from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD +from langflow.services.settings.utils import read_secret_from_file, write_secret_to_file from loguru import logger from passlib.context import CryptContext from pydantic import Field, validator from pydantic_settings import BaseSettings -from langflow.services.settings.constants import ( - DEFAULT_SUPERUSER, - DEFAULT_SUPERUSER_PASSWORD, -) -from langflow.services.settings.utils import read_secret_from_file, write_secret_to_file - class AuthSettings(BaseSettings): # Login settings @@ -24,12 +20,10 @@ class AuthSettings(BaseSettings): ) ALGORITHM: str = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 - REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 12 + REFRESH_TOKEN_EXPIRE_MINUTES: int = 60 * 12 * 7 # API Key to execute /process endpoint - API_KEY_SECRET_KEY: Optional[ - str - ] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a" + API_KEY_SECRET_KEY: Optional[str] = "b82818e0ad4ff76615c5721ee21004b07d84cd9b87ba4d9cb42374da134b841a" API_KEY_ALGORITHM: str = "HS256" API_V1_STR: str = "/api/v1" diff --git a/src/backend/langflow/services/settings/base.py b/src/backend/langflow/services/settings/base.py index 053c45afd..63c9de38d 100644 --- a/src/backend/langflow/services/settings/base.py +++ b/src/backend/langflow/services/settings/base.py @@ -1,15 +1,15 @@ import contextlib import json -import orjson import os -from shutil import copy2 -from typing import Optional, List from pathlib import Path +from shutil import copy2 +from typing import List, Optional +import orjson import yaml +from loguru import logger from pydantic import field_validator, validator from pydantic_settings import BaseSettings, SettingsConfigDict -from loguru import logger # BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components") BASE_COMPONENTS_PATH = str(Path(__file__).parent.parent.parent / "components") @@ -53,6 +53,13 @@ class Settings(BaseSettings): LANGFUSE_PUBLIC_KEY: Optional[str] = None LANGFUSE_HOST: Optional[str] = None + STORE: Optional[bool] = True + STORE_URL: Optional[str] = "https://api.langflow.store" + DOWNLOAD_WEBHOOK_URL: Optional[ + str + ] = "https://api.langflow.store/flows/trigger/ec611a61-8460-4438-b187-a4f65e5559d4" + LIKE_WEBHOOK_URL: Optional[str] = "https://api.langflow.store/flows/trigger/64275852-ec00-45c1-984e-3bff814732da" + @validator("CONFIG_DIR", pre=True, allow_reuse=True) def set_langflow_dir(cls, value): if not value: @@ -79,9 +86,7 @@ class Settings(BaseSettings): @validator("DATABASE_URL", pre=True) def set_database_url(cls, value, values): if not value: - logger.debug( - "No database_url provided, trying LANGFLOW_DATABASE_URL env variable" - ) + logger.debug("No database_url provided, trying LANGFLOW_DATABASE_URL env variable") if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"): value = langflow_database_url logger.debug("Using LANGFLOW_DATABASE_URL env variable.") @@ -91,9 +96,7 @@ class Settings(BaseSettings): # so we need to migrate to the new format # if there is a database in that location if not values["CONFIG_DIR"]: - raise ValueError( - "CONFIG_DIR not set, please set it or provide a DATABASE_URL" - ) + raise ValueError("CONFIG_DIR not set, please set it or provide a DATABASE_URL") new_path = f"{values['CONFIG_DIR']}/langflow.db" if Path("./langflow.db").exists(): @@ -117,22 +120,15 @@ class Settings(BaseSettings): if os.getenv("LANGFLOW_COMPONENTS_PATH"): logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path") langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH") - if ( - Path(langflow_component_path).exists() - and langflow_component_path not in value - ): + if Path(langflow_component_path).exists() and langflow_component_path not in value: if isinstance(langflow_component_path, list): for path in langflow_component_path: if path not in value: value.append(path) - logger.debug( - f"Extending {langflow_component_path} to components_path" - ) + logger.debug(f"Extending {langflow_component_path} to components_path") elif langflow_component_path not in value: value.append(langflow_component_path) - logger.debug( - f"Appending {langflow_component_path} to components_path" - ) + logger.debug(f"Appending {langflow_component_path} to components_path") if not value: value = [BASE_COMPONENTS_PATH] @@ -144,9 +140,7 @@ class Settings(BaseSettings): logger.debug(f"Components path: {value}") return value - model_config = SettingsConfigDict( - validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_" - ) + model_config = SettingsConfigDict(validate_assignment=True, extra="ignore", env_prefix="LANGFLOW_") # @model_validator() # @classmethod @@ -211,7 +205,7 @@ class Settings(BaseSettings): def save_settings_to_yaml(settings: Settings, file_path: str): with open(file_path, "w") as f: - settings_dict = settings.dict() + settings_dict = settings.model_dump() yaml.dump(settings_dict, f) diff --git a/src/backend/langflow/services/settings/factory.py b/src/backend/langflow/services/settings/factory.py index 9202ae8c3..713f13f82 100644 --- a/src/backend/langflow/services/settings/factory.py +++ b/src/backend/langflow/services/settings/factory.py @@ -1,5 +1,5 @@ from pathlib import Path -from langflow.services.settings.manager import SettingsService +from langflow.services.settings.service import SettingsService from langflow.services.factory import ServiceFactory @@ -10,6 +10,4 @@ class SettingsServiceFactory(ServiceFactory): def create(self): # Here you would have logic to create and configure a SettingsService langflow_dir = Path(__file__).parent.parent.parent - return SettingsService.load_settings_from_yaml( - str(langflow_dir / "config.yaml") - ) + return SettingsService.load_settings_from_yaml(str(langflow_dir / "config.yaml")) diff --git a/src/backend/langflow/services/settings/manager.py b/src/backend/langflow/services/settings/service.py similarity index 91% rename from src/backend/langflow/services/settings/manager.py rename to src/backend/langflow/services/settings/service.py index 40bf396f0..a57a59eb8 100644 --- a/src/backend/langflow/services/settings/manager.py +++ b/src/backend/langflow/services/settings/service.py @@ -30,9 +30,7 @@ class SettingsService(Service): for key in settings_dict: if key not in Settings.model_fields.keys(): raise KeyError(f"Key {key} not found in settings") - logger.debug( - f"Loading {len(settings_dict[key])} {key} from {file_path}" - ) + logger.debug(f"Loading {len(settings_dict[key])} {key} from {file_path}") settings = Settings(**settings_dict) if not settings.CONFIG_DIR: diff --git a/src/backend/langflow/services/settings/utils.py b/src/backend/langflow/services/settings/utils.py index fae96ff28..1fd308e72 100644 --- a/src/backend/langflow/services/settings/utils.py +++ b/src/backend/langflow/services/settings/utils.py @@ -14,9 +14,7 @@ def set_secure_permissions(file_path): import win32security user, domain, _ = win32security.LookupAccountName("", win32api.GetUserName()) - sd = win32security.GetFileSecurity( - file_path, win32security.DACL_SECURITY_INFORMATION - ) + sd = win32security.GetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION) dacl = win32security.ACL() # Set the new DACL for the file: read and write access for the owner, no access for everyone else @@ -26,9 +24,7 @@ def set_secure_permissions(file_path): user, ) sd.SetSecurityDescriptorDacl(1, dacl, 0) - win32security.SetFileSecurity( - file_path, win32security.DACL_SECURITY_INFORMATION, sd - ) + win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, sd) else: print("Unsupported OS") diff --git a/src/backend/langflow/services/store/__init__.py b/src/backend/langflow/services/store/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/backend/langflow/services/store/exceptions.py b/src/backend/langflow/services/store/exceptions.py new file mode 100644 index 000000000..058b21652 --- /dev/null +++ b/src/backend/langflow/services/store/exceptions.py @@ -0,0 +1,25 @@ +class CustomException(Exception): + def __init__(self, detail, status_code): + super().__init__(detail) + self.status_code = status_code + + +# Define custom exceptions with status codes +class UnauthorizedError(CustomException): + def __init__(self, detail="Unauthorized access"): + super().__init__(detail, 401) + + +class ForbiddenError(CustomException): + def __init__(self, detail="Forbidden"): + super().__init__(detail, 403) + + +class APIKeyError(CustomException): + def __init__(self, detail="API key error"): + super().__init__(detail, 401) + + +class FilterError(CustomException): + def __init__(self, detail="Filter error"): + super().__init__(detail, 400) diff --git a/src/backend/langflow/services/store/factory.py b/src/backend/langflow/services/store/factory.py new file mode 100644 index 000000000..a25ad78c7 --- /dev/null +++ b/src/backend/langflow/services/store/factory.py @@ -0,0 +1,14 @@ +from typing import TYPE_CHECKING +from langflow.services.store.service import StoreService +from langflow.services.factory import ServiceFactory + +if TYPE_CHECKING: + from langflow.services.settings.service import SettingsService + + +class StoreServiceFactory(ServiceFactory): + def __init__(self): + super().__init__(StoreService) + + def create(self, settings_service: "SettingsService"): + return StoreService(settings_service) diff --git a/src/backend/langflow/services/store/schema.py b/src/backend/langflow/services/store/schema.py new file mode 100644 index 000000000..0f99c0cb3 --- /dev/null +++ b/src/backend/langflow/services/store/schema.py @@ -0,0 +1,74 @@ +from typing import List, Optional +from uuid import UUID + +from pydantic import BaseModel, validator + + +class TagResponse(BaseModel): + id: UUID + name: Optional[str] + + +class UsersLikesResponse(BaseModel): + likes_count: Optional[int] + liked_by_user: Optional[bool] + + +class CreateComponentResponse(BaseModel): + id: UUID + + +class TagsIdResponse(BaseModel): + tags_id: Optional[TagResponse] + + +class ListComponentResponse(BaseModel): + id: UUID + name: Optional[str] + description: Optional[str] + liked_by_count: Optional[int] + liked_by_user: Optional[bool] = None + is_component: Optional[bool] + metadata: Optional[dict] + user_created: Optional[dict] + tags: Optional[List[TagResponse]] = None + downloads_count: Optional[int] + last_tested_version: Optional[str] + + # tags comes as a TagsIdResponse but we want to return a list of TagResponse + @validator("tags", pre=True) + def tags_to_list(cls, v): + # Check if all values are have id and name + # if so, return v else transform to TagResponse + if not v: + return v + if all(["id" in tag and "name" in tag for tag in v]): + return v + else: + return [TagResponse(**tag.get("tags_id")) for tag in v if tag.get("tags_id")] + + +class ListComponentResponseModel(BaseModel): + count: Optional[int] = 0 + authorized: bool + results: Optional[List[ListComponentResponse]] + + +class DownloadComponentResponse(BaseModel): + id: UUID + name: Optional[str] + description: Optional[str] + data: Optional[dict] + is_component: Optional[bool] + metadata: Optional[dict] = {} + + +class StoreComponentCreate(BaseModel): + name: str + description: Optional[str] + data: dict + tags: Optional[List[str]] + parent: Optional[UUID] = None + is_component: Optional[bool] + last_tested_version: Optional[str] = None + private: Optional[bool] = True diff --git a/src/backend/langflow/services/store/service.py b/src/backend/langflow/services/store/service.py new file mode 100644 index 000000000..9c300799d --- /dev/null +++ b/src/backend/langflow/services/store/service.py @@ -0,0 +1,486 @@ +import json +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple +from uuid import UUID + +import httpx +from httpx import HTTPError, HTTPStatusError +from langflow.services.base import Service +from langflow.services.store.exceptions import APIKeyError, FilterError, ForbiddenError +from langflow.services.store.schema import ( + CreateComponentResponse, + DownloadComponentResponse, + ListComponentResponse, + ListComponentResponseModel, + StoreComponentCreate, +) +from langflow.services.store.utils import ( + process_component_data, + process_tags_for_post, + update_components_with_user_data, +) +from loguru import logger + +if TYPE_CHECKING: + from langflow.services.settings.service import SettingsService + +from contextlib import asynccontextmanager +from contextvars import ContextVar + +user_data_var: ContextVar[Optional[Dict[str, Any]]] = ContextVar("user_data", default=None) + + +@asynccontextmanager +async def user_data_context(store_service: "StoreService", api_key: Optional[str] = None): + # Fetch and set user data to the context variable + if api_key: + try: + user_data, _ = await store_service._get( + f"{store_service.base_url}/users/me", api_key, params={"fields": "id"} + ) + user_data_var.set(user_data[0]) + except HTTPStatusError as exc: + if exc.response.status_code == 403: + raise ValueError("Invalid API key") + try: + yield + finally: + # Clear the user data from the context variable + user_data_var.set(None) + + +class StoreService(Service): + """This is a service that integrates langflow with the store which + is a Directus instance. It allows to search, get and post components to + the store.""" + + name = "store_service" + + def __init__(self, settings_service: "SettingsService"): + self.settings_service = settings_service + self.base_url = self.settings_service.settings.STORE_URL + self.download_webhook_url = self.settings_service.settings.DOWNLOAD_WEBHOOK_URL + self.like_webhook_url = self.settings_service.settings.LIKE_WEBHOOK_URL + self.components_url = f"{self.base_url}/items/components" + self.default_fields = [ + "id", + "name", + "description", + "user_created.username", + "is_component", + "tags.tags_id.name", + "tags.tags_id.id", + "count(liked_by)", + "count(downloads)", + "metadata", + "last_tested_version", + ] + + # Create a context manager that will use the api key to + # get the user data and all requests inside the context manager + # will make a property return that data + # Without making the request multiple times + + async def check_api_key(self, api_key: str): + # Check if the api key is valid + # If it is, return True + # If it is not, return False + try: + user_data, _ = await self._get(f"{self.base_url}/users/me", api_key, params={"fields": "id"}) + + return "id" in user_data[0] + except HTTPStatusError as exc: + if exc.response.status_code in [403, 401]: + return False + else: + raise ValueError(f"Unexpected status code: {exc.response.status_code}") + except Exception as exc: + raise ValueError(f"Unexpected error: {exc}") + + async def _get( + self, url: str, api_key: Optional[str] = None, params: Optional[Dict[str, Any]] = None + ) -> Tuple[List[Dict[str, Any]], Dict[str, Any]]: + """Utility method to perform GET requests.""" + if api_key: + headers = {"Authorization": f"Bearer {api_key}"} + else: + headers = {} + async with httpx.AsyncClient() as client: + try: + response = await client.get(url, headers=headers, params=params) + response.raise_for_status() + except HTTPError as exc: + raise exc + except Exception as exc: + raise ValueError(f"GET failed: {exc}") + json_response = response.json() + result = json_response["data"] + metadata = {} + if "meta" in json_response: + metadata = json_response["meta"] + + if isinstance(result, dict): + return [result], metadata + return result, metadata + + async def call_webhook(self, api_key: str, webhook_url: str, component_id: UUID) -> None: + # The webhook is a POST request with the data in the body + # For now we are calling it just for testing + try: + headers = {"Authorization": f"Bearer {api_key}"} + async with httpx.AsyncClient() as client: + response = await client.post(webhook_url, headers=headers, json={"component_id": str(component_id)}) + response.raise_for_status() + return response.json() + except HTTPError as exc: + raise exc + except Exception as exc: + logger.debug(f"Webhook failed: {exc}") + + def build_tags_filter(self, tags: List[str]): + tags_filter: Dict[str, Any] = {"tags": {"_and": []}} + for tag in tags: + tags_filter["tags"]["_and"].append({"_some": {"tags_id": {"name": {"_eq": tag}}}}) + return tags_filter + + async def count_components( + self, + filter_conditions: List[Dict[str, Any]], + api_key: Optional[str] = None, + use_api_key: Optional[bool] = False, + ) -> int: + params = {"aggregate": json.dumps({"count": "*"})} + if filter_conditions: + params["filter"] = json.dumps({"_and": filter_conditions}) + + api_key = api_key if use_api_key else None + + results, _ = await self._get(self.components_url, api_key, params) + return int(results[0].get("count", 0)) + + @staticmethod + def build_search_filter_conditions(query: str): + # instead of build the param ?search=query, we will build the filter + # that will use _icontains (case insensitive) + conditions: Dict[str, Any] = {"_or": []} + conditions["_or"].append({"name": {"_icontains": query}}) + conditions["_or"].append({"description": {"_icontains": query}}) + conditions["_or"].append({"tags": {"tags_id": {"name": {"_icontains": query}}}}) + return conditions + + def build_filter_conditions( + self, + search: Optional[str] = None, + private: Optional[bool] = None, + tags: Optional[List[str]] = None, + is_component: Optional[bool] = None, + filter_by_user: Optional[bool] = False, + liked: Optional[bool] = False, + store_api_Key: Optional[str] = None, + ): + filter_conditions = [] + + if search is not None: + search_conditions = self.build_search_filter_conditions(search) + filter_conditions.append(search_conditions) + + if private is not None: + filter_conditions.append({"private": {"_eq": private}}) + + if tags: + tags_filter = self.build_tags_filter(tags) + filter_conditions.append(tags_filter) + + if is_component is not None: + filter_conditions.append({"is_component": {"_eq": is_component}}) + if liked and store_api_Key: + liked_filter = self.build_liked_filter() + filter_conditions.append(liked_filter) + elif liked and not store_api_Key: + raise APIKeyError("You must provide an API key to filter by likes") + + if filter_by_user and store_api_Key: + user_data = user_data_var.get() + if not user_data: + raise ValueError("No user data") + filter_conditions.append({"user_created": {"_eq": user_data["id"]}}) + elif filter_by_user and not store_api_Key: + raise APIKeyError("You must provide an API key to filter your components") + else: + filter_conditions.append({"private": {"_eq": False}}) + + return filter_conditions + + def build_liked_filter(self): + user_data = user_data_var.get() + # params["filter"] = json.dumps({"user_created": {"_eq": user_data["id"]}}) + if not user_data: + raise ValueError("No user data") + return {"liked_by": {"directus_users_id": {"_eq": user_data["id"]}}} + + async def query_components( + self, + api_key: Optional[str] = None, + sort: Optional[List[str]] = None, + page: int = 1, + limit: int = 15, + fields: Optional[List[str]] = None, + filter_conditions: Optional[List[Dict[str, Any]]] = None, + use_api_key: Optional[bool] = False, + ) -> Tuple[List[ListComponentResponse], Dict[str, Any]]: + params: Dict[str, Any] = { + "page": page, + "limit": limit, + "fields": ",".join(fields) if fields else ",".join(self.default_fields), + "meta": "filter_count", # !This is DEPRECATED so we should remove it ASAP + } + # ?aggregate[count]=likes + + if sort: + params["sort"] = ",".join(sort) + + # Only public components or the ones created by the user + # check for "public" or "Public" + + if filter_conditions: + params["filter"] = json.dumps({"_and": filter_conditions}) + + # If not liked, this means we are getting public components + # so we don't need to risk passing an invalid api_key + # and getting 401 + api_key = api_key if use_api_key else None + results, metadata = await self._get(self.components_url, api_key, params) + if isinstance(results, dict): + results = [results] + + results_objects = [ListComponentResponse(**result) for result in results] + + return results_objects, metadata + + async def get_liked_by_user_components(self, component_ids: List[str], api_key: str) -> List[str]: + # Get fields id + # filter should be "id is in component_ids AND liked_by directus_users_id token is api_key" + # return the ids + user_data = user_data_var.get() + if not user_data: + raise ValueError("No user data") + params = { + "fields": "id", + "filter": json.dumps( + { + "_and": [ + {"id": {"_in": component_ids}}, + {"liked_by": {"directus_users_id": {"_eq": user_data["id"]}}}, + ] + } + ), + } + results, _ = await self._get(self.components_url, api_key, params) + return [result["id"] for result in results] + + # Which of the components is parent of the user's components + async def get_components_in_users_collection(self, component_ids: List[str], api_key: str): + user_data = user_data_var.get() + if not user_data: + raise ValueError("No user data") + params = { + "fields": "id", + "filter": json.dumps( + { + "_and": [ + {"user_created": {"_eq": user_data["id"]}}, + {"parent": {"_in": component_ids}}, + ] + } + ), + } + results, _ = await self._get(self.components_url, api_key, params) + return [result["id"] for result in results] + + async def download(self, api_key: str, component_id: UUID) -> DownloadComponentResponse: + url = f"{self.components_url}/{component_id}" + params = {"fields": ",".join(["id", "name", "description", "data", "is_component", "metadata"])} + if not self.download_webhook_url: + raise ValueError("DOWNLOAD_WEBHOOK_URL is not set") + component, _ = await self._get(url, api_key, params) + await self.call_webhook(api_key, self.download_webhook_url, component_id) + if len(component) > 1: + raise ValueError("Something went wrong while downloading the component") + component_dict = component[0] + + download_component = DownloadComponentResponse(**component_dict) + # Check if metadata is an empty dict + if download_component.metadata in [None, {}] and download_component.data is not None: + # If it is, we need to build the metadata + try: + download_component.metadata = process_component_data(download_component.data.get("nodes", [])) + except KeyError: + raise ValueError("Invalid component data. No nodes found") + return download_component + + async def upload(self, api_key: str, component_data: StoreComponentCreate) -> CreateComponentResponse: + headers = {"Authorization": f"Bearer {api_key}"} + component_dict = component_data.model_dump(exclude_unset=True) + # Parent is a UUID, but the store expects a string + response = None + if component_dict.get("parent"): + component_dict["parent"] = str(component_dict["parent"]) + + component_dict = process_tags_for_post(component_dict) + try: + # response = httpx.post(self.components_url, headers=headers, json=component_dict) + # response.raise_for_status() + async with httpx.AsyncClient() as client: + response = await client.post(self.components_url, headers=headers, json=component_dict) + response.raise_for_status() + component = response.json()["data"] + return CreateComponentResponse(**component) + except HTTPError as exc: + if response: + try: + errors = response.json() + message = errors["errors"][0]["message"] + raise FilterError(message) + except UnboundLocalError: + pass + raise ValueError(f"Upload failed: {exc}") + + async def get_tags(self) -> List[Dict[str, Any]]: + url = f"{self.base_url}/items/tags" + params = {"fields": ",".join(["id", "name"])} + tags, _ = await self._get(url, api_key=None, params=params) + return tags + + async def get_user_likes(self, api_key: str) -> List[Dict[str, Any]]: + url = f"{self.base_url}/users/me" + params = { + "fields": ",".join(["id", "likes"]), + } + likes, _ = await self._get(url, api_key, params) + return likes + + async def get_component_likes_count(self, component_id: str, api_key: Optional[str] = None) -> int: + url = f"{self.components_url}/{component_id}" + + params = { + "fields": ",".join(["id", "count(liked_by)"]), + } + result, _ = await self._get(url, api_key=api_key, params=params) + if len(result) == 0: + raise ValueError("Component not found") + likes = result[0]["liked_by_count"] + # likes_by_count is a string + # try to convert it to int + try: + likes = int(likes) + except ValueError: + raise ValueError(f"Unexpected value for likes count: {likes}") + return likes + + async def like_component(self, api_key: str, component_id: str) -> bool: + # if it returns a list with one id, it means the like was successful + # if it returns an int, it means the like was removed + if not self.like_webhook_url: + raise ValueError("LIKE_WEBHOOK_URL is not set") + headers = {"Authorization": f"Bearer {api_key}"} + # response = httpx.post( + # self.like_webhook_url, + # json={"component_id": str(component_id)}, + # headers=headers, + # ) + + # response.raise_for_status() + async with httpx.AsyncClient() as client: + response = await client.post( + self.like_webhook_url, + json={"component_id": str(component_id)}, + headers=headers, + ) + response.raise_for_status() + if response.status_code == 200: + result = response.json() + + if isinstance(result, list): + return True + elif isinstance(result, int): + return False + else: + raise ValueError(f"Unexpected result: {result}") + else: + raise ValueError(f"Unexpected status code: {response.status_code}") + + async def get_list_component_response_model( + self, + search: Optional[str] = None, + private: Optional[bool] = None, + tags: Optional[List[str]] = None, + is_component: Optional[bool] = None, + filter_by_user: bool = False, + liked: bool = False, + store_api_key: Optional[str] = None, + sort: Optional[List[str]] = None, + page: int = 1, + limit: int = 15, + ): + async with user_data_context(api_key=store_api_key, store_service=self): + filter_conditions: List[Dict[str, Any]] = self.build_filter_conditions( + search=search, + private=private, + tags=tags, + is_component=is_component, + filter_by_user=filter_by_user, + liked=liked, + store_api_Key=store_api_key, + ) + + result: List[ListComponentResponse] = [] + authorized = False + try: + result, metadata = await self.query_components( + api_key=store_api_key, + page=page, + limit=limit, + sort=sort, + filter_conditions=filter_conditions, + use_api_key=liked or filter_by_user, + ) + if metadata: + comp_count = metadata.get("filter_count", 0) + except HTTPStatusError as exc: + if exc.response.status_code == 403: + raise ForbiddenError("You are not authorized to access this public resource") + elif exc.response.status_code == 401: + raise APIKeyError("You are not authorized to access this resource. Please check your API key.") + try: + if result and not metadata: + if len(result) >= limit: + comp_count = await self.count_components( + api_key=store_api_key, + filter_conditions=filter_conditions, + use_api_key=liked or filter_by_user, + ) + else: + comp_count = len(result) + elif not metadata: + comp_count = 0 + except HTTPStatusError as exc: + if exc.response.status_code == 403: + raise ForbiddenError("You are not authorized to access this public resource") + elif exc.response.status_code == 401: + raise APIKeyError("You are not authorized to access this resource. Please check your API key.") + + if store_api_key: + # Now, from the result, we need to get the components + # the user likes and set the liked_by_user to True + if result: + try: + updated_result = await update_components_with_user_data( + result, self, store_api_key, liked=liked + ) + authorized = True + result = updated_result + except Exception: + # If we get an error here, it means the user is not authorized + authorized = False + else: + authorized = await self.check_api_key(store_api_key) + return ListComponentResponseModel(results=result, authorized=authorized, count=comp_count) diff --git a/src/backend/langflow/services/store/utils.py b/src/backend/langflow/services/store/utils.py new file mode 100644 index 000000000..3ce4434f1 --- /dev/null +++ b/src/backend/langflow/services/store/utils.py @@ -0,0 +1,64 @@ +from typing import TYPE_CHECKING, List + +import httpx + +if TYPE_CHECKING: + from langflow.services.store.schema import ListComponentResponse + from langflow.services.store.service import StoreService + + +def process_tags_for_post(component_dict): + tags = component_dict.pop("tags", None) + if tags and all(isinstance(tag, str) for tag in tags): + component_dict["tags"] = [{"tags_id": tag} for tag in tags] + return component_dict + + +async def update_components_with_user_data( + components: List["ListComponentResponse"], + store_service: "StoreService", + store_api_Key: str, + liked: bool, +): + """ + Updates the components with the user data (liked_by_user and in_users_collection) + """ + component_ids = [str(component.id) for component in components] + if liked: + # If liked is True, this means all we got were liked_by_user components + # So we can set liked_by_user to True for all components + liked_by_user_ids = component_ids + else: + liked_by_user_ids = await store_service.get_liked_by_user_components( + component_ids=component_ids, + api_key=store_api_Key, + ) + # Now we need to set the liked_by_user attribute + for component in components: + component.liked_by_user = str(component.id) in liked_by_user_ids + + return components + + +# Get the latest released version of langflow (https://pypi.org/project/langflow/) +def get_lf_version_from_pypi(): + try: + response = httpx.get("https://pypi.org/pypi/langflow/json") + if response.status_code != 200: + return None + return response.json()["info"]["version"] + except Exception: + return None + + +def process_component_data(nodes_list): + names = [node["id"].split("-")[0] for node in nodes_list] + metadata = {} + for name in names: + if name in metadata: + metadata[name]["count"] += 1 + else: + metadata[name] = {"count": 1} + metadata["total"] = len(names) + + return metadata diff --git a/src/backend/langflow/services/task/backends/celery.py b/src/backend/langflow/services/task/backends/celery.py index eae985f3a..f23374549 100644 --- a/src/backend/langflow/services/task/backends/celery.py +++ b/src/backend/langflow/services/task/backends/celery.py @@ -10,9 +10,7 @@ class CeleryBackend(TaskBackend): def __init__(self): self.celery_app = celery_app - def launch_task( - self, task_func: Callable[..., Any], *args: Any, **kwargs: Any - ) -> tuple[str, AsyncResult]: + def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> tuple[str, AsyncResult]: # I need to type the delay method to make it easier from celery import Task # type: ignore diff --git a/src/backend/langflow/services/task/factory.py b/src/backend/langflow/services/task/factory.py index efb6ac24d..e87eecc94 100644 --- a/src/backend/langflow/services/task/factory.py +++ b/src/backend/langflow/services/task/factory.py @@ -1,4 +1,4 @@ -from langflow.services.task.manager import TaskService +from langflow.services.task.service import TaskService from langflow.services.factory import ServiceFactory diff --git a/src/backend/langflow/services/task/manager.py b/src/backend/langflow/services/task/service.py similarity index 95% rename from src/backend/langflow/services/task/manager.py rename to src/backend/langflow/services/task/service.py index 807505c3a..6730b1d91 100644 --- a/src/backend/langflow/services/task/manager.py +++ b/src/backend/langflow/services/task/service.py @@ -63,9 +63,7 @@ class TaskService(Service): result = task.get() return task.id, result - async def launch_task( - self, task_func: Callable[..., Any], *args: Any, **kwargs: Any - ) -> Any: + async def launch_task(self, task_func: Callable[..., Any], *args: Any, **kwargs: Any) -> Any: logger.debug(f"Launching task {task_func} with args {args} and kwargs {kwargs}") logger.debug(f"Using backend {self.backend}") task = self.backend.launch_task(task_func, *args, **kwargs) diff --git a/src/backend/langflow/services/utils.py b/src/backend/langflow/services/utils.py index adbf072fe..72807ee0c 100644 --- a/src/backend/langflow/services/utils.py +++ b/src/backend/langflow/services/utils.py @@ -2,23 +2,22 @@ from langflow.services.auth.utils import create_super_user, verify_password from langflow.services.database.utils import initialize_database from langflow.services.manager import service_manager from langflow.services.schema import ServiceType -from langflow.services.settings.constants import ( - DEFAULT_SUPERUSER, - DEFAULT_SUPERUSER_PASSWORD, -) -from sqlmodel import Session -from .getters import get_db_service, get_session, get_settings_service +from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD from loguru import logger +from sqlmodel import Session + +from .deps import get_db_service, get_session, get_settings_service def get_factories_and_deps(): - from langflow.services.database import factory as database_factory + from langflow.services.auth import factory as auth_factory from langflow.services.cache import factory as cache_factory from langflow.services.chat import factory as chat_factory - from langflow.services.settings import factory as settings_factory - from langflow.services.auth import factory as auth_factory - from langflow.services.task import factory as task_factory + from langflow.services.database import factory as database_factory from langflow.services.session import factory as session_service_factory # type: ignore + from langflow.services.settings import factory as settings_factory + from langflow.services.store import factory as store_factory + from langflow.services.task import factory as task_factory return [ (settings_factory.SettingsServiceFactory(), []), @@ -40,6 +39,7 @@ def get_factories_and_deps(): session_service_factory.SessionServiceFactory(), [ServiceType.CACHE_SERVICE], ), + (store_factory.StoreServiceFactory(), [ServiceType.SETTINGS_SERVICE]), ] @@ -69,16 +69,12 @@ def get_or_create_super_user(session: Session, username, password, is_default): ) return None else: - logger.debug( - "User with superuser credentials exists but is not a superuser." - ) + logger.debug("User with superuser credentials exists but is not a superuser.") return None if user: if verify_password(password, user.password): - raise ValueError( - "User with superuser credentials exists but is not a superuser." - ) + raise ValueError("User with superuser credentials exists but is not a superuser.") else: raise ValueError("Incorrect superuser credentials") @@ -100,25 +96,22 @@ def get_or_create_super_user(session: Session, username, password, is_default): def setup_superuser(settings_service, session: Session): if settings_service.auth_settings.AUTO_LOGIN: logger.debug("AUTO_LOGIN is set to True. Creating default superuser.") + else: + # Remove the default superuser if it exists + teardown_superuser(settings_service, session) username = settings_service.auth_settings.SUPERUSER password = settings_service.auth_settings.SUPERUSER_PASSWORD - is_default = (username == DEFAULT_SUPERUSER) and ( - password == DEFAULT_SUPERUSER_PASSWORD - ) + is_default = (username == DEFAULT_SUPERUSER) and (password == DEFAULT_SUPERUSER_PASSWORD) try: - user = get_or_create_super_user( - session=session, username=username, password=password, is_default=is_default - ) + user = get_or_create_super_user(session=session, username=username, password=password, is_default=is_default) if user is not None: logger.debug("Superuser created successfully.") except Exception as exc: logger.exception(exc) - raise RuntimeError( - "Could not create superuser. Please create a superuser manually." - ) from exc + raise RuntimeError("Could not create superuser. Please create a superuser manually.") from exc finally: settings_service.auth_settings.reset_credentials() @@ -130,10 +123,10 @@ def teardown_superuser(settings_service, session): # If AUTO_LOGIN is True, we will remove the default superuser # from the database. - if settings_service.auth_settings.AUTO_LOGIN: + if not settings_service.auth_settings.AUTO_LOGIN: try: - logger.debug("AUTO_LOGIN is set to True. Removing default superuser.") - username = settings_service.auth_settings.SUPERUSER + logger.debug("AUTO_LOGIN is set to False. Removing default superuser if exists.") + username = DEFAULT_SUPERUSER from langflow.services.database.models.user.user import User user = session.query(User).filter(User.username == username).first() @@ -175,14 +168,12 @@ def initialize_session_service(): """ Initialize the session manager. """ - from langflow.services.session import factory as session_service_factory # type: ignore from langflow.services.cache import factory as cache_factory + from langflow.services.session import factory as session_service_factory # type: ignore initialize_settings_service() - service_manager.register_factory( - cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE] - ) + service_manager.register_factory(cache_factory.CacheServiceFactory(), dependencies=[ServiceType.SETTINGS_SERVICE]) service_manager.register_factory( session_service_factory.SessionServiceFactory(), @@ -199,17 +190,13 @@ def initialize_services(): service_manager.register_factory(factory, dependencies=dependencies) except Exception as exc: logger.exception(exc) - raise RuntimeError( - "Could not initialize services. Please check your settings." - ) from exc + raise RuntimeError("Could not initialize services. Please check your settings.") from exc # Test cache connection service_manager.get(ServiceType.CACHE_SERVICE) # Setup the superuser initialize_database() - setup_superuser( - service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session()) - ) + setup_superuser(service_manager.get(ServiceType.SETTINGS_SERVICE), next(get_session())) try: get_db_service().migrate_flows_if_auto_login() except Exception as exc: diff --git a/src/backend/langflow/settings.py b/src/backend/langflow/settings.py index 6b6e81baf..625bd56e5 100644 --- a/src/backend/langflow/settings.py +++ b/src/backend/langflow/settings.py @@ -1,12 +1,13 @@ import contextlib import json import os -from typing import Optional, List from pathlib import Path +from typing import List, Optional import yaml -from pydantic import validator, model_validator +from pydantic import model_validator, validator from pydantic_settings import BaseSettings + from langflow.utils.logger import logger BASE_COMPONENTS_PATH = str(Path(__file__).parent / "components") @@ -39,9 +40,7 @@ class Settings(BaseSettings): @validator("DATABASE_URL", pre=True) def set_database_url(cls, value): if not value: - logger.debug( - "No database_url provided, trying LANGFLOW_DATABASE_URL env variable" - ) + logger.debug("No database_url provided, trying LANGFLOW_DATABASE_URL env variable") if langflow_database_url := os.getenv("LANGFLOW_DATABASE_URL"): value = langflow_database_url logger.debug("Using LANGFLOW_DATABASE_URL env variable.") @@ -56,22 +55,15 @@ class Settings(BaseSettings): if os.getenv("LANGFLOW_COMPONENTS_PATH"): logger.debug("Adding LANGFLOW_COMPONENTS_PATH to components_path") langflow_component_path = os.getenv("LANGFLOW_COMPONENTS_PATH") - if ( - Path(langflow_component_path).exists() - and langflow_component_path not in value - ): + if Path(langflow_component_path).exists() and langflow_component_path not in value: if isinstance(langflow_component_path, list): for path in langflow_component_path: if path not in value: value.append(path) - logger.debug( - f"Extending {langflow_component_path} to components_path" - ) + logger.debug(f"Extending {langflow_component_path} to components_path") elif langflow_component_path not in value: value.append(langflow_component_path) - logger.debug( - f"Appending {langflow_component_path} to components_path" - ) + logger.debug(f"Appending {langflow_component_path} to components_path") if not value: value = [BASE_COMPONENTS_PATH] @@ -150,7 +142,7 @@ class Settings(BaseSettings): def save_settings_to_yaml(settings: Settings, file_path: str): with open(file_path, "w") as f: - settings_dict = settings.dict() + settings_dict = settings.model_dump() yaml.dump(settings_dict, f) diff --git a/src/backend/langflow/template/field/base.py b/src/backend/langflow/template/field/base.py index 31c68d094..e596f21be 100644 --- a/src/backend/langflow/template/field/base.py +++ b/src/backend/langflow/template/field/base.py @@ -60,7 +60,7 @@ class TemplateFieldCreator(BaseModel, ABC): """Additional information about the field to be shown in the tooltip. Defaults to an empty string.""" def to_dict(self): - result = self.dict() + result = self.model_dump() # Remove key if it is None for key in list(result.keys()): if result[key] is None or result[key] == []: diff --git a/src/backend/langflow/template/frontend_node/base.py b/src/backend/langflow/template/frontend_node/base.py index 4b1c151a3..dc64b0fd8 100644 --- a/src/backend/langflow/template/frontend_node/base.py +++ b/src/backend/langflow/template/frontend_node/base.py @@ -67,11 +67,7 @@ class FrontendNode(BaseModel): def process_base_classes(self) -> None: """Removes unwanted base classes from the list of base classes.""" - self.base_classes = [ - base_class - for base_class in self.base_classes - if base_class not in CLASSES_TO_REMOVE - ] + self.base_classes = [base_class for base_class in self.base_classes if base_class not in CLASSES_TO_REMOVE] def to_dict(self) -> dict: """Returns a dict representation of the frontend node.""" @@ -130,9 +126,7 @@ class FrontendNode(BaseModel): return _type @staticmethod - def handle_special_field( - field, key: str, _type: str, SPECIAL_FIELD_HANDLERS - ) -> str: + def handle_special_field(field, key: str, _type: str, SPECIAL_FIELD_HANDLERS) -> str: """Handles special field by using the respective handler if present.""" handler = SPECIAL_FIELD_HANDLERS.get(key) return handler(field) if handler else _type @@ -144,11 +138,7 @@ class FrontendNode(BaseModel): field.field_type = "file" field.suffixes = [".json", ".yaml", ".yml"] field.file_types = ["json", "yaml", "yml"] - elif ( - _type.startswith("Dict") - or _type.startswith("Mapping") - or _type.startswith("dict") - ): + elif _type.startswith("Dict") or _type.startswith("Mapping") or _type.startswith("dict"): field.field_type = "dict" return _type @@ -159,9 +149,7 @@ class FrontendNode(BaseModel): field.value = value["default"] @staticmethod - def handle_specific_field_values( - field: TemplateField, key: str, name: Optional[str] = None - ) -> None: + def handle_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None: """Handles specific field values for certain fields.""" if key == "headers": field.value = """{"Authorization": "Bearer "}""" @@ -169,9 +157,7 @@ class FrontendNode(BaseModel): FrontendNode._handle_api_key_specific_field_values(field, key, name) @staticmethod - def _handle_model_specific_field_values( - field: TemplateField, key: str, name: Optional[str] = None - ) -> None: + def _handle_model_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None: """Handles specific field values related to models.""" model_dict = { "OpenAI": constants.OPENAI_MODELS, @@ -184,9 +170,7 @@ class FrontendNode(BaseModel): field.is_list = True @staticmethod - def _handle_api_key_specific_field_values( - field: TemplateField, key: str, name: Optional[str] = None - ) -> None: + def _handle_api_key_specific_field_values(field: TemplateField, key: str, name: Optional[str] = None) -> None: """Handles specific field values related to API keys.""" if "api_key" in key and "OpenAI" in str(name): field.display_name = "OpenAI API Key" @@ -225,10 +209,7 @@ class FrontendNode(BaseModel): @staticmethod def should_be_password(key: str, show: bool) -> bool: """Determines whether the field should be a password field.""" - return ( - any(text in key.lower() for text in {"password", "token", "api", "key"}) - and show - ) + return any(text in key.lower() for text in {"password", "token", "api", "key"}) and show @staticmethod def should_be_multiline(key: str) -> bool: diff --git a/src/backend/langflow/template/frontend_node/chains.py b/src/backend/langflow/template/frontend_node/chains.py index 18cedbe1d..f6e1eacd1 100644 --- a/src/backend/langflow/template/frontend_node/chains.py +++ b/src/backend/langflow/template/frontend_node/chains.py @@ -135,7 +135,9 @@ class SeriesCharacterChainNode(FrontendNode): ), ], ) - description: str = "SeriesCharacterChain is a chain you can use to have a conversation with a character from a series." # noqa + description: str = ( + "SeriesCharacterChain is a chain you can use to have a conversation with a character from a series." # noqa + ) base_classes: list[str] = [ "LLMChain", "BaseCustomChain", diff --git a/src/backend/langflow/template/frontend_node/custom_components.py b/src/backend/langflow/template/frontend_node/custom_components.py index e239775bc..60c3f0bc0 100644 --- a/src/backend/langflow/template/frontend_node/custom_components.py +++ b/src/backend/langflow/template/frontend_node/custom_components.py @@ -1,8 +1,9 @@ +from typing import Optional + +from langflow.interface.custom.constants import DEFAULT_CUSTOM_COMPONENT_CODE from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode from langflow.template.template.base import Template -from langflow.interface.custom.constants import DEFAULT_CUSTOM_COMPONENT_CODE -from typing import Optional class CustomComponentFrontendNode(FrontendNode): diff --git a/src/backend/langflow/template/frontend_node/documentloaders.py b/src/backend/langflow/template/frontend_node/documentloaders.py index ed5a0fa42..3b9458321 100644 --- a/src/backend/langflow/template/frontend_node/documentloaders.py +++ b/src/backend/langflow/template/frontend_node/documentloaders.py @@ -3,9 +3,7 @@ from langflow.template.field.base import TemplateField from langflow.template.frontend_node.base import FrontendNode -def build_file_field( - suffixes: list, fileTypes: list, name: str = "file_path" -) -> TemplateField: +def build_file_field(suffixes: list, fileTypes: list, name: str = "file_path") -> TemplateField: """Build a template field for a document loader.""" return TemplateField( field_type="file", @@ -27,32 +25,22 @@ class DocumentLoaderFrontNode(FrontendNode): "AirbyteJSONLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]), "CoNLLULoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]), "CSVLoader": build_file_field(suffixes=[".csv"], fileTypes=["csv"]), - "UnstructuredEmailLoader": build_file_field( - suffixes=[".eml"], fileTypes=["eml"] - ), + "UnstructuredEmailLoader": build_file_field(suffixes=[".eml"], fileTypes=["eml"]), "EverNoteLoader": build_file_field(suffixes=[".xml"], fileTypes=["xml"]), "FacebookChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]), "BSHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]), - "UnstructuredHTMLLoader": build_file_field( - suffixes=[".html"], fileTypes=["html"] - ), + "UnstructuredHTMLLoader": build_file_field(suffixes=[".html"], fileTypes=["html"]), "UnstructuredImageLoader": build_file_field( suffixes=[".jpg", ".jpeg", ".png", ".gif", ".bmp"], fileTypes=["jpg", "jpeg", "png", "gif", "bmp"], ), - "UnstructuredMarkdownLoader": build_file_field( - suffixes=[".md"], fileTypes=["md"] - ), + "UnstructuredMarkdownLoader": build_file_field(suffixes=[".md"], fileTypes=["md"]), "PyPDFLoader": build_file_field(suffixes=[".pdf"], fileTypes=["pdf"]), - "UnstructuredPowerPointLoader": build_file_field( - suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"] - ), + "UnstructuredPowerPointLoader": build_file_field(suffixes=[".pptx", ".ppt"], fileTypes=["pptx", "ppt"]), "SRTLoader": build_file_field(suffixes=[".srt"], fileTypes=["srt"]), "TelegramChatLoader": build_file_field(suffixes=[".json"], fileTypes=["json"]), "TextLoader": build_file_field(suffixes=[".txt"], fileTypes=["txt"]), - "UnstructuredWordDocumentLoader": build_file_field( - suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"] - ), + "UnstructuredWordDocumentLoader": build_file_field(suffixes=[".docx", ".doc"], fileTypes=["docx", "doc"]), } def add_extra_fields(self) -> None: diff --git a/src/backend/langflow/template/frontend_node/embeddings.py b/src/backend/langflow/template/frontend_node/embeddings.py index 665328e78..4c608fe54 100644 --- a/src/backend/langflow/template/frontend_node/embeddings.py +++ b/src/backend/langflow/template/frontend_node/embeddings.py @@ -70,9 +70,7 @@ class EmbeddingFrontendNode(FrontendNode): field.advanced = True split_name = field.name.split("_") title_name = " ".join([s.capitalize() for s in split_name]) - field.display_name = title_name.replace("Openai", "OpenAI").replace( - "Api", "API" - ) + field.display_name = title_name.replace("Openai", "OpenAI").replace("Api", "API") if "api_key" in field.name: field.password = True diff --git a/src/backend/langflow/template/frontend_node/formatter/field_formatters.py b/src/backend/langflow/template/frontend_node/formatter/field_formatters.py index 654a92c2f..35fb74512 100644 --- a/src/backend/langflow/template/frontend_node/formatter/field_formatters.py +++ b/src/backend/langflow/template/frontend_node/formatter/field_formatters.py @@ -112,10 +112,7 @@ class PasswordFieldFormatter(FieldFormatter): def format(self, field: TemplateField, name: Optional[str] = None) -> None: key = field.name show = field.show - if ( - any(text in key.lower() for text in {"password", "token", "api", "key"}) - and show - ): + if any(text in key.lower() for text in {"password", "token", "api", "key"}) and show: field.password = True @@ -157,9 +154,5 @@ class DictCodeFileFormatter(FieldFormatter): field.field_type = "file" field.suffixes = [".json", ".yaml", ".yml"] field.file_types = ["json", "yaml", "yml"] - elif ( - _type.startswith("Dict") - or _type.startswith("Mapping") - or _type.startswith("dict") - ): + elif _type.startswith("Dict") or _type.startswith("Mapping") or _type.startswith("dict"): field.field_type = "dict" diff --git a/src/backend/langflow/template/frontend_node/llms.py b/src/backend/langflow/template/frontend_node/llms.py index b8e007a27..613b2c1d0 100644 --- a/src/backend/langflow/template/frontend_node/llms.py +++ b/src/backend/langflow/template/frontend_node/llms.py @@ -54,9 +54,9 @@ class LLMFrontendNode(FrontendNode): @staticmethod def format_openai_field(field: TemplateField): if "openai" in field.name.lower(): - field.display_name = ( - field.name.title().replace("Openai", "OpenAI").replace("_", " ") - ).replace("Api", "API") + field.display_name = (field.name.title().replace("Openai", "OpenAI").replace("_", " ")).replace( + "Api", "API" + ) if "key" not in field.name.lower() and "token" not in field.name.lower(): field.password = False @@ -109,10 +109,7 @@ class LLMFrontendNode(FrontendNode): if field.name in SHOW_FIELDS: field.show = True - if "api" in field.name and ( - "key" in field.name - or ("token" in field.name and "tokens" not in field.name) - ): + if "api" in field.name and ("key" in field.name or ("token" in field.name and "tokens" not in field.name)): field.password = True field.show = True # Required should be False to support diff --git a/src/backend/langflow/template/frontend_node/memories.py b/src/backend/langflow/template/frontend_node/memories.py index 019dc0fa8..bbf1c9a8d 100644 --- a/src/backend/langflow/template/frontend_node/memories.py +++ b/src/backend/langflow/template/frontend_node/memories.py @@ -76,9 +76,7 @@ class MemoryFrontendNode(FrontendNode): field.show = True field.advanced = False field.value = "" - field.info = ( - INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO - ) + field.info = INPUT_KEY_INFO if field.name == "input_key" else OUTPUT_KEY_INFO if field.name == "memory_key": field.value = "chat_history" diff --git a/src/backend/langflow/template/frontend_node/prompts.py b/src/backend/langflow/template/frontend_node/prompts.py index f0ebc35aa..dccd66301 100644 --- a/src/backend/langflow/template/frontend_node/prompts.py +++ b/src/backend/langflow/template/frontend_node/prompts.py @@ -36,10 +36,7 @@ class PromptFrontendNode(FrontendNode): field.field_type = "prompt" field.advanced = False - if ( - "Union" in field.field_type - and "BaseMessagePromptTemplate" in field.field_type - ): + if "Union" in field.field_type and "BaseMessagePromptTemplate" in field.field_type: field.field_type = "BaseMessagePromptTemplate" # All prompt fields should be password=False diff --git a/src/backend/langflow/utils/payload.py b/src/backend/langflow/utils/payload.py index cac23a0d6..02cca5c71 100644 --- a/src/backend/langflow/utils/payload.py +++ b/src/backend/langflow/utils/payload.py @@ -81,9 +81,7 @@ def build_json(root, graph) -> Dict: raise ValueError(f"No child with type {node_type} found") values = [build_json(child, graph) for child in children] value = ( - list(values) - if value["list"] - else next(iter(values), None) # type: ignore + list(values) if value["list"] else next(iter(values), None) # type: ignore ) final_dict[key] = value diff --git a/src/backend/langflow/utils/util.py b/src/backend/langflow/utils/util.py index 1c7a7f094..c86a5f91f 100644 --- a/src/backend/langflow/utils/util.py +++ b/src/backend/langflow/utils/util.py @@ -15,12 +15,8 @@ def remove_ansi_escape_codes(text): return re.sub(r"\x1b\[[0-9;]*[a-zA-Z]", "", text) -def build_template_from_function( - name: str, type_to_loader_dict: Dict, add_function: bool = False -): - classes = [ - item.__annotations__["return"].__name__ for item in type_to_loader_dict.values() - ] +def build_template_from_function(name: str, type_to_loader_dict: Dict, add_function: bool = False): + classes = [item.__annotations__["return"].__name__ for item in type_to_loader_dict.values()] # Raise error if name is not in chains if name not in classes: @@ -41,9 +37,7 @@ def build_template_from_function( for name_, value_ in value.__repr_args__(): if name_ == "default_factory": try: - variables[class_field_items][ - "default" - ] = get_default_factory( + variables[class_field_items]["default"] = get_default_factory( module=_class.__base__.__module__, function=value_ ) except Exception: @@ -52,9 +46,7 @@ def build_template_from_function( variables[class_field_items][name_] = value_ variables[class_field_items]["placeholder"] = ( - docs.params[class_field_items] - if class_field_items in docs.params - else "" + docs.params[class_field_items] if class_field_items in docs.params else "" ) # Adding function to base classes to allow # the output to be a function @@ -69,9 +61,7 @@ def build_template_from_function( } -def build_template_from_class( - name: str, type_to_cls_dict: Dict, add_function: bool = False -): +def build_template_from_class(name: str, type_to_cls_dict: Dict, add_function: bool = False): classes = [item.__name__ for item in type_to_cls_dict.values()] # Raise error if name is not in chains @@ -95,9 +85,7 @@ def build_template_from_class( for name_, value_ in value.__repr_args__(): if name_ == "default_factory": try: - variables[class_field_items][ - "default" - ] = get_default_factory( + variables[class_field_items]["default"] = get_default_factory( module=_class.__base__.__module__, function=value_ ) except Exception: @@ -106,9 +94,7 @@ def build_template_from_class( variables[class_field_items][name_] = value_ variables[class_field_items]["placeholder"] = ( - docs.params[class_field_items] - if class_field_items in docs.params - else "" + docs.params[class_field_items] if class_field_items in docs.params else "" ) base_classes = get_base_classes(_class) # Adding function to base classes to allow @@ -140,9 +126,7 @@ def build_template_from_method( # Check if the method exists in this class if not hasattr(_class, method_name): - raise ValueError( - f"Method {method_name} not found in class {class_name}" - ) + raise ValueError(f"Method {method_name} not found in class {class_name}") # Get the method method = getattr(_class, method_name) @@ -161,12 +145,8 @@ def build_template_from_method( "_type": _type, **{ name: { - "default": param.default - if param.default != param.empty - else None, - "type": param.annotation - if param.annotation != param.empty - else None, + "default": param.default if param.default != param.empty else None, + "type": param.annotation if param.annotation != param.empty else None, "required": param.default == param.empty, } for name, param in params.items() @@ -253,9 +233,7 @@ def sync_to_async(func): return async_wrapper -def format_dict( - dictionary: Dict[str, Any], class_name: Optional[str] = None -) -> Dict[str, Any]: +def format_dict(dictionary: Dict[str, Any], class_name: Optional[str] = None) -> Dict[str, Any]: """ Formats a dictionary by removing certain keys and modifying the values of other keys. @@ -341,9 +319,7 @@ def check_list_type(_type: str, value: Dict[str, Any]) -> str: The modified type string. """ if any(list_type in _type for list_type in ["List", "Sequence", "Set"]): - _type = ( - _type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1] - ) + _type = _type.replace("List[", "").replace("Sequence[", "").replace("Set[", "")[:-1] value["list"] = True else: value["list"] = False @@ -447,9 +423,7 @@ def set_headers_value(value: Dict[str, Any]) -> None: value["value"] = """{"Authorization": "Bearer "}""" -def add_options_to_field( - value: Dict[str, Any], class_name: Optional[str], key: str -) -> None: +def add_options_to_field(value: Dict[str, Any], class_name: Optional[str], key: str) -> None: """ Adds options to the field based on the class name and key. """ diff --git a/src/backend/langflow/utils/validate.py b/src/backend/langflow/utils/validate.py index f8a9c1d1d..dc0244aca 100644 --- a/src/backend/langflow/utils/validate.py +++ b/src/backend/langflow/utils/validate.py @@ -41,9 +41,7 @@ def validate_code(code): # Evaluate the function definition for node in tree.body: if isinstance(node, ast.FunctionDef): - code_obj = compile( - ast.Module(body=[node], type_ignores=[]), "", "exec" - ) + code_obj = compile(ast.Module(body=[node], type_ignores=[]), "", "exec") try: exec(code_obj) except Exception as e: @@ -63,8 +61,7 @@ def eval_function(function_string: str): ( obj for name, obj in namespace.items() - if isinstance(obj, types.FunctionType) - and obj.__code__.co_filename == "" + if isinstance(obj, types.FunctionType) and obj.__code__.co_filename == "" ), None, ) @@ -88,23 +85,15 @@ def execute_function(code, function_name, *args, **kwargs): exec_globals, locals(), ) - exec_globals[alias.asname or alias.name] = importlib.import_module( - alias.name - ) + exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name) except ModuleNotFoundError as e: - raise ModuleNotFoundError( - f"Module {alias.name} not found. Please install it and try again." - ) from e + raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e function_code = next( - node - for node in module.body - if isinstance(node, ast.FunctionDef) and node.name == function_name + node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name ) function_code.parent = None - code_obj = compile( - ast.Module(body=[function_code], type_ignores=[]), "", "exec" - ) + code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "", "exec") try: exec(code_obj, exec_globals, locals()) except Exception as exc: @@ -131,23 +120,15 @@ def create_function(code, function_name): if isinstance(node, ast.Import): for alias in node.names: try: - exec_globals[alias.asname or alias.name] = importlib.import_module( - alias.name - ) + exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name) except ModuleNotFoundError as e: - raise ModuleNotFoundError( - f"Module {alias.name} not found. Please install it and try again." - ) from e + raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e function_code = next( - node - for node in module.body - if isinstance(node, ast.FunctionDef) and node.name == function_name + node for node in module.body if isinstance(node, ast.FunctionDef) and node.name == function_name ) function_code.parent = None - code_obj = compile( - ast.Module(body=[function_code], type_ignores=[]), "", "exec" - ) + code_obj = compile(ast.Module(body=[function_code], type_ignores=[]), "", "exec") with contextlib.suppress(Exception): exec(code_obj, exec_globals, locals()) exec_globals[function_name] = locals()[function_name] @@ -178,32 +159,20 @@ def create_class(code, class_name): if isinstance(node, ast.Import): for alias in node.names: try: - exec_globals[alias.asname or alias.name] = importlib.import_module( - alias.name - ) + exec_globals[alias.asname or alias.name] = importlib.import_module(alias.name) except ModuleNotFoundError as e: - raise ModuleNotFoundError( - f"Module {alias.name} not found. Please install it and try again." - ) from e + raise ModuleNotFoundError(f"Module {alias.name} not found. Please install it and try again.") from e elif isinstance(node, ast.ImportFrom): try: imported_module = importlib.import_module(node.module) for alias in node.names: exec_globals[alias.name] = getattr(imported_module, alias.name) except ModuleNotFoundError as e: - raise ModuleNotFoundError( - f"Module {node.module} not found. Please install it and try again." - ) from e + raise ModuleNotFoundError(f"Module {node.module} not found. Please install it and try again.") from e - class_code = next( - node - for node in module.body - if isinstance(node, ast.ClassDef) and node.name == class_name - ) + class_code = next(node for node in module.body if isinstance(node, ast.ClassDef) and node.name == class_name) class_code.parent = None - code_obj = compile( - ast.Module(body=[class_code], type_ignores=[]), "", "exec" - ) + code_obj = compile(ast.Module(body=[class_code], type_ignores=[]), "", "exec") # This suppresses import errors # with contextlib.suppress(Exception): exec(code_obj, exec_globals, locals()) diff --git a/src/backend/langflow/worker.py b/src/backend/langflow/worker.py index 2eeba14a5..8f2abcb43 100644 --- a/src/backend/langflow/worker.py +++ b/src/backend/langflow/worker.py @@ -1,15 +1,15 @@ -from langflow.core.celery_app import celery_app -from typing import Any, Dict, Optional -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict, Optional from celery.exceptions import SoftTimeLimitExceeded # type: ignore + +from langflow.core.celery_app import celery_app from langflow.processing.process import ( Result, generate_result, process_inputs, ) +from langflow.services.deps import get_session_service from langflow.services.manager import initialize_session_service -from langflow.services.getters import get_session_service if TYPE_CHECKING: from langflow.graph.vertex.base import Vertex @@ -30,9 +30,7 @@ def build_vertex(self, vertex: "Vertex") -> "Vertex": vertex.build() return vertex except SoftTimeLimitExceeded as e: - raise self.retry( - exc=SoftTimeLimitExceeded("Task took too long"), countdown=2 - ) from e + raise self.retry(exc=SoftTimeLimitExceeded("Task took too long"), countdown=2) from e @celery_app.task(acks_late=True) @@ -47,9 +45,7 @@ def process_graph_cached_task( if clear_cache: session_service.clear_session(session_id) if session_id is None: - session_id = session_service.generate_key( - session_id=session_id, data_graph=data_graph - ) + session_id = session_service.generate_key(session_id=session_id, data_graph=data_graph) # Load the graph using SessionService graph, artifacts = session_service.load_session(session_id, data_graph) built_object = graph.build() @@ -59,4 +55,4 @@ def process_graph_cached_task( # we need to update the cache with the updated langchain_object session_service.update_session(session_id, (graph, artifacts)) - return Result(result=result, session_id=session_id).dict() + return Result(result=result, session_id=session_id).model_dump() diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 0bf66bdca..3fe0befd7 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -89,7 +89,7 @@ "@types/uuid": "^9.0.2", "@vitejs/plugin-react-swc": "^3.3.2", "autoprefixer": "^10.4.15", - "daisyui": "^3.6.3", + "daisyui": "^4.0.4", "postcss": "^8.4.29", "prettier": "^2.8.8", "prettier-plugin-organize-imports": "^3.2.3", @@ -129,9 +129,9 @@ } }, "node_modules/@antfu/ni": { - "version": "0.21.8", - "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.8.tgz", - "integrity": "sha512-90X8pU2szlvw0AJo9EZMbYc2eQKkmO7mAdC4tD4r5co2Mm56MT37MIG8EyB7p4WRheuzGxuLDxJ63mF6+Zajiw==", + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.9.tgz", + "integrity": "sha512-zlwQy574YEYl9ssWMV98ADxobU5wePdtyaOeQ5jgzdV8WldPcK+Osqd1SeQwEWjN0Io0GKiqpQzKZXVgxU1jPg==", "bin": { "na": "bin/na.mjs", "nci": "bin/nci.mjs", @@ -219,28 +219,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", + "@babel/parser": "^7.23.3", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -269,11 +269,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "dependencies": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -361,9 +361,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -515,9 +515,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -550,18 +550,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -570,9 +570,9 @@ } }, "node_modules/@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", @@ -1063,9 +1063,9 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", "dependencies": { "@floating-ui/dom": "^1.5.1" }, @@ -1165,14 +1165,14 @@ } }, "node_modules/@mui/base": { - "version": "5.0.0-beta.21", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.21.tgz", - "integrity": "sha512-eTKWx3WV/nwmRUK4z4K1MzlMyWCsi3WJ3RtV4DiXZeRh4qd4JCyp1Zzzi8Wv9xM4dEBmqQntFoei716PzwmFfA==", + "version": "5.0.0-beta.24", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.24.tgz", + "integrity": "sha512-bKt2pUADHGQtqWDZ8nvL2Lvg2GNJyd/ZUgZAJoYzRgmnxBL9j36MSlS3+exEdYkikcnvVafcBtD904RypFKb0w==", "dependencies": { "@babel/runtime": "^7.23.2", - "@floating-ui/react-dom": "^2.0.2", - "@mui/types": "^7.2.7", - "@mui/utils": "^5.14.15", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.9", + "@mui/utils": "^5.14.18", "@popperjs/core": "^2.11.8", "clsx": "^2.0.0", "prop-types": "^15.8.1" @@ -1204,26 +1204,26 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.15.tgz", - "integrity": "sha512-ZCDzBWtCKjAYAlKKM3PA/jG/3uVIDT9ZitOtVixIVmTCQyc5jSV1qhJX8+qIGz4RQZ9KLzPWO2tXd0O5hvzouQ==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.18.tgz", + "integrity": "sha512-yFpF35fEVDV81nVktu0BE9qn2dD/chs7PsQhlyaV3EnTeZi9RZBuvoEfRym1/jmhJ2tcfeWXiRuHG942mQXJJQ==", "funding": { "type": "opencollective", "url": "https://opencollective.com/mui" } }, "node_modules/@mui/material": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.15.tgz", - "integrity": "sha512-Gq65rHjvLzkxmhG8bvag851Oqsmru7qkUb/cCI2xu7dQzmY345f9xJRJi72sRGjhaqHXWeRKw/yIwp/7oQoeXg==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.18.tgz", + "integrity": "sha512-y3UiR/JqrkF5xZR0sIKj6y7xwuEiweh9peiN3Zfjy1gXWXhz5wjlaLdoxFfKIEBUFfeQALxr/Y8avlHH+B9lpQ==", "dependencies": { "@babel/runtime": "^7.23.2", - "@mui/base": "5.0.0-beta.21", - "@mui/core-downloads-tracker": "^5.14.15", - "@mui/system": "^5.14.15", - "@mui/types": "^7.2.7", - "@mui/utils": "^5.14.15", - "@types/react-transition-group": "^4.4.7", + "@mui/base": "5.0.0-beta.24", + "@mui/core-downloads-tracker": "^5.14.18", + "@mui/system": "^5.14.18", + "@mui/types": "^7.2.9", + "@mui/utils": "^5.14.18", + "@types/react-transition-group": "^4.4.8", "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1", @@ -1265,12 +1265,12 @@ } }, "node_modules/@mui/private-theming": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.15.tgz", - "integrity": "sha512-V2Xh+Tu6A07NoSpup0P9m29GwvNMYl5DegsGWqlOTJyAV7cuuVjmVPqxgvL8xBng4R85xqIQJRMjtYYktoPNuQ==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.18.tgz", + "integrity": "sha512-WSgjqRlzfHU+2Rou3HlR2Gqfr4rZRsvFgataYO3qQ0/m6gShJN+lhVEvwEiJ9QYyVzMDvNpXZAcqp8Y2Vl+PAw==", "dependencies": { "@babel/runtime": "^7.23.2", - "@mui/utils": "^5.14.15", + "@mui/utils": "^5.14.18", "prop-types": "^15.8.1" }, "engines": { @@ -1291,9 +1291,9 @@ } }, "node_modules/@mui/styled-engine": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.15.tgz", - "integrity": "sha512-mbOjRf867BysNpexe5Z/P8s3bWzDPNowmKhi7gtNDP/LPEeqAfiDSuC4WPTXmtvse1dCl30Nl755OLUYuoi7Mw==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.18.tgz", + "integrity": "sha512-pW8bpmF9uCB5FV2IPk6mfbQCjPI5vGI09NOLhtGXPeph/4xIfC3JdIX0TILU0WcTs3aFQqo6s2+1SFgIB9rCXA==", "dependencies": { "@babel/runtime": "^7.23.2", "@emotion/cache": "^11.11.0", @@ -1322,15 +1322,15 @@ } }, "node_modules/@mui/system": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.15.tgz", - "integrity": "sha512-zr0Gdk1RgKiEk+tCMB900LaOpEC8NaGvxtkmMdL/CXgkqQZSVZOt2PQsxJWaw7kE4YVkIe4VukFVc43qcq9u3w==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.18.tgz", + "integrity": "sha512-hSQQdb3KF72X4EN2hMEiv8EYJZSflfdd1TRaGPoR7CIAG347OxCslpBUwWngYobaxgKvq6xTrlIl+diaactVww==", "dependencies": { "@babel/runtime": "^7.23.2", - "@mui/private-theming": "^5.14.15", - "@mui/styled-engine": "^5.14.15", - "@mui/types": "^7.2.7", - "@mui/utils": "^5.14.15", + "@mui/private-theming": "^5.14.18", + "@mui/styled-engine": "^5.14.18", + "@mui/types": "^7.2.9", + "@mui/utils": "^5.14.18", "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -1369,9 +1369,9 @@ } }, "node_modules/@mui/types": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.7.tgz", - "integrity": "sha512-sofpWmcBqOlTzRbr1cLQuUDKaUYVZTw8ENQrtL39TECRNENEzwgnNPh6WMfqMZlMvf1Aj9DLg74XPjnLr0izUQ==", + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.9.tgz", + "integrity": "sha512-k1lN/PolaRZfNsRdAqXtcR71sTnv3z/VCCGPxU8HfdftDkzi335MdJ6scZxvofMAd/K/9EbzCZTFBmlNpQVdCg==", "peerDependencies": { "@types/react": "^17.0.0 || ^18.0.0" }, @@ -1382,12 +1382,12 @@ } }, "node_modules/@mui/utils": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.15.tgz", - "integrity": "sha512-QBfHovAvTa0J1jXuYDaXGk+Yyp7+Fm8GSqx6nK2JbezGqzCFfirNdop/+bL9Flh/OQ/64PeXcW4HGDdOge+n3A==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.18.tgz", + "integrity": "sha512-HZDRsJtEZ7WMSnrHV9uwScGze4wM/Y+u6pDVo+grUjt5yXzn+wI8QX/JwTHh9YSw/WpnUL80mJJjgCnWj2VrzQ==", "dependencies": { "@babel/runtime": "^7.23.2", - "@types/prop-types": "^15.7.8", + "@types/prop-types": "^15.7.10", "prop-types": "^15.8.1", "react-is": "^18.2.0" }, @@ -2587,11 +2587,11 @@ } }, "node_modules/@reactflow/background": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.4.tgz", - "integrity": "sha512-bgwvqWxF09chwmdkyClpYEMaewBspdwjgLbbFlLf4SpWPFMYyuvCBQrcISsvy/EDEWO9i3Uj9ktgGAhvtSQsmA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.6.tgz", + "integrity": "sha512-06FPlSUOOMALEEs+2PqPAbpqmL7WDjrkbG2UsDr2d6mbcDDhHiV4tu9FYoz44SQvXo7ma9VRotlsaR4OiRcYsg==", "dependencies": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -2601,11 +2601,11 @@ } }, "node_modules/@reactflow/controls": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.4.tgz", - "integrity": "sha512-x6e5p9iHjC6gd+4SoZ3DOOp0F1MefGKQ8hT6yPVdqxfo1+rV2WhrWvrX/MCoEu12Dp7457LdLfa0giy3aho8tQ==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.6.tgz", + "integrity": "sha512-4QHT92/ACVlZkvV+Hq44bAPV8WbMhkJl+/J0EbXcqQ1+an7cWJsF84eeelJw7R5J76RoaSSpKdsWsL2v7HAVlw==", "dependencies": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -2615,9 +2615,9 @@ } }, "node_modules/@reactflow/core": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.9.4.tgz", - "integrity": "sha512-Ko7nKPOYalwDTTbRHi2+QXDiidSAcpUzGN3G+0B+QysLZkcaPCkpkMjjHiDC4c/Z1BJBzs1FRJg/T6BXaBnYkg==", + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.10.1.tgz", + "integrity": "sha512-GIh3usY1W3eVobx//OO9+Cwm+5evQBBdPGxDaeXwm25UqPMWRI240nXQA5F/5gL5Mwpf0DUC7DR2EmrKNQy+Rw==", "dependencies": { "@types/d3": "^7.4.0", "@types/d3-drag": "^3.0.1", @@ -2635,11 +2635,11 @@ } }, "node_modules/@reactflow/minimap": { - "version": "11.7.4", - "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.4.tgz", - "integrity": "sha512-Jo1R+uDey9IV7O2s3m0gK2+cZpg9M8hq2EZJb3NGfOSzMAPhj3mby0fNJIgTzycreuht0TpA51c2YfjGI3YIOw==", + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.6.tgz", + "integrity": "sha512-kJEtyeQkTZYViLGebVWHVUJROMAGcvejvT+iX4DqKnFb5yK8E8LWlXQpRx2FrL9gDy80mJJaciy7IxnnQKE1bg==", "dependencies": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "@types/d3-selection": "^3.0.3", "@types/d3-zoom": "^3.0.1", "classcat": "^5.0.3", @@ -2653,11 +2653,11 @@ } }, "node_modules/@reactflow/node-resizer": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.4.tgz", - "integrity": "sha512-+p271/hAsM5M1+RQTWW/02pbNkCHeGXwxGimIlL1tMIagyuko0NX2vOz2B8jxJnPKlF09Wj18BcXBNUm3nDcSg==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.6.tgz", + "integrity": "sha512-1Xb6q97uP7hRBLpog9sRCNfnsHdDgFRGEiU+lQqGgPEAeYwl4nRjWa/sXwH6ajniKxBhGEvrdzOgEFn6CRMcpQ==", "dependencies": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.4", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", @@ -2669,11 +2669,11 @@ } }, "node_modules/@reactflow/node-toolbar": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.4.tgz", - "integrity": "sha512-TfcmpXHRBb2mUfzKGjburiU6FWqRME9pPFs1OwIC1z5e9BjupQhNDEKEk8XHi7PKL/mAiDfwuGXaM1BVVFuPqw==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.6.tgz", + "integrity": "sha512-JXDEuZ0wKjZ8z7qK2bIst0eZPzNyVEsiHL0e93EyuqT4fA9icoyE0fLq2ryNOOp7MXgId1h7LusnH6ta45F0yQ==", "dependencies": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.3", "zustand": "^4.4.1" }, @@ -2683,9 +2683,9 @@ } }, "node_modules/@remix-run/router": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", - "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.12.0.tgz", + "integrity": "sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==", "engines": { "node": ">=14.0.0" } @@ -2961,9 +2961,9 @@ } }, "node_modules/@swc/cli": { - "version": "0.1.62", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.1.62.tgz", - "integrity": "sha512-kOFLjKY3XH1DWLfXL1/B5MizeNorHR8wHKEi92S/Zi9Md/AK17KSqR8MgyRJ6C1fhKHvbBCl8wboyKAFXStkYw==", + "version": "0.1.63", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.1.63.tgz", + "integrity": "sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==", "dev": true, "dependencies": { "@mole-inc/bin-wrapper": "^8.0.1", @@ -3001,9 +3001,9 @@ } }, "node_modules/@swc/core": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.95.tgz", - "integrity": "sha512-PMrNeuqIusq9DPDooV3FfNEbZuTu5jKAc04N3Hm6Uk2Fl49cqElLFQ4xvl4qDmVDz97n3n/C1RE0/f6WyGPEiA==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.96.tgz", + "integrity": "sha512-zwE3TLgoZwJfQygdv2SdCK9mRLYluwDOM53I+dT6Z5ZvrgVENmY3txvWDvduzkV+/8IuvrRbVezMpxcojadRdQ==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -3018,16 +3018,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.3.95", - "@swc/core-darwin-x64": "1.3.95", - "@swc/core-linux-arm-gnueabihf": "1.3.95", - "@swc/core-linux-arm64-gnu": "1.3.95", - "@swc/core-linux-arm64-musl": "1.3.95", - "@swc/core-linux-x64-gnu": "1.3.95", - "@swc/core-linux-x64-musl": "1.3.95", - "@swc/core-win32-arm64-msvc": "1.3.95", - "@swc/core-win32-ia32-msvc": "1.3.95", - "@swc/core-win32-x64-msvc": "1.3.95" + "@swc/core-darwin-arm64": "1.3.96", + "@swc/core-darwin-x64": "1.3.96", + "@swc/core-linux-arm-gnueabihf": "1.3.96", + "@swc/core-linux-arm64-gnu": "1.3.96", + "@swc/core-linux-arm64-musl": "1.3.96", + "@swc/core-linux-x64-gnu": "1.3.96", + "@swc/core-linux-x64-musl": "1.3.96", + "@swc/core-win32-arm64-msvc": "1.3.96", + "@swc/core-win32-ia32-msvc": "1.3.96", + "@swc/core-win32-x64-msvc": "1.3.96" }, "peerDependencies": { "@swc/helpers": "^0.5.0" @@ -3039,9 +3039,9 @@ } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.95.tgz", - "integrity": "sha512-VAuBAP3MNetO/yBIBzvorUXq7lUBwhfpJxYViSxyluMwtoQDhE/XWN598TWMwMl1ZuImb56d7eUsuFdjgY7pJw==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.96.tgz", + "integrity": "sha512-8hzgXYVd85hfPh6mJ9yrG26rhgzCmcLO0h1TIl8U31hwmTbfZLzRitFQ/kqMJNbIBCwmNH1RU2QcJnL3d7f69A==", "cpu": [ "arm64" ], @@ -3055,9 +3055,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.95.tgz", - "integrity": "sha512-20vF2rvUsN98zGLZc+dsEdHvLoCuiYq/1B+TDeE4oolgTFDmI1jKO+m44PzWjYtKGU9QR95sZ6r/uec0QC5O4Q==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.96.tgz", + "integrity": "sha512-mFp9GFfuPg+43vlAdQZl0WZpZSE8sEzqL7sr/7Reul5McUHP0BaLsEzwjvD035ESfkY8GBZdLpMinblIbFNljQ==", "cpu": [ "x64" ], @@ -3071,9 +3071,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.95.tgz", - "integrity": "sha512-oEudEM8PST1MRNGs+zu0cx5i9uP8TsLE4/L9HHrS07Ck0RJ3DCj3O2fU832nmLe2QxnAGPwBpSO9FntLfOiWEQ==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.96.tgz", + "integrity": "sha512-8UEKkYJP4c8YzYIY/LlbSo8z5Obj4hqcv/fUTHiEePiGsOddgGf7AWjh56u7IoN/0uEmEro59nc1ChFXqXSGyg==", "cpu": [ "arm" ], @@ -3087,9 +3087,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.95.tgz", - "integrity": "sha512-pIhFI+cuC1aYg+0NAPxwT/VRb32f2ia8oGxUjQR6aJg65gLkUYQzdwuUmpMtFR2WVf7WVFYxUnjo4UyMuyh3ng==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.96.tgz", + "integrity": "sha512-c/IiJ0s1y3Ymm2BTpyC/xr6gOvoqAVETrivVXHq68xgNms95luSpbYQ28rqaZC8bQC8M5zdXpSc0T8DJu8RJGw==", "cpu": [ "arm64" ], @@ -3103,9 +3103,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.95.tgz", - "integrity": "sha512-ZpbTr+QZDT4OPJfjPAmScqdKKaT+wGurvMU5AhxLaf85DuL8HwUwwlL0n1oLieLc47DwIJEMuKQkYhXMqmJHlg==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.96.tgz", + "integrity": "sha512-i5/UTUwmJLri7zhtF6SAo/4QDQJDH2fhYJaBIUhrICmIkRO/ltURmpejqxsM/ye9Jqv5zG7VszMC0v/GYn/7BQ==", "cpu": [ "arm64" ], @@ -3119,9 +3119,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.95.tgz", - "integrity": "sha512-n9SuHEFtdfSJ+sHdNXNRuIOVprB8nbsz+08apKfdo4lEKq6IIPBBAk5kVhPhkjmg2dFVHVo4Tr/OHXM1tzWCCw==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.96.tgz", + "integrity": "sha512-USdaZu8lTIkm4Yf9cogct/j5eqtdZqTgcTib4I+NloUW0E/hySou3eSyp3V2UAA1qyuC72ld1otXuyKBna0YKQ==", "cpu": [ "x64" ], @@ -3135,9 +3135,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.95.tgz", - "integrity": "sha512-L1JrVlsXU3LC0WwmVnMK9HrOT2uhHahAoPNMJnZQpc18a0paO9fqifPG8M/HjNRffMUXR199G/phJsf326UvVg==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.96.tgz", + "integrity": "sha512-QYErutd+G2SNaCinUVobfL7jWWjGTI0QEoQ6hqTp7PxCJS/dmKmj3C5ZkvxRYcq7XcZt7ovrYCTwPTHzt6lZBg==", "cpu": [ "x64" ], @@ -3151,9 +3151,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.95.tgz", - "integrity": "sha512-YaP4x/aZbUyNdqCBpC2zL8b8n58MEpOUpmOIZK6G1SxGi+2ENht7gs7+iXpWPc0sy7X3YPKmSWMAuui0h8lgAA==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.96.tgz", + "integrity": "sha512-hjGvvAduA3Un2cZ9iNP4xvTXOO4jL3G9iakhFsgVhpkU73SGmK7+LN8ZVBEu4oq2SUcHO6caWvnZ881cxGuSpg==", "cpu": [ "arm64" ], @@ -3167,9 +3167,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.95.tgz", - "integrity": "sha512-w0u3HI916zT4BC/57gOd+AwAEjXeUlQbGJ9H4p/gzs1zkSHtoDQghVUNy3n/ZKp9KFod/95cA8mbVF9t1+6epQ==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.96.tgz", + "integrity": "sha512-Far2hVFiwr+7VPCM2GxSmbh3ikTpM3pDombE+d69hkedvYHYZxtTF+2LTKl/sXtpbUnsoq7yV/32c9R/xaaWfw==", "cpu": [ "ia32" ], @@ -3183,9 +3183,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.95.tgz", - "integrity": "sha512-5RGnMt0S6gg4Gc6QtPUJ3Qs9Un4sKqccEzgH/tj7V/DVTJwKdnBKxFZfgQ34OR2Zpz7zGOn889xwsFVXspVWNA==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.96.tgz", + "integrity": "sha512-4VbSAniIu0ikLf5mBX81FsljnfqjoVGleEkCQv4+zRlyZtO3FHoDPkeLVoy6WRlj7tyrRcfUJ4mDdPkbfTO14g==", "cpu": [ "x64" ], @@ -3223,20 +3223,20 @@ } }, "node_modules/@tabler/icons": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.39.0.tgz", - "integrity": "sha512-iK3j2jIEGIUaJcbYYg5iwyG1Y/m4lzUxAUbxRpvgeXCWP29jvZaH5hajZmU3KaSealddHuJg7PSQislPHpCsoQ==", + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.40.0.tgz", + "integrity": "sha512-VqKsBSX159cLFTnCzkCmGhZtSPJHNN0lM2sC4xe0HPOfPUnjiex7rDHDdut4oe4iKRecDDpwXwM9BcU6xCPlCg==", "funding": { "type": "github", "url": "https://github.com/sponsors/codecalm" } }, "node_modules/@tabler/icons-react": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.39.0.tgz", - "integrity": "sha512-MyUK1jqtmHPZBnDXqIc1Y5OnfoqG+tGaSB1/gcl0mlY462fJ5f3QB0ZIZzAHMAGYb6K2iJSdFIFavhcgpDDZ7Q==", + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.40.0.tgz", + "integrity": "sha512-C+dDOZowFbwI3LGQP0fdua+hOPkGkW7XeMcRXTSdEKc5fD75W6zRO5nXnWivIMRKsi/Y26EDmnQo15N8JX378w==", "dependencies": { - "@tabler/icons": "2.39.0", + "@tabler/icons": "2.40.0", "prop-types": "^15.7.2" }, "funding": { @@ -3248,9 +3248,9 @@ } }, "node_modules/@tailwindcss/forms": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.6.tgz", - "integrity": "sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", + "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", "dependencies": { "mini-svg-data-uri": "^1.2.3" }, @@ -3437,9 +3437,9 @@ } }, "node_modules/@types/aria-query": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.3.tgz", - "integrity": "sha512-0Z6Tr7wjKJIk4OUEjVUQMtyunLDy339vcMaj38Kpj6jM2OE1p3S4kXExKZ7a3uXQAPCoy3sbrP1wibDKaf39oA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true }, "node_modules/@types/axios": { @@ -3469,9 +3469,9 @@ "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" }, "node_modules/@types/d3": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.2.tgz", - "integrity": "sha512-Y4g2Yb30ZJmmtqAJTqMRaqXwRawfvpdpVmyEYEcyGNhrQI/Zvkq3k7yE1tdN07aFSmNBfvmegMQ9Fe2qy9ZMhw==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", @@ -3506,229 +3506,229 @@ } }, "node_modules/@types/d3-array": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.9.tgz", - "integrity": "sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" }, "node_modules/@types/d3-axis": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.5.tgz", - "integrity": "sha512-ufDAV3SQzju+uB3Jlty7SUb/jMigjpIlvDDcSGvGmmO6OT/sNO93UE0dRzwWOZeBLzrLSA0CQM4bf3iq1std3A==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-brush": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.5.tgz", - "integrity": "sha512-JROQXZNq1X6QdWstESDUv1VilwZ2hBCQnWB91yal+5yZvYwGQvYsGCjrkHGfKK/8/AcX1JnERmpQzdDDuLRUsA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-chord": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.5.tgz", - "integrity": "sha512-rs26AIhJjtc+XLR4YQU8IjPTLOlDVO4PR1y+pVFYEHzKh2tE5tYz3MF4QV6iz7HboXQEaYpJQt8dH9uUkne8yA==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" }, "node_modules/@types/d3-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.2.tgz", - "integrity": "sha512-At+Ski7dL8Bs58E8g8vPcFJc8tGcaC12Z4m07+p41+DRqnZQcAlp3NfYjLrhNYv+zEyQitU1CUxXNjqUyf+c0g==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" }, "node_modules/@types/d3-contour": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.5.tgz", - "integrity": "sha512-wLvjwdOQVd1NL1IcW90CCt1VtpeZ3V20p/OTXlkT8uAiprrJnq2PNNnRNe1QCez4U9aMU29Z14zpJQVLW1+Lcg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "node_modules/@types/d3-delaunay": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.3.tgz", - "integrity": "sha512-+Lf5NPKZ4JBC9tbudVkKceQXRxU3jJs0el9aKQvinMtdnFSOG84eVXyhCNgIFuXNQO3iIcYs7sgzN359FEOZnQ==" + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" }, "node_modules/@types/d3-dispatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.5.tgz", - "integrity": "sha512-hxvq2kc+9hydVppo21JCGfcM0tLTh1DXnG3MLN0KlxsNZJH4bsdl1iXDuWtXFpWWlBrCMwSqlnoLPDxNAZU3Bg==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" }, "node_modules/@types/d3-drag": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.5.tgz", - "integrity": "sha512-arHyAGvO0NEGGPCU2jTb31TlXeSxwty1bIxr5wOFOCVqVjgriXloLWXoRp39Oa0Y/qXxcAVMIonAWLrtLxUZAQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-dsv": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.5.tgz", - "integrity": "sha512-73WZR3QFOaSRVz9iOrebTbTnbo7xjcgS/i0Cq5zy0jMXPO3v/JbkTD3Zqii1eYE6v4EJ78g5VP407rm+p8fdlA==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" }, "node_modules/@types/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-VZofjpEt8HWv3nxUAosj5o/+4JflnJ7Bbv07k17VO3T2WRuzGdZeookfaF60iVh5RdhVG49LE5w6LIshVUC6rg==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" }, "node_modules/@types/d3-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.5.tgz", - "integrity": "sha512-Rc8pb6H0RRLpAV2hEXduykUgcDUOhjSLTLmCIeo6ejzgs4SaITh/EteMb3p5Env3Hqjsqw0fCksyqopHHzMkMg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "dependencies": { "@types/d3-dsv": "*" } }, "node_modules/@types/d3-force": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.7.tgz", - "integrity": "sha512-rsok4CEvPLyVWRPsFiBhanJc3up03H/EARVz4d8soPh8drv82YMuAckYy4yv8g4/81JwCng5U5/o9aj9d0T6bQ==" + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==" }, "node_modules/@types/d3-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.3.tgz", - "integrity": "sha512-kxuLXSAEJykTeL/EI3tUiEfGqru7PRdqEy099YBnqFl+fF167UVSB4+wntlZv86ZdoYf0DHjsRHnTIm8kcH7qw==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" }, "node_modules/@types/d3-geo": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.6.tgz", - "integrity": "sha512-wblAES3b+C3hvp4VakwECEKtHquT/xc6K4HOna95LM1j1fd7s7WmU4V+JMQZfKhNCMkV2vWD+ZUgY2Uj6gqfuA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "dependencies": { "@types/geojson": "*" } }, "node_modules/@types/d3-hierarchy": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.5.tgz", - "integrity": "sha512-DEcBUj1IL3WyPLDlh4m2nsNXnMLITXM5Vwcu4G85yJHtf2cVGPBjgky3L11WBnT+ayHKf06Tchk5mY1eGmd4WQ==" + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", + "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==" }, "node_modules/@types/d3-interpolate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.3.tgz", - "integrity": "sha512-6OZ2EIB4lLj+8cUY7I/Cgn9Q+hLdA4DjJHYOQDiHL0SzqS1K9DL5xIOVBSIHgF+tiuO9MU1D36qvdIvRDRPh+Q==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "dependencies": { "@types/d3-color": "*" } }, "node_modules/@types/d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-blRhp7ki7pVznM8k6lk5iUU9paDbVRVq+/xpf0RRgSJn5gr6SE7RcFtxooYGMBOc1RZiGyqRpVdu5AD0z0ooMA==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", + "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==" }, "node_modules/@types/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-nrcWPk7B9qs6xnpq60Cls44zm9eDmFAv65qi/N/emh/oftnG6uYz49aIS0mdFaGeJxVN8H3pHneMuZMV8EwFdw==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" }, "node_modules/@types/d3-quadtree": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.4.tgz", - "integrity": "sha512-B725MopFDIOQ6njFbeOxIEf42HVO2Xv+FmcxQISdOKErvLbFqWz3Riu+OWujUYoogreqqyHBHcGGL/JzzXQYsw==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.5.tgz", + "integrity": "sha512-Cb1f3jyNBnvMMkf4KBZ7IgAQVWd9yzBwYcrxGqg3aPCUgWELAS+nyeB7r76aqu1e3+CGDjhk4BrWaFBekMwigg==" }, "node_modules/@types/d3-random": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.2.tgz", - "integrity": "sha512-8QhsqkKs6mymAZMrg3ZFXPxKA34rdgp3ZrtB8o6mhFsKAd1gOvR1gocWnca+kmXypQdwgnzKm9gZE2Uw8NjjKw==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" }, "node_modules/@types/d3-scale": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.6.tgz", - "integrity": "sha512-lo3oMLSiqsQUovv8j15X4BNEDOsnHuGjeVg7GRbAuB2PUa1prK5BNSOu6xixgNf3nqxPl4I1BqJWrPvFGlQoGQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", "dependencies": { "@types/d3-time": "*" } }, "node_modules/@types/d3-scale-chromatic": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.1.tgz", - "integrity": "sha512-Ob7OrwiTeQXY/WBBbRHGZBOn6rH1h7y3jjpTSKYqDEeqFjktql6k2XSgNwLrLDmAsXhEn8P9NHDY4VTuo0ZY1w==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.2.tgz", + "integrity": "sha512-kpKNZMDT3OAX6b5ct5nS/mv6LULagnUy4DmS6yyNjclje1qVe7vbjPwY3q1TGz6+Wr2IUkgFatCzqYUl54fHag==" }, "node_modules/@types/d3-selection": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.8.tgz", - "integrity": "sha512-pxCZUfQyedq/DIlPXIR5wE1mIH37omOdx1yxRudL3KZ4AC+156jMjOv1z5RVlGq62f8WX2kyO0hTVgEx627QFg==" + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==" }, "node_modules/@types/d3-shape": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.4.tgz", - "integrity": "sha512-M2/xsWPsjaZc5ifMKp1EBp0gqJG0eO/zlldJNOC85Y/5DGsBQ49gDkRJ2h5GY7ZVD6KUumvZWsylSbvTaJTqKg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.5.tgz", + "integrity": "sha512-dfEWpZJ1Pdg8meLlICX1M3WBIpxnaH2eQV2eY43Y5ysRJOTAV9f3/R++lgJKFstfrEOE2zdJ0sv5qwr2Bkic6Q==", "dependencies": { "@types/d3-path": "*" } }, "node_modules/@types/d3-time": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.2.tgz", - "integrity": "sha512-kbdRXTmUgNfw5OTE3KZnFQn6XdIc4QGroN5UixgdrXATmYsdlPQS6pEut9tVlIojtzuFD4txs/L+Rq41AHtLpg==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" }, "node_modules/@types/d3-time-format": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.2.tgz", - "integrity": "sha512-wr08C1Gh77qaN8JIkrn5Rz/bdt5M9bdEqFmEOcYhUSq2t2sHvLTBfb4XAtGB3D4hm0ubj50NXWWXoXyp5tPXDg==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" }, "node_modules/@types/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-GGTvzKccVEhxmRfJEB6zhY9ieT4UhGVUIQaBzFpUO9OXy2ycAlnPCSJLzmGGgqt3KVjqN3QCQB4g1rsZnHsWhg==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" }, "node_modules/@types/d3-transition": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.6.tgz", - "integrity": "sha512-K0To23B5UxNwFtKORnS5JoNYvw/DnknU5MzhHIS9czJ/lTqFFDeU6w9lArOdoTl0cZFNdNrMJSFCbRCEHccH2w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", "dependencies": { "@types/d3-selection": "*" } }, "node_modules/@types/d3-zoom": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.6.tgz", - "integrity": "sha512-dGZQaXEu7aNcCL71LPpjB58IjoQNM9oDPfQuMUJ7N/fbkcIWGX2PnmUWO1jPJ+RLbZBpRUggJUX8twKRvo2hKQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "node_modules/@types/debug": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.10.tgz", - "integrity": "sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/estree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/geojson": { - "version": "7946.0.12", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.12.tgz", - "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA==" + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" }, "node_modules/@types/hast": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.7.tgz", - "integrity": "sha512-EVLigw5zInURhzfXUM65eixfadfsHKomGKUakToXo84t8gGIJuTcD2xooM2See7GyQ7DRtYjhCHnSUQez8JaLw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.8.tgz", + "integrity": "sha512-aMIqAlFd2wTIDZuvLbhUT+TGvMxrNC8ECUIVtH6xxy0sQLs3iu6NO8Kp/VT5je7i5ufnebXzdV1dNDMnvaH6IQ==", "dependencies": { "@types/unist": "^2" } }, "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.4.tgz", - "integrity": "sha512-ZchYkbieA+7tnxwX/SCBySx9WwvWR8TaP5tb2jRAzwvLb/rWchGw3v0w3pqUbUvj0GCwW2Xz/AVPSk6kUGctXQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", "dependencies": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" } }, "node_modules/@types/http-cache-semantics": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "node_modules/@types/jest": { @@ -3742,9 +3742,9 @@ } }, "node_modules/@types/katex": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.5.tgz", - "integrity": "sha512-DD2Y3xMlTQvAnN6d8803xdgnOeYZ+HwMglb7/9YCf49J9RkJL53azf9qKa40MkEYhqVwxZ1GS2+VlShnz4Z1Bw==" + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.6.tgz", + "integrity": "sha512-rZYO1HInM99rAFYNwGqbYPxHZHxu2IwZYKj4bJ4oh6edVrm1UId8mmbHIZLBtG253qU6y3piag0XYe/joNnwzQ==" }, "node_modules/@types/keyv": { "version": "3.1.4", @@ -3756,9 +3756,9 @@ } }, "node_modules/@types/lodash": { - "version": "4.14.200", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", - "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==", + "version": "4.14.201", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz", + "integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==", "dev": true }, "node_modules/@types/mathjax": { @@ -3767,38 +3767,38 @@ "integrity": "sha512-y0WSZBtBNQwcYipTU/BhgeFu1EZNlFvUNCmkMXV9kBQZq7/o5z82dNVyH3yy2Xv5zzeNeQoHSL4Xm06+EQiH+g==" }, "node_modules/@types/mdast": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", - "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", "dependencies": { "@types/unist": "^2" } }, "node_modules/@types/ms": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz", - "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==" + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "16.18.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.59.tgz", - "integrity": "sha512-PJ1w2cNeKUEdey4LiPra0ZuxZFOGvetswE8qHRriV/sUkL5Al4tTmPV9D2+Y/TPIxTHHgxTfRjZVKWhPw/ORhQ==", + "version": "16.18.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.61.tgz", + "integrity": "sha512-k0N7BqGhJoJzdh6MuQg1V1ragJiXTh8VUBAZTWjJ9cUq23SG0F0xavOwZbhiP4J3y20xd6jxKx+xNUhkMAi76Q==", "devOptional": true }, "node_modules/@types/parse-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz", - "integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "node_modules/@types/prop-types": { - "version": "15.7.9", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", - "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" + "version": "15.7.10", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz", + "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==" }, "node_modules/@types/react": { - "version": "18.2.32", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.32.tgz", - "integrity": "sha512-F0FVIZQ1x5Gxy/VYJb7XcWvCcHR28Sjwt1dXLspdIatfPq1MVACfnBDwKe6ANLxQ64riIJooXClpUR6oxTiepg==", + "version": "18.2.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz", + "integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==", "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -3806,35 +3806,35 @@ } }, "node_modules/@types/react-dom": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", - "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", + "version": "18.2.15", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz", + "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==", "devOptional": true, "dependencies": { "@types/react": "*" } }, "node_modules/@types/react-transition-group": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz", - "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.9.tgz", + "integrity": "sha512-ZVNmWumUIh5NhH8aMD9CR2hdW0fNuYInlocZHaZ+dgk/1K49j1w/HoAuK1ki+pgscQrOFRTlXeoURtuzEkV3dg==", "dependencies": { "@types/react": "*" } }, "node_modules/@types/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/scheduler": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", - "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==" + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", + "integrity": "sha512-Vlktnchmkylvc9SnwwwozTv04L/e1NykF5vgoQ0XTmI8DD+wxfjQuHuvHS3p0r2jz2x2ghPs2h1FVeDirIteWA==" }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", @@ -3846,26 +3846,26 @@ } }, "node_modules/@types/unist": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.9.tgz", - "integrity": "sha512-zC0iXxAv1C1ERURduJueYzkzZ2zaGyc+P2c95hgkikHPr3z8EdUZOlgEQ5X0DRmwDZn+hekycQnoeiiRVrmilQ==" + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, "node_modules/@types/uuid": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", - "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", "dev": true }, "node_modules/@vitejs/plugin-react-swc": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.4.0.tgz", - "integrity": "sha512-m7UaA4Uvz82N/0EOVpZL4XsFIakRqrFKeSNxa1FBLSXGvWrWRBwmZb4qxk+ZIVAZcW3c3dn5YosomDgx62XWcQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", + "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", "dev": true, "dependencies": { - "@swc/core": "^1.3.85" + "@swc/core": "^1.3.96" }, "peerDependencies": { - "vite": "^4" + "vite": "^4 || ^5" } }, "node_modules/abab": { @@ -3879,14 +3879,14 @@ "integrity": "sha512-jbQfFaw+57OBwPt7qSNHuW+RA8smmRwkWRS1Ozh6K/QxUspBgBV/LpdSzlY7vee8TomS6j3D33B9rIeH1qMwsA==" }, "node_modules/ace-builds": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.31.0.tgz", - "integrity": "sha512-nitIhcUYA6wyO3lo2WZBPX5fcjllW6XFt4EFyHwcN2Fp70/IZwz8tdw6a0+8udDEwDj/ebt3aWEClIyCs/6qYA==" + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.31.2.tgz", + "integrity": "sha512-IeZI9ytPA6mB+goPxPkUPW4vXBoLuaBl5czu2tjtKrMi7mdRgyIUA/8e5JlrI1mqKoMeWHoUujzMTWkyutTdBw==" }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "bin": { "acorn": "bin/acorn" }, @@ -3904,9 +3904,9 @@ } }, "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", "engines": { "node": ">=0.4.0" } @@ -4121,9 +4121,9 @@ } }, "node_modules/axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -4517,9 +4517,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001554", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz", - "integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==", + "version": "1.0.30001562", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", + "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==", "funding": [ { "type": "opencollective", @@ -4713,12 +4713,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -4857,6 +4851,15 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "node_modules/culori": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/culori/-/culori-3.2.0.tgz", + "integrity": "sha512-HIEbTSP7vs1mPq/2P9In6QyFE0Tkpevh0k9a+FkjhD+cwsYm9WRSbn4uMdW9O0yXlNYC3ppxL3gWWPOcvEl57w==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -4954,16 +4957,15 @@ } }, "node_modules/daisyui": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.3.tgz", - "integrity": "sha512-8li177QCu6dqlEOzE3h/dAV1y9Movbjx5bzJIO/hNqMNZtJkbHM0trjTzbDejV7N57eNGdjBvAGtxZYKzS4jow==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.2.3.tgz", + "integrity": "sha512-tu3dDlfz+98kfgcPZPd5vyNVMUS2+lQQkrVINZNnAlIZEn0YDxXSmot30V9hy3g6f+8tdYQtdYYhjXuGzlI8Lg==", "dev": true, "dependencies": { - "colord": "^2.9", "css-selector-tokenizer": "^0.8", - "postcss": "^8", - "postcss-js": "^4", - "tailwindcss": "^3.1" + "culori": "^3", + "picocolors": "^1", + "postcss-js": "^4" }, "engines": { "node": ">=16.9.0" @@ -5064,15 +5066,15 @@ } }, "node_modules/deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", + "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", @@ -5082,11 +5084,14 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", + "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5237,9 +5242,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.566", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.566.tgz", - "integrity": "sha512-mv+fAy27uOmTVlUULy15U3DVJ+jg+8iyKH1bpwboCRhtDC69GKf1PPTZvEIhCyDr81RFqfxZJYrbgp933a1vtg==" + "version": "1.4.586", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.586.tgz", + "integrity": "sha512-qMa+E6yf1fNQbg3G66pHLXeJUP5CCCzNat1VPczOZOqgI2w4u+8y9sQnswMdGs5m4C1rOePq37EVBr/nsPQY7w==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -5482,9 +5487,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -6688,9 +6693,9 @@ } }, "node_modules/jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==", + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", "bin": { "jiti": "bin/jiti.js" } @@ -7952,9 +7957,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -8483,9 +8488,9 @@ } }, "node_modules/postcss-load-config/node_modules/yaml": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", - "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==", "engines": { "node": ">= 14" } @@ -8554,9 +8559,9 @@ } }, "node_modules/prettier-plugin-organize-imports": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz", - "integrity": "sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", + "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", "dev": true, "peerDependencies": { "@volar/vue-language-plugin-pug": "^1.0.4", @@ -8715,9 +8720,9 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/property-information": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.3.0.tgz", - "integrity": "sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.0.tgz", + "integrity": "sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ==", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" @@ -8750,9 +8755,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "engines": { "node": ">=6" } @@ -8857,9 +8862,9 @@ } }, "node_modules/react-icons": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", - "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", "peerDependencies": { "react": "*" } @@ -8957,11 +8962,11 @@ } }, "node_modules/react-router": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", - "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.19.0.tgz", + "integrity": "sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==", "dependencies": { - "@remix-run/router": "1.10.0" + "@remix-run/router": "1.12.0" }, "engines": { "node": ">=14.0.0" @@ -8971,12 +8976,12 @@ } }, "node_modules/react-router-dom": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", - "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.19.0.tgz", + "integrity": "sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==", "dependencies": { - "@remix-run/router": "1.10.0", - "react-router": "6.17.0" + "@remix-run/router": "1.12.0", + "react-router": "6.19.0" }, "engines": { "node": ">=14.0.0" @@ -9044,9 +9049,9 @@ } }, "node_modules/react-tooltip": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.21.6.tgz", - "integrity": "sha512-WbND5ee8Kr5HaSuDDiAmSyRp5jH77PSk8M0CUzmVfD+1WST8XOm1StJndK/wOQIP5GPvDVPy96ylLxY/V+VpqA==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.23.0.tgz", + "integrity": "sha512-MYqn6n+Af8NHHDL3zrSqzVSoK2LLqTNFp30CuuHCYlBEf+q88FWfg+8pSO+0GnDvOa5ZaryNDq9sAVQeNhnsgw==", "dependencies": { "@floating-ui/dom": "^1.0.0", "classnames": "^2.3.0" @@ -9080,16 +9085,16 @@ } }, "node_modules/reactflow": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.9.4.tgz", - "integrity": "sha512-IHAKBkJngNvU9y1vZ5Nw9rvA3Z+zc9geTgQQIi9qq9Y9knGLlDDr9KfsjbFMew9AycAAgVg8TvBEakF4IT5lqg==", + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.10.1.tgz", + "integrity": "sha512-Q616fElAc5/N37tMwjuRkkgm/VgmnLLTNNCj61z5mvJxae+/VXZQMfot1K6a5LLz9G3SVKqU97PMb9Ga1PRXew==", "dependencies": { - "@reactflow/background": "11.3.4", - "@reactflow/controls": "11.2.4", - "@reactflow/core": "11.9.4", - "@reactflow/minimap": "11.7.4", - "@reactflow/node-resizer": "2.2.4", - "@reactflow/node-toolbar": "1.3.4" + "@reactflow/background": "11.3.6", + "@reactflow/controls": "11.2.6", + "@reactflow/core": "11.10.1", + "@reactflow/minimap": "11.7.6", + "@reactflow/node-resizer": "2.2.6", + "@reactflow/node-toolbar": "1.3.6" }, "peerDependencies": { "react": ">=17", @@ -10590,9 +10595,9 @@ } }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "engines": { "node": ">= 10.0.0" } @@ -11419,9 +11424,9 @@ } }, "node_modules/zustand": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.4.tgz", - "integrity": "sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.6.tgz", + "integrity": "sha512-Rb16eW55gqL4W2XZpJh0fnrATxYEG3Apl2gfHTyDSE965x/zxslTikpNch0JgNjJA9zK6gEFW8Fl6d1rTZaqgg==", "dependencies": { "use-sync-external-store": "1.2.0" }, @@ -11477,9 +11482,9 @@ } }, "@antfu/ni": { - "version": "0.21.8", - "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.8.tgz", - "integrity": "sha512-90X8pU2szlvw0AJo9EZMbYc2eQKkmO7mAdC4tD4r5co2Mm56MT37MIG8EyB7p4WRheuzGxuLDxJ63mF6+Zajiw==" + "version": "0.21.9", + "resolved": "https://registry.npmjs.org/@antfu/ni/-/ni-0.21.9.tgz", + "integrity": "sha512-zlwQy574YEYl9ssWMV98ADxobU5wePdtyaOeQ5jgzdV8WldPcK+Osqd1SeQwEWjN0Io0GKiqpQzKZXVgxU1jPg==" }, "@babel/code-frame": { "version": "7.22.13", @@ -11542,25 +11547,25 @@ } }, "@babel/compat-data": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", - "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==" + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", + "integrity": "sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==" }, "@babel/core": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", - "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.3.tgz", + "integrity": "sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==", "requires": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.23.0", + "@babel/helper-module-transforms": "^7.23.3", "@babel/helpers": "^7.23.2", - "@babel/parser": "^7.23.0", + "@babel/parser": "^7.23.3", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.23.2", - "@babel/types": "^7.23.0", + "@babel/traverse": "^7.23.3", + "@babel/types": "^7.23.3", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -11581,11 +11586,11 @@ } }, "@babel/generator": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", - "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.3.tgz", + "integrity": "sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==", "requires": { - "@babel/types": "^7.23.0", + "@babel/types": "^7.23.3", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -11654,9 +11659,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", - "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", "requires": { "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-module-imports": "^7.22.15", @@ -11768,9 +11773,9 @@ } }, "@babel/parser": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", - "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", + "integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==" }, "@babel/runtime": { "version": "7.23.2", @@ -11791,26 +11796,26 @@ } }, "@babel/traverse": { - "version": "7.23.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", - "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.3.tgz", + "integrity": "sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==", "requires": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.23.0", + "@babel/generator": "^7.23.3", "@babel/helper-environment-visitor": "^7.22.20", "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.23.0", - "@babel/types": "^7.23.0", + "@babel/parser": "^7.23.3", + "@babel/types": "^7.23.3", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", - "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", + "integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "requires": { "@babel/helper-string-parser": "^7.22.5", "@babel/helper-validator-identifier": "^7.22.20", @@ -12081,9 +12086,9 @@ } }, "@floating-ui/react-dom": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz", - "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.4.tgz", + "integrity": "sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==", "requires": { "@floating-ui/dom": "^1.5.1" } @@ -12158,14 +12163,14 @@ } }, "@mui/base": { - "version": "5.0.0-beta.21", - "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.21.tgz", - "integrity": "sha512-eTKWx3WV/nwmRUK4z4K1MzlMyWCsi3WJ3RtV4DiXZeRh4qd4JCyp1Zzzi8Wv9xM4dEBmqQntFoei716PzwmFfA==", + "version": "5.0.0-beta.24", + "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.24.tgz", + "integrity": "sha512-bKt2pUADHGQtqWDZ8nvL2Lvg2GNJyd/ZUgZAJoYzRgmnxBL9j36MSlS3+exEdYkikcnvVafcBtD904RypFKb0w==", "requires": { "@babel/runtime": "^7.23.2", - "@floating-ui/react-dom": "^2.0.2", - "@mui/types": "^7.2.7", - "@mui/utils": "^5.14.15", + "@floating-ui/react-dom": "^2.0.4", + "@mui/types": "^7.2.9", + "@mui/utils": "^5.14.18", "@popperjs/core": "^2.11.8", "clsx": "^2.0.0", "prop-types": "^15.8.1" @@ -12179,22 +12184,22 @@ } }, "@mui/core-downloads-tracker": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.15.tgz", - "integrity": "sha512-ZCDzBWtCKjAYAlKKM3PA/jG/3uVIDT9ZitOtVixIVmTCQyc5jSV1qhJX8+qIGz4RQZ9KLzPWO2tXd0O5hvzouQ==" + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.18.tgz", + "integrity": "sha512-yFpF35fEVDV81nVktu0BE9qn2dD/chs7PsQhlyaV3EnTeZi9RZBuvoEfRym1/jmhJ2tcfeWXiRuHG942mQXJJQ==" }, "@mui/material": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.15.tgz", - "integrity": "sha512-Gq65rHjvLzkxmhG8bvag851Oqsmru7qkUb/cCI2xu7dQzmY345f9xJRJi72sRGjhaqHXWeRKw/yIwp/7oQoeXg==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.18.tgz", + "integrity": "sha512-y3UiR/JqrkF5xZR0sIKj6y7xwuEiweh9peiN3Zfjy1gXWXhz5wjlaLdoxFfKIEBUFfeQALxr/Y8avlHH+B9lpQ==", "requires": { "@babel/runtime": "^7.23.2", - "@mui/base": "5.0.0-beta.21", - "@mui/core-downloads-tracker": "^5.14.15", - "@mui/system": "^5.14.15", - "@mui/types": "^7.2.7", - "@mui/utils": "^5.14.15", - "@types/react-transition-group": "^4.4.7", + "@mui/base": "5.0.0-beta.24", + "@mui/core-downloads-tracker": "^5.14.18", + "@mui/system": "^5.14.18", + "@mui/types": "^7.2.9", + "@mui/utils": "^5.14.18", + "@types/react-transition-group": "^4.4.8", "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1", @@ -12210,19 +12215,19 @@ } }, "@mui/private-theming": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.15.tgz", - "integrity": "sha512-V2Xh+Tu6A07NoSpup0P9m29GwvNMYl5DegsGWqlOTJyAV7cuuVjmVPqxgvL8xBng4R85xqIQJRMjtYYktoPNuQ==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.18.tgz", + "integrity": "sha512-WSgjqRlzfHU+2Rou3HlR2Gqfr4rZRsvFgataYO3qQ0/m6gShJN+lhVEvwEiJ9QYyVzMDvNpXZAcqp8Y2Vl+PAw==", "requires": { "@babel/runtime": "^7.23.2", - "@mui/utils": "^5.14.15", + "@mui/utils": "^5.14.18", "prop-types": "^15.8.1" } }, "@mui/styled-engine": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.15.tgz", - "integrity": "sha512-mbOjRf867BysNpexe5Z/P8s3bWzDPNowmKhi7gtNDP/LPEeqAfiDSuC4WPTXmtvse1dCl30Nl755OLUYuoi7Mw==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.18.tgz", + "integrity": "sha512-pW8bpmF9uCB5FV2IPk6mfbQCjPI5vGI09NOLhtGXPeph/4xIfC3JdIX0TILU0WcTs3aFQqo6s2+1SFgIB9rCXA==", "requires": { "@babel/runtime": "^7.23.2", "@emotion/cache": "^11.11.0", @@ -12231,15 +12236,15 @@ } }, "@mui/system": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.15.tgz", - "integrity": "sha512-zr0Gdk1RgKiEk+tCMB900LaOpEC8NaGvxtkmMdL/CXgkqQZSVZOt2PQsxJWaw7kE4YVkIe4VukFVc43qcq9u3w==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.18.tgz", + "integrity": "sha512-hSQQdb3KF72X4EN2hMEiv8EYJZSflfdd1TRaGPoR7CIAG347OxCslpBUwWngYobaxgKvq6xTrlIl+diaactVww==", "requires": { "@babel/runtime": "^7.23.2", - "@mui/private-theming": "^5.14.15", - "@mui/styled-engine": "^5.14.15", - "@mui/types": "^7.2.7", - "@mui/utils": "^5.14.15", + "@mui/private-theming": "^5.14.18", + "@mui/styled-engine": "^5.14.18", + "@mui/types": "^7.2.9", + "@mui/utils": "^5.14.18", "clsx": "^2.0.0", "csstype": "^3.1.2", "prop-types": "^15.8.1" @@ -12253,18 +12258,18 @@ } }, "@mui/types": { - "version": "7.2.7", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.7.tgz", - "integrity": "sha512-sofpWmcBqOlTzRbr1cLQuUDKaUYVZTw8ENQrtL39TECRNENEzwgnNPh6WMfqMZlMvf1Aj9DLg74XPjnLr0izUQ==", + "version": "7.2.9", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.9.tgz", + "integrity": "sha512-k1lN/PolaRZfNsRdAqXtcR71sTnv3z/VCCGPxU8HfdftDkzi335MdJ6scZxvofMAd/K/9EbzCZTFBmlNpQVdCg==", "requires": {} }, "@mui/utils": { - "version": "5.14.15", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.15.tgz", - "integrity": "sha512-QBfHovAvTa0J1jXuYDaXGk+Yyp7+Fm8GSqx6nK2JbezGqzCFfirNdop/+bL9Flh/OQ/64PeXcW4HGDdOge+n3A==", + "version": "5.14.18", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.18.tgz", + "integrity": "sha512-HZDRsJtEZ7WMSnrHV9uwScGze4wM/Y+u6pDVo+grUjt5yXzn+wI8QX/JwTHh9YSw/WpnUL80mJJjgCnWj2VrzQ==", "requires": { "@babel/runtime": "^7.23.2", - "@types/prop-types": "^15.7.8", + "@types/prop-types": "^15.7.10", "prop-types": "^15.8.1", "react-is": "^18.2.0" } @@ -12892,29 +12897,29 @@ } }, "@reactflow/background": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.4.tgz", - "integrity": "sha512-bgwvqWxF09chwmdkyClpYEMaewBspdwjgLbbFlLf4SpWPFMYyuvCBQrcISsvy/EDEWO9i3Uj9ktgGAhvtSQsmA==", + "version": "11.3.6", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.6.tgz", + "integrity": "sha512-06FPlSUOOMALEEs+2PqPAbpqmL7WDjrkbG2UsDr2d6mbcDDhHiV4tu9FYoz44SQvXo7ma9VRotlsaR4OiRcYsg==", "requires": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.3", "zustand": "^4.4.1" } }, "@reactflow/controls": { - "version": "11.2.4", - "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.4.tgz", - "integrity": "sha512-x6e5p9iHjC6gd+4SoZ3DOOp0F1MefGKQ8hT6yPVdqxfo1+rV2WhrWvrX/MCoEu12Dp7457LdLfa0giy3aho8tQ==", + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.6.tgz", + "integrity": "sha512-4QHT92/ACVlZkvV+Hq44bAPV8WbMhkJl+/J0EbXcqQ1+an7cWJsF84eeelJw7R5J76RoaSSpKdsWsL2v7HAVlw==", "requires": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.3", "zustand": "^4.4.1" } }, "@reactflow/core": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.9.4.tgz", - "integrity": "sha512-Ko7nKPOYalwDTTbRHi2+QXDiidSAcpUzGN3G+0B+QysLZkcaPCkpkMjjHiDC4c/Z1BJBzs1FRJg/T6BXaBnYkg==", + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.10.1.tgz", + "integrity": "sha512-GIh3usY1W3eVobx//OO9+Cwm+5evQBBdPGxDaeXwm25UqPMWRI240nXQA5F/5gL5Mwpf0DUC7DR2EmrKNQy+Rw==", "requires": { "@types/d3": "^7.4.0", "@types/d3-drag": "^3.0.1", @@ -12928,11 +12933,11 @@ } }, "@reactflow/minimap": { - "version": "11.7.4", - "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.4.tgz", - "integrity": "sha512-Jo1R+uDey9IV7O2s3m0gK2+cZpg9M8hq2EZJb3NGfOSzMAPhj3mby0fNJIgTzycreuht0TpA51c2YfjGI3YIOw==", + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.6.tgz", + "integrity": "sha512-kJEtyeQkTZYViLGebVWHVUJROMAGcvejvT+iX4DqKnFb5yK8E8LWlXQpRx2FrL9gDy80mJJaciy7IxnnQKE1bg==", "requires": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "@types/d3-selection": "^3.0.3", "@types/d3-zoom": "^3.0.1", "classcat": "^5.0.3", @@ -12942,11 +12947,11 @@ } }, "@reactflow/node-resizer": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.4.tgz", - "integrity": "sha512-+p271/hAsM5M1+RQTWW/02pbNkCHeGXwxGimIlL1tMIagyuko0NX2vOz2B8jxJnPKlF09Wj18BcXBNUm3nDcSg==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.6.tgz", + "integrity": "sha512-1Xb6q97uP7hRBLpog9sRCNfnsHdDgFRGEiU+lQqGgPEAeYwl4nRjWa/sXwH6ajniKxBhGEvrdzOgEFn6CRMcpQ==", "requires": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.4", "d3-drag": "^3.0.0", "d3-selection": "^3.0.0", @@ -12954,19 +12959,19 @@ } }, "@reactflow/node-toolbar": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.4.tgz", - "integrity": "sha512-TfcmpXHRBb2mUfzKGjburiU6FWqRME9pPFs1OwIC1z5e9BjupQhNDEKEk8XHi7PKL/mAiDfwuGXaM1BVVFuPqw==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.6.tgz", + "integrity": "sha512-JXDEuZ0wKjZ8z7qK2bIst0eZPzNyVEsiHL0e93EyuqT4fA9icoyE0fLq2ryNOOp7MXgId1h7LusnH6ta45F0yQ==", "requires": { - "@reactflow/core": "11.9.4", + "@reactflow/core": "11.10.1", "classcat": "^5.0.3", "zustand": "^4.4.1" } }, "@remix-run/router": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", - "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==" + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.12.0.tgz", + "integrity": "sha512-2hXv036Bux90e1GXTWSMfNzfDDK8LA8JYEWfyHxzvwdp6GyoWEovKc9cotb3KCKmkdwsIBuFGX7ScTWyiHv7Eg==" }, "@rollup/pluginutils": { "version": "5.0.5", @@ -13100,9 +13105,9 @@ } }, "@swc/cli": { - "version": "0.1.62", - "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.1.62.tgz", - "integrity": "sha512-kOFLjKY3XH1DWLfXL1/B5MizeNorHR8wHKEi92S/Zi9Md/AK17KSqR8MgyRJ6C1fhKHvbBCl8wboyKAFXStkYw==", + "version": "0.1.63", + "resolved": "https://registry.npmjs.org/@swc/cli/-/cli-0.1.63.tgz", + "integrity": "sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==", "dev": true, "requires": { "@mole-inc/bin-wrapper": "^8.0.1", @@ -13122,92 +13127,92 @@ } }, "@swc/core": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.95.tgz", - "integrity": "sha512-PMrNeuqIusq9DPDooV3FfNEbZuTu5jKAc04N3Hm6Uk2Fl49cqElLFQ4xvl4qDmVDz97n3n/C1RE0/f6WyGPEiA==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.96.tgz", + "integrity": "sha512-zwE3TLgoZwJfQygdv2SdCK9mRLYluwDOM53I+dT6Z5ZvrgVENmY3txvWDvduzkV+/8IuvrRbVezMpxcojadRdQ==", "dev": true, "requires": { - "@swc/core-darwin-arm64": "1.3.95", - "@swc/core-darwin-x64": "1.3.95", - "@swc/core-linux-arm-gnueabihf": "1.3.95", - "@swc/core-linux-arm64-gnu": "1.3.95", - "@swc/core-linux-arm64-musl": "1.3.95", - "@swc/core-linux-x64-gnu": "1.3.95", - "@swc/core-linux-x64-musl": "1.3.95", - "@swc/core-win32-arm64-msvc": "1.3.95", - "@swc/core-win32-ia32-msvc": "1.3.95", - "@swc/core-win32-x64-msvc": "1.3.95", + "@swc/core-darwin-arm64": "1.3.96", + "@swc/core-darwin-x64": "1.3.96", + "@swc/core-linux-arm-gnueabihf": "1.3.96", + "@swc/core-linux-arm64-gnu": "1.3.96", + "@swc/core-linux-arm64-musl": "1.3.96", + "@swc/core-linux-x64-gnu": "1.3.96", + "@swc/core-linux-x64-musl": "1.3.96", + "@swc/core-win32-arm64-msvc": "1.3.96", + "@swc/core-win32-ia32-msvc": "1.3.96", + "@swc/core-win32-x64-msvc": "1.3.96", "@swc/counter": "^0.1.1", "@swc/types": "^0.1.5" } }, "@swc/core-darwin-arm64": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.95.tgz", - "integrity": "sha512-VAuBAP3MNetO/yBIBzvorUXq7lUBwhfpJxYViSxyluMwtoQDhE/XWN598TWMwMl1ZuImb56d7eUsuFdjgY7pJw==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.96.tgz", + "integrity": "sha512-8hzgXYVd85hfPh6mJ9yrG26rhgzCmcLO0h1TIl8U31hwmTbfZLzRitFQ/kqMJNbIBCwmNH1RU2QcJnL3d7f69A==", "dev": true, "optional": true }, "@swc/core-darwin-x64": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.95.tgz", - "integrity": "sha512-20vF2rvUsN98zGLZc+dsEdHvLoCuiYq/1B+TDeE4oolgTFDmI1jKO+m44PzWjYtKGU9QR95sZ6r/uec0QC5O4Q==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.3.96.tgz", + "integrity": "sha512-mFp9GFfuPg+43vlAdQZl0WZpZSE8sEzqL7sr/7Reul5McUHP0BaLsEzwjvD035ESfkY8GBZdLpMinblIbFNljQ==", "dev": true, "optional": true }, "@swc/core-linux-arm-gnueabihf": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.95.tgz", - "integrity": "sha512-oEudEM8PST1MRNGs+zu0cx5i9uP8TsLE4/L9HHrS07Ck0RJ3DCj3O2fU832nmLe2QxnAGPwBpSO9FntLfOiWEQ==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.96.tgz", + "integrity": "sha512-8UEKkYJP4c8YzYIY/LlbSo8z5Obj4hqcv/fUTHiEePiGsOddgGf7AWjh56u7IoN/0uEmEro59nc1ChFXqXSGyg==", "dev": true, "optional": true }, "@swc/core-linux-arm64-gnu": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.95.tgz", - "integrity": "sha512-pIhFI+cuC1aYg+0NAPxwT/VRb32f2ia8oGxUjQR6aJg65gLkUYQzdwuUmpMtFR2WVf7WVFYxUnjo4UyMuyh3ng==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.96.tgz", + "integrity": "sha512-c/IiJ0s1y3Ymm2BTpyC/xr6gOvoqAVETrivVXHq68xgNms95luSpbYQ28rqaZC8bQC8M5zdXpSc0T8DJu8RJGw==", "dev": true, "optional": true }, "@swc/core-linux-arm64-musl": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.95.tgz", - "integrity": "sha512-ZpbTr+QZDT4OPJfjPAmScqdKKaT+wGurvMU5AhxLaf85DuL8HwUwwlL0n1oLieLc47DwIJEMuKQkYhXMqmJHlg==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.96.tgz", + "integrity": "sha512-i5/UTUwmJLri7zhtF6SAo/4QDQJDH2fhYJaBIUhrICmIkRO/ltURmpejqxsM/ye9Jqv5zG7VszMC0v/GYn/7BQ==", "dev": true, "optional": true }, "@swc/core-linux-x64-gnu": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.95.tgz", - "integrity": "sha512-n9SuHEFtdfSJ+sHdNXNRuIOVprB8nbsz+08apKfdo4lEKq6IIPBBAk5kVhPhkjmg2dFVHVo4Tr/OHXM1tzWCCw==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.96.tgz", + "integrity": "sha512-USdaZu8lTIkm4Yf9cogct/j5eqtdZqTgcTib4I+NloUW0E/hySou3eSyp3V2UAA1qyuC72ld1otXuyKBna0YKQ==", "dev": true, "optional": true }, "@swc/core-linux-x64-musl": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.95.tgz", - "integrity": "sha512-L1JrVlsXU3LC0WwmVnMK9HrOT2uhHahAoPNMJnZQpc18a0paO9fqifPG8M/HjNRffMUXR199G/phJsf326UvVg==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.96.tgz", + "integrity": "sha512-QYErutd+G2SNaCinUVobfL7jWWjGTI0QEoQ6hqTp7PxCJS/dmKmj3C5ZkvxRYcq7XcZt7ovrYCTwPTHzt6lZBg==", "dev": true, "optional": true }, "@swc/core-win32-arm64-msvc": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.95.tgz", - "integrity": "sha512-YaP4x/aZbUyNdqCBpC2zL8b8n58MEpOUpmOIZK6G1SxGi+2ENht7gs7+iXpWPc0sy7X3YPKmSWMAuui0h8lgAA==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.96.tgz", + "integrity": "sha512-hjGvvAduA3Un2cZ9iNP4xvTXOO4jL3G9iakhFsgVhpkU73SGmK7+LN8ZVBEu4oq2SUcHO6caWvnZ881cxGuSpg==", "dev": true, "optional": true }, "@swc/core-win32-ia32-msvc": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.95.tgz", - "integrity": "sha512-w0u3HI916zT4BC/57gOd+AwAEjXeUlQbGJ9H4p/gzs1zkSHtoDQghVUNy3n/ZKp9KFod/95cA8mbVF9t1+6epQ==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.96.tgz", + "integrity": "sha512-Far2hVFiwr+7VPCM2GxSmbh3ikTpM3pDombE+d69hkedvYHYZxtTF+2LTKl/sXtpbUnsoq7yV/32c9R/xaaWfw==", "dev": true, "optional": true }, "@swc/core-win32-x64-msvc": { - "version": "1.3.95", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.95.tgz", - "integrity": "sha512-5RGnMt0S6gg4Gc6QtPUJ3Qs9Un4sKqccEzgH/tj7V/DVTJwKdnBKxFZfgQ34OR2Zpz7zGOn889xwsFVXspVWNA==", + "version": "1.3.96", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.96.tgz", + "integrity": "sha512-4VbSAniIu0ikLf5mBX81FsljnfqjoVGleEkCQv4+zRlyZtO3FHoDPkeLVoy6WRlj7tyrRcfUJ4mDdPkbfTO14g==", "dev": true, "optional": true }, @@ -13233,23 +13238,23 @@ } }, "@tabler/icons": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.39.0.tgz", - "integrity": "sha512-iK3j2jIEGIUaJcbYYg5iwyG1Y/m4lzUxAUbxRpvgeXCWP29jvZaH5hajZmU3KaSealddHuJg7PSQislPHpCsoQ==" + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/@tabler/icons/-/icons-2.40.0.tgz", + "integrity": "sha512-VqKsBSX159cLFTnCzkCmGhZtSPJHNN0lM2sC4xe0HPOfPUnjiex7rDHDdut4oe4iKRecDDpwXwM9BcU6xCPlCg==" }, "@tabler/icons-react": { - "version": "2.39.0", - "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.39.0.tgz", - "integrity": "sha512-MyUK1jqtmHPZBnDXqIc1Y5OnfoqG+tGaSB1/gcl0mlY462fJ5f3QB0ZIZzAHMAGYb6K2iJSdFIFavhcgpDDZ7Q==", + "version": "2.40.0", + "resolved": "https://registry.npmjs.org/@tabler/icons-react/-/icons-react-2.40.0.tgz", + "integrity": "sha512-C+dDOZowFbwI3LGQP0fdua+hOPkGkW7XeMcRXTSdEKc5fD75W6zRO5nXnWivIMRKsi/Y26EDmnQo15N8JX378w==", "requires": { - "@tabler/icons": "2.39.0", + "@tabler/icons": "2.40.0", "prop-types": "^15.7.2" } }, "@tailwindcss/forms": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.6.tgz", - "integrity": "sha512-Fw+2BJ0tmAwK/w01tEFL5TiaJBX1NLT1/YbWgvm7ws3Qcn11kiXxzNTEQDMs5V3mQemhB56l3u0i9dwdzSQldA==", + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", + "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", "requires": { "mini-svg-data-uri": "^1.2.3" } @@ -13397,9 +13402,9 @@ } }, "@types/aria-query": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.3.tgz", - "integrity": "sha512-0Z6Tr7wjKJIk4OUEjVUQMtyunLDy339vcMaj38Kpj6jM2OE1p3S4kXExKZ7a3uXQAPCoy3sbrP1wibDKaf39oA==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", "dev": true }, "@types/axios": { @@ -13428,9 +13433,9 @@ "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" }, "@types/d3": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.2.tgz", - "integrity": "sha512-Y4g2Yb30ZJmmtqAJTqMRaqXwRawfvpdpVmyEYEcyGNhrQI/Zvkq3k7yE1tdN07aFSmNBfvmegMQ9Fe2qy9ZMhw==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", "requires": { "@types/d3-array": "*", "@types/d3-axis": "*", @@ -13465,229 +13470,229 @@ } }, "@types/d3-array": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.9.tgz", - "integrity": "sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA==" + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" }, "@types/d3-axis": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.5.tgz", - "integrity": "sha512-ufDAV3SQzju+uB3Jlty7SUb/jMigjpIlvDDcSGvGmmO6OT/sNO93UE0dRzwWOZeBLzrLSA0CQM4bf3iq1std3A==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", "requires": { "@types/d3-selection": "*" } }, "@types/d3-brush": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.5.tgz", - "integrity": "sha512-JROQXZNq1X6QdWstESDUv1VilwZ2hBCQnWB91yal+5yZvYwGQvYsGCjrkHGfKK/8/AcX1JnERmpQzdDDuLRUsA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", "requires": { "@types/d3-selection": "*" } }, "@types/d3-chord": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.5.tgz", - "integrity": "sha512-rs26AIhJjtc+XLR4YQU8IjPTLOlDVO4PR1y+pVFYEHzKh2tE5tYz3MF4QV6iz7HboXQEaYpJQt8dH9uUkne8yA==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" }, "@types/d3-color": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.2.tgz", - "integrity": "sha512-At+Ski7dL8Bs58E8g8vPcFJc8tGcaC12Z4m07+p41+DRqnZQcAlp3NfYjLrhNYv+zEyQitU1CUxXNjqUyf+c0g==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" }, "@types/d3-contour": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.5.tgz", - "integrity": "sha512-wLvjwdOQVd1NL1IcW90CCt1VtpeZ3V20p/OTXlkT8uAiprrJnq2PNNnRNe1QCez4U9aMU29Z14zpJQVLW1+Lcg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", "requires": { "@types/d3-array": "*", "@types/geojson": "*" } }, "@types/d3-delaunay": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.3.tgz", - "integrity": "sha512-+Lf5NPKZ4JBC9tbudVkKceQXRxU3jJs0el9aKQvinMtdnFSOG84eVXyhCNgIFuXNQO3iIcYs7sgzN359FEOZnQ==" + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" }, "@types/d3-dispatch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.5.tgz", - "integrity": "sha512-hxvq2kc+9hydVppo21JCGfcM0tLTh1DXnG3MLN0KlxsNZJH4bsdl1iXDuWtXFpWWlBrCMwSqlnoLPDxNAZU3Bg==" + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.6.tgz", + "integrity": "sha512-4fvZhzMeeuBJYZXRXrRIQnvUYfyXwYmLsdiN7XXmVNQKKw1cM8a5WdID0g1hVFZDqT9ZqZEY5pD44p24VS7iZQ==" }, "@types/d3-drag": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.5.tgz", - "integrity": "sha512-arHyAGvO0NEGGPCU2jTb31TlXeSxwty1bIxr5wOFOCVqVjgriXloLWXoRp39Oa0Y/qXxcAVMIonAWLrtLxUZAQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", "requires": { "@types/d3-selection": "*" } }, "@types/d3-dsv": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.5.tgz", - "integrity": "sha512-73WZR3QFOaSRVz9iOrebTbTnbo7xjcgS/i0Cq5zy0jMXPO3v/JbkTD3Zqii1eYE6v4EJ78g5VP407rm+p8fdlA==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" }, "@types/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-VZofjpEt8HWv3nxUAosj5o/+4JflnJ7Bbv07k17VO3T2WRuzGdZeookfaF60iVh5RdhVG49LE5w6LIshVUC6rg==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" }, "@types/d3-fetch": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.5.tgz", - "integrity": "sha512-Rc8pb6H0RRLpAV2hEXduykUgcDUOhjSLTLmCIeo6ejzgs4SaITh/EteMb3p5Env3Hqjsqw0fCksyqopHHzMkMg==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", "requires": { "@types/d3-dsv": "*" } }, "@types/d3-force": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.7.tgz", - "integrity": "sha512-rsok4CEvPLyVWRPsFiBhanJc3up03H/EARVz4d8soPh8drv82YMuAckYy4yv8g4/81JwCng5U5/o9aj9d0T6bQ==" + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.9.tgz", + "integrity": "sha512-IKtvyFdb4Q0LWna6ymywQsEYjK/94SGhPrMfEr1TIc5OBeziTi+1jcCvttts8e0UWZIxpasjnQk9MNk/3iS+kA==" }, "@types/d3-format": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.3.tgz", - "integrity": "sha512-kxuLXSAEJykTeL/EI3tUiEfGqru7PRdqEy099YBnqFl+fF167UVSB4+wntlZv86ZdoYf0DHjsRHnTIm8kcH7qw==" + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" }, "@types/d3-geo": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.0.6.tgz", - "integrity": "sha512-wblAES3b+C3hvp4VakwECEKtHquT/xc6K4HOna95LM1j1fd7s7WmU4V+JMQZfKhNCMkV2vWD+ZUgY2Uj6gqfuA==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", "requires": { "@types/geojson": "*" } }, "@types/d3-hierarchy": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.5.tgz", - "integrity": "sha512-DEcBUj1IL3WyPLDlh4m2nsNXnMLITXM5Vwcu4G85yJHtf2cVGPBjgky3L11WBnT+ayHKf06Tchk5mY1eGmd4WQ==" + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.6.tgz", + "integrity": "sha512-qlmD/8aMk5xGorUvTUWHCiumvgaUXYldYjNVOWtYoTYY/L+WwIEAmJxUmTgr9LoGNG0PPAOmqMDJVDPc7DOpPw==" }, "@types/d3-interpolate": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.3.tgz", - "integrity": "sha512-6OZ2EIB4lLj+8cUY7I/Cgn9Q+hLdA4DjJHYOQDiHL0SzqS1K9DL5xIOVBSIHgF+tiuO9MU1D36qvdIvRDRPh+Q==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", "requires": { "@types/d3-color": "*" } }, "@types/d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-blRhp7ki7pVznM8k6lk5iUU9paDbVRVq+/xpf0RRgSJn5gr6SE7RcFtxooYGMBOc1RZiGyqRpVdu5AD0z0ooMA==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.2.tgz", + "integrity": "sha512-WAIEVlOCdd/NKRYTsqCpOMHQHemKBEINf8YXMYOtXH0GA7SY0dqMB78P3Uhgfy+4X+/Mlw2wDtlETkN6kQUCMA==" }, "@types/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-nrcWPk7B9qs6xnpq60Cls44zm9eDmFAv65qi/N/emh/oftnG6uYz49aIS0mdFaGeJxVN8H3pHneMuZMV8EwFdw==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" }, "@types/d3-quadtree": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.4.tgz", - "integrity": "sha512-B725MopFDIOQ6njFbeOxIEf42HVO2Xv+FmcxQISdOKErvLbFqWz3Riu+OWujUYoogreqqyHBHcGGL/JzzXQYsw==" + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.5.tgz", + "integrity": "sha512-Cb1f3jyNBnvMMkf4KBZ7IgAQVWd9yzBwYcrxGqg3aPCUgWELAS+nyeB7r76aqu1e3+CGDjhk4BrWaFBekMwigg==" }, "@types/d3-random": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.2.tgz", - "integrity": "sha512-8QhsqkKs6mymAZMrg3ZFXPxKA34rdgp3ZrtB8o6mhFsKAd1gOvR1gocWnca+kmXypQdwgnzKm9gZE2Uw8NjjKw==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" }, "@types/d3-scale": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.6.tgz", - "integrity": "sha512-lo3oMLSiqsQUovv8j15X4BNEDOsnHuGjeVg7GRbAuB2PUa1prK5BNSOu6xixgNf3nqxPl4I1BqJWrPvFGlQoGQ==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", "requires": { "@types/d3-time": "*" } }, "@types/d3-scale-chromatic": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.1.tgz", - "integrity": "sha512-Ob7OrwiTeQXY/WBBbRHGZBOn6rH1h7y3jjpTSKYqDEeqFjktql6k2XSgNwLrLDmAsXhEn8P9NHDY4VTuo0ZY1w==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.0.2.tgz", + "integrity": "sha512-kpKNZMDT3OAX6b5ct5nS/mv6LULagnUy4DmS6yyNjclje1qVe7vbjPwY3q1TGz6+Wr2IUkgFatCzqYUl54fHag==" }, "@types/d3-selection": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.8.tgz", - "integrity": "sha512-pxCZUfQyedq/DIlPXIR5wE1mIH37omOdx1yxRudL3KZ4AC+156jMjOv1z5RVlGq62f8WX2kyO0hTVgEx627QFg==" + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.10.tgz", + "integrity": "sha512-cuHoUgS/V3hLdjJOLTT691+G2QoqAjCVLmr4kJXR4ha56w1Zdu8UUQ5TxLRqudgNjwXeQxKMq4j+lyf9sWuslg==" }, "@types/d3-shape": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.4.tgz", - "integrity": "sha512-M2/xsWPsjaZc5ifMKp1EBp0gqJG0eO/zlldJNOC85Y/5DGsBQ49gDkRJ2h5GY7ZVD6KUumvZWsylSbvTaJTqKg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.5.tgz", + "integrity": "sha512-dfEWpZJ1Pdg8meLlICX1M3WBIpxnaH2eQV2eY43Y5ysRJOTAV9f3/R++lgJKFstfrEOE2zdJ0sv5qwr2Bkic6Q==", "requires": { "@types/d3-path": "*" } }, "@types/d3-time": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.2.tgz", - "integrity": "sha512-kbdRXTmUgNfw5OTE3KZnFQn6XdIc4QGroN5UixgdrXATmYsdlPQS6pEut9tVlIojtzuFD4txs/L+Rq41AHtLpg==" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" }, "@types/d3-time-format": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.2.tgz", - "integrity": "sha512-wr08C1Gh77qaN8JIkrn5Rz/bdt5M9bdEqFmEOcYhUSq2t2sHvLTBfb4XAtGB3D4hm0ubj50NXWWXoXyp5tPXDg==" + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" }, "@types/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-GGTvzKccVEhxmRfJEB6zhY9ieT4UhGVUIQaBzFpUO9OXy2ycAlnPCSJLzmGGgqt3KVjqN3QCQB4g1rsZnHsWhg==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" }, "@types/d3-transition": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.6.tgz", - "integrity": "sha512-K0To23B5UxNwFtKORnS5JoNYvw/DnknU5MzhHIS9czJ/lTqFFDeU6w9lArOdoTl0cZFNdNrMJSFCbRCEHccH2w==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.8.tgz", + "integrity": "sha512-ew63aJfQ/ms7QQ4X7pk5NxQ9fZH/z+i24ZfJ6tJSfqxJMrYLiK01EAs2/Rtw/JreGUsS3pLPNV644qXFGnoZNQ==", "requires": { "@types/d3-selection": "*" } }, "@types/d3-zoom": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.6.tgz", - "integrity": "sha512-dGZQaXEu7aNcCL71LPpjB58IjoQNM9oDPfQuMUJ7N/fbkcIWGX2PnmUWO1jPJ+RLbZBpRUggJUX8twKRvo2hKQ==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", "requires": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "@types/debug": { - "version": "4.1.10", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.10.tgz", - "integrity": "sha512-tOSCru6s732pofZ+sMv9o4o3Zc+Sa8l3bxd/tweTQudFn06vAzb13ZX46Zi6m6EJ+RUbRTHvgQJ1gBtSgkaUYA==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "requires": { "@types/ms": "*" } }, "@types/estree": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.3.tgz", - "integrity": "sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "@types/geojson": { - "version": "7946.0.12", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.12.tgz", - "integrity": "sha512-uK2z1ZHJyC0nQRbuovXFt4mzXDwf27vQeUWNhfKGwRcWW429GOhP8HxUHlM6TLH4bzmlv/HlEjpvJh3JfmGsAA==" + "version": "7946.0.13", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.13.tgz", + "integrity": "sha512-bmrNrgKMOhM3WsafmbGmC+6dsF2Z308vLFsQ3a/bT8X8Sv5clVYpPars/UPq+sAaJP+5OoLAYgwbkS5QEJdLUQ==" }, "@types/hast": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.7.tgz", - "integrity": "sha512-EVLigw5zInURhzfXUM65eixfadfsHKomGKUakToXo84t8gGIJuTcD2xooM2See7GyQ7DRtYjhCHnSUQez8JaLw==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.8.tgz", + "integrity": "sha512-aMIqAlFd2wTIDZuvLbhUT+TGvMxrNC8ECUIVtH6xxy0sQLs3iu6NO8Kp/VT5je7i5ufnebXzdV1dNDMnvaH6IQ==", "requires": { "@types/unist": "^2" } }, "@types/hoist-non-react-statics": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.4.tgz", - "integrity": "sha512-ZchYkbieA+7tnxwX/SCBySx9WwvWR8TaP5tb2jRAzwvLb/rWchGw3v0w3pqUbUvj0GCwW2Xz/AVPSk6kUGctXQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", "requires": { "@types/react": "*", "hoist-non-react-statics": "^3.3.0" } }, "@types/http-cache-semantics": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.3.tgz", - "integrity": "sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", "dev": true }, "@types/jest": { @@ -13701,9 +13706,9 @@ } }, "@types/katex": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.5.tgz", - "integrity": "sha512-DD2Y3xMlTQvAnN6d8803xdgnOeYZ+HwMglb7/9YCf49J9RkJL53azf9qKa40MkEYhqVwxZ1GS2+VlShnz4Z1Bw==" + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.6.tgz", + "integrity": "sha512-rZYO1HInM99rAFYNwGqbYPxHZHxu2IwZYKj4bJ4oh6edVrm1UId8mmbHIZLBtG253qU6y3piag0XYe/joNnwzQ==" }, "@types/keyv": { "version": "3.1.4", @@ -13715,9 +13720,9 @@ } }, "@types/lodash": { - "version": "4.14.200", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.200.tgz", - "integrity": "sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==", + "version": "4.14.201", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.201.tgz", + "integrity": "sha512-y9euML0cim1JrykNxADLfaG0FgD1g/yTHwUs/Jg9ZIU7WKj2/4IW9Lbb1WZbvck78W/lfGXFfe+u2EGfIJXdLQ==", "dev": true }, "@types/mathjax": { @@ -13726,38 +13731,38 @@ "integrity": "sha512-y0WSZBtBNQwcYipTU/BhgeFu1EZNlFvUNCmkMXV9kBQZq7/o5z82dNVyH3yy2Xv5zzeNeQoHSL4Xm06+EQiH+g==" }, "@types/mdast": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.14.tgz", - "integrity": "sha512-gVZ04PGgw1qLZKsnWnyFv4ORnaJ+DXLdHTVSFbU8yX6xZ34Bjg4Q32yPkmveUP1yItXReKfB0Aknlh/3zxTKAw==", + "version": "3.0.15", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", + "integrity": "sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==", "requires": { "@types/unist": "^2" } }, "@types/ms": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.33.tgz", - "integrity": "sha512-AuHIyzR5Hea7ij0P9q7vx7xu4z0C28ucwjAZC0ja7JhINyCnOw8/DnvAPQQ9TfOlCtZAmCERKQX9+o1mgQhuOQ==" + "version": "0.7.34", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.34.tgz", + "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "@types/node": { - "version": "16.18.59", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.59.tgz", - "integrity": "sha512-PJ1w2cNeKUEdey4LiPra0ZuxZFOGvetswE8qHRriV/sUkL5Al4tTmPV9D2+Y/TPIxTHHgxTfRjZVKWhPw/ORhQ==", + "version": "16.18.61", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.61.tgz", + "integrity": "sha512-k0N7BqGhJoJzdh6MuQg1V1ragJiXTh8VUBAZTWjJ9cUq23SG0F0xavOwZbhiP4J3y20xd6jxKx+xNUhkMAi76Q==", "devOptional": true }, "@types/parse-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.1.tgz", - "integrity": "sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" }, "@types/prop-types": { - "version": "15.7.9", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.9.tgz", - "integrity": "sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==" + "version": "15.7.10", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.10.tgz", + "integrity": "sha512-mxSnDQxPqsZxmeShFH+uwQ4kO4gcJcGahjjMFeLbKE95IAZiiZyiEepGZjtXJ7hN/yfu0bu9xN2ajcU0JcxX6A==" }, "@types/react": { - "version": "18.2.32", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.32.tgz", - "integrity": "sha512-F0FVIZQ1x5Gxy/VYJb7XcWvCcHR28Sjwt1dXLspdIatfPq1MVACfnBDwKe6ANLxQ64riIJooXClpUR6oxTiepg==", + "version": "18.2.37", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.37.tgz", + "integrity": "sha512-RGAYMi2bhRgEXT3f4B92WTohopH6bIXw05FuGlmJEnv/omEn190+QYEIYxIAuIBdKgboYYdVved2p1AxZVQnaw==", "requires": { "@types/prop-types": "*", "@types/scheduler": "*", @@ -13765,35 +13770,35 @@ } }, "@types/react-dom": { - "version": "18.2.14", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.14.tgz", - "integrity": "sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==", + "version": "18.2.15", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.15.tgz", + "integrity": "sha512-HWMdW+7r7MR5+PZqJF6YFNSCtjz1T0dsvo/f1BV6HkV+6erD/nA7wd9NM00KVG83zf2nJ7uATPO9ttdIPvi3gg==", "devOptional": true, "requires": { "@types/react": "*" } }, "@types/react-transition-group": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.8.tgz", - "integrity": "sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.9.tgz", + "integrity": "sha512-ZVNmWumUIh5NhH8aMD9CR2hdW0fNuYInlocZHaZ+dgk/1K49j1w/HoAuK1ki+pgscQrOFRTlXeoURtuzEkV3dg==", "requires": { "@types/react": "*" } }, "@types/responselike": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.2.tgz", - "integrity": "sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", + "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", "dev": true, "requires": { "@types/node": "*" } }, "@types/scheduler": { - "version": "0.16.5", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.5.tgz", - "integrity": "sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==" + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.6.tgz", + "integrity": "sha512-Vlktnchmkylvc9SnwwwozTv04L/e1NykF5vgoQ0XTmI8DD+wxfjQuHuvHS3p0r2jz2x2ghPs2h1FVeDirIteWA==" }, "@types/testing-library__jest-dom": { "version": "5.14.9", @@ -13805,23 +13810,23 @@ } }, "@types/unist": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.9.tgz", - "integrity": "sha512-zC0iXxAv1C1ERURduJueYzkzZ2zaGyc+P2c95hgkikHPr3z8EdUZOlgEQ5X0DRmwDZn+hekycQnoeiiRVrmilQ==" + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", + "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==" }, "@types/uuid": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.6.tgz", - "integrity": "sha512-BT2Krtx4xaO6iwzwMFUYvWBWkV2pr37zD68Vmp1CDV196MzczBRxuEpD6Pr395HAgebC/co7hOphs53r8V7jew==", + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", "dev": true }, "@vitejs/plugin-react-swc": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.4.0.tgz", - "integrity": "sha512-m7UaA4Uvz82N/0EOVpZL4XsFIakRqrFKeSNxa1FBLSXGvWrWRBwmZb4qxk+ZIVAZcW3c3dn5YosomDgx62XWcQ==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz", + "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==", "dev": true, "requires": { - "@swc/core": "^1.3.85" + "@swc/core": "^1.3.96" } }, "abab": { @@ -13835,14 +13840,14 @@ "integrity": "sha512-jbQfFaw+57OBwPt7qSNHuW+RA8smmRwkWRS1Ozh6K/QxUspBgBV/LpdSzlY7vee8TomS6j3D33B9rIeH1qMwsA==" }, "ace-builds": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.31.0.tgz", - "integrity": "sha512-nitIhcUYA6wyO3lo2WZBPX5fcjllW6XFt4EFyHwcN2Fp70/IZwz8tdw6a0+8udDEwDj/ebt3aWEClIyCs/6qYA==" + "version": "1.31.2", + "resolved": "https://registry.npmjs.org/ace-builds/-/ace-builds-1.31.2.tgz", + "integrity": "sha512-IeZI9ytPA6mB+goPxPkUPW4vXBoLuaBl5czu2tjtKrMi7mdRgyIUA/8e5JlrI1mqKoMeWHoUujzMTWkyutTdBw==" }, "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==" + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==" }, "acorn-globals": { "version": "7.0.1", @@ -13854,9 +13859,9 @@ } }, "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==" + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", + "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==" }, "add": { "version": "2.0.6", @@ -13991,9 +13996,9 @@ "dev": true }, "axios": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.1.tgz", - "integrity": "sha512-Q28iYCWzNHjAm+yEAot5QaAMxhMghWLFVf7rRdwhUI+c2jix2DUXjAHXVi+s1ibs3mjPO/cCgbA++3BjD0vP/A==", + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -14248,9 +14253,9 @@ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, "caniuse-lite": { - "version": "1.0.30001554", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001554.tgz", - "integrity": "sha512-A2E3U//MBwbJVzebddm1YfNp7Nud5Ip+IPn4BozBmn4KqVX7AvluoIDFWjsv5OkGnKUXQVmMSoMKLa3ScCblcQ==" + "version": "1.0.30001562", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001562.tgz", + "integrity": "sha512-kfte3Hym//51EdX4239i+Rmp20EsLIYGdPkERegTgU19hQWCRhsRFGKHTliUlsry53tv17K7n077Kqa0WJU4ng==" }, "ccount": { "version": "2.0.1", @@ -14370,12 +14375,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "dev": true - }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -14488,6 +14487,12 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, + "culori": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/culori/-/culori-3.2.0.tgz", + "integrity": "sha512-HIEbTSP7vs1mPq/2P9In6QyFE0Tkpevh0k9a+FkjhD+cwsYm9WRSbn4uMdW9O0yXlNYC3ppxL3gWWPOcvEl57w==", + "dev": true + }, "d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -14555,16 +14560,15 @@ } }, "daisyui": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-3.9.3.tgz", - "integrity": "sha512-8li177QCu6dqlEOzE3h/dAV1y9Movbjx5bzJIO/hNqMNZtJkbHM0trjTzbDejV7N57eNGdjBvAGtxZYKzS4jow==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.2.3.tgz", + "integrity": "sha512-tu3dDlfz+98kfgcPZPd5vyNVMUS2+lQQkrVINZNnAlIZEn0YDxXSmot30V9hy3g6f+8tdYQtdYYhjXuGzlI8Lg==", "dev": true, "requires": { - "colord": "^2.9", "css-selector-tokenizer": "^0.8", - "postcss": "^8", - "postcss-js": "^4", - "tailwindcss": "^3.1" + "culori": "^3", + "picocolors": "^1", + "postcss-js": "^4" } }, "data-uri-to-buffer": { @@ -14628,15 +14632,15 @@ } }, "deep-equal": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", - "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, "requires": { "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.2", + "call-bind": "^1.0.5", "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.1", + "get-intrinsic": "^1.2.2", "is-arguments": "^1.1.1", "is-array-buffer": "^3.0.2", "is-date-object": "^1.0.5", @@ -14646,11 +14650,11 @@ "object-is": "^1.1.5", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.0", + "regexp.prototype.flags": "^1.5.1", "side-channel": "^1.0.4", "which-boxed-primitive": "^1.0.2", "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.13" } }, "defaults": { @@ -14768,9 +14772,9 @@ } }, "electron-to-chromium": { - "version": "1.4.566", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.566.tgz", - "integrity": "sha512-mv+fAy27uOmTVlUULy15U3DVJ+jg+8iyKH1bpwboCRhtDC69GKf1PPTZvEIhCyDr81RFqfxZJYrbgp933a1vtg==" + "version": "1.4.586", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.586.tgz", + "integrity": "sha512-qMa+E6yf1fNQbg3G66pHLXeJUP5CCCzNat1VPczOZOqgI2w4u+8y9sQnswMdGs5m4C1rOePq37EVBr/nsPQY7w==" }, "emoji-regex": { "version": "8.0.0", @@ -14953,9 +14957,9 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-glob": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", - "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -15765,9 +15769,9 @@ } }, "jiti": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.20.0.tgz", - "integrity": "sha512-3TV69ZbrvV6U5DfQimop50jE9Dl6J8O1ja1dvBbMba/sZ3YBEQqJ2VZRoQPVnhlzjNtU1vaXRZVrVjU4qtm8yA==" + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==" }, "js-tokens": { "version": "4.0.0", @@ -16604,9 +16608,9 @@ } }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "no-case": { "version": "3.0.4", @@ -16925,9 +16929,9 @@ }, "dependencies": { "yaml": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.3.tgz", - "integrity": "sha512-zw0VAJxgeZ6+++/su5AFoqBbZbrEakwu+X0M5HmcwUiBL7AzcuPKjj5we4xfQLp78LkEMpD0cOnUhmgOVy3KdQ==" + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.4.tgz", + "integrity": "sha512-8aAvwVUSHpfEqTQ4w/KMlf3HcRdt50E5ODIQJBw1fQ5RL34xabzxtUlzTXVqc4rkZsPbvrXKWnABCD7kWSmocA==" } } }, @@ -16972,9 +16976,9 @@ "dev": true }, "prettier-plugin-organize-imports": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.3.tgz", - "integrity": "sha512-KFvk8C/zGyvUaE3RvxN2MhCLwzV6OBbFSkwZ2OamCrs9ZY4i5L77jQ/w4UmUr+lqX8qbaqVq6bZZkApn+IgJSg==", + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz", + "integrity": "sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog==", "dev": true, "requires": {} }, @@ -17042,9 +17046,9 @@ } }, "property-information": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.3.0.tgz", - "integrity": "sha512-gVNZ74nqhRMiIUYWGQdosYetaKc83x8oT41a0LlV3AAFCAZwCpg4vmGkq8t34+cUhp3cnM4XDiU/7xlgK7HGrg==" + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.4.0.tgz", + "integrity": "sha512-9t5qARVofg2xQqKtytzt+lZ4d1Qvj8t5B8fEwXK6qOfgRLgH/b13QlgEyDh033NOS31nXeFbYv7CLUDG1CeifQ==" }, "proxy-from-env": { "version": "1.1.0", @@ -17073,9 +17077,9 @@ } }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" }, "querystringify": { "version": "2.2.0", @@ -17141,9 +17145,9 @@ } }, "react-icons": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.11.0.tgz", - "integrity": "sha512-V+4khzYcE5EBk/BvcuYRq6V/osf11ODUM2J8hg2FDSswRrGvqiYUYPRy4OdrWaQOBj4NcpJfmHZLNaD+VH0TyA==", + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", + "integrity": "sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==", "requires": {} }, "react-is": { @@ -17203,20 +17207,20 @@ } }, "react-router": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", - "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.19.0.tgz", + "integrity": "sha512-0W63PKCZ7+OuQd7Tm+RbkI8kCLmn4GPjDbX61tWljPxWgqTKlEpeQUwPkT1DRjYhF8KSihK0hQpmhU4uxVMcdw==", "requires": { - "@remix-run/router": "1.10.0" + "@remix-run/router": "1.12.0" } }, "react-router-dom": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", - "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "version": "6.19.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.19.0.tgz", + "integrity": "sha512-N6dWlcgL2w0U5HZUUqU2wlmOrSb3ighJmtQ438SWbhB1yuLTXQ8yyTBMK3BSvVjp7gBtKurT554nCtMOgxCZmQ==", "requires": { - "@remix-run/router": "1.10.0", - "react-router": "6.17.0" + "@remix-run/router": "1.12.0", + "react-router": "6.19.0" } }, "react-style-singleton": { @@ -17258,9 +17262,9 @@ } }, "react-tooltip": { - "version": "5.21.6", - "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.21.6.tgz", - "integrity": "sha512-WbND5ee8Kr5HaSuDDiAmSyRp5jH77PSk8M0CUzmVfD+1WST8XOm1StJndK/wOQIP5GPvDVPy96ylLxY/V+VpqA==", + "version": "5.23.0", + "resolved": "https://registry.npmjs.org/react-tooltip/-/react-tooltip-5.23.0.tgz", + "integrity": "sha512-MYqn6n+Af8NHHDL3zrSqzVSoK2LLqTNFp30CuuHCYlBEf+q88FWfg+8pSO+0GnDvOa5ZaryNDq9sAVQeNhnsgw==", "requires": { "@floating-ui/dom": "^1.0.0", "classnames": "^2.3.0" @@ -17284,16 +17288,16 @@ "requires": {} }, "reactflow": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.9.4.tgz", - "integrity": "sha512-IHAKBkJngNvU9y1vZ5Nw9rvA3Z+zc9geTgQQIi9qq9Y9knGLlDDr9KfsjbFMew9AycAAgVg8TvBEakF4IT5lqg==", + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.10.1.tgz", + "integrity": "sha512-Q616fElAc5/N37tMwjuRkkgm/VgmnLLTNNCj61z5mvJxae+/VXZQMfot1K6a5LLz9G3SVKqU97PMb9Ga1PRXew==", "requires": { - "@reactflow/background": "11.3.4", - "@reactflow/controls": "11.2.4", - "@reactflow/core": "11.9.4", - "@reactflow/minimap": "11.7.4", - "@reactflow/node-resizer": "2.2.4", - "@reactflow/node-toolbar": "1.3.4" + "@reactflow/background": "11.3.6", + "@reactflow/controls": "11.2.6", + "@reactflow/core": "11.10.1", + "@reactflow/minimap": "11.7.6", + "@reactflow/node-resizer": "2.2.6", + "@reactflow/node-toolbar": "1.3.6" } }, "read-cache": { @@ -18343,9 +18347,9 @@ } }, "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" }, "update-browserslist-db": { "version": "1.0.13", @@ -18786,9 +18790,9 @@ "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==" }, "zustand": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.4.tgz", - "integrity": "sha512-5UTUIAiHMNf5+mFp7/AnzJXS7+XxktULFN0+D1sCiZWyX7ZG+AQpqs2qpYrynRij4QvoDdCD+U+bmg/cG3Ucxw==", + "version": "4.4.6", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.4.6.tgz", + "integrity": "sha512-Rb16eW55gqL4W2XZpJh0fnrATxYEG3Apl2gfHTyDSE965x/zxslTikpNch0JgNjJA9zK6gEFW8Fl6d1rTZaqgg==", "requires": { "use-sync-external-store": "1.2.0" } diff --git a/src/frontend/package.json b/src/frontend/package.json index 0be0c9260..2d8f4b7ff 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -111,7 +111,7 @@ "@types/uuid": "^9.0.2", "@vitejs/plugin-react-swc": "^3.3.2", "autoprefixer": "^10.4.15", - "daisyui": "^3.6.3", + "daisyui": "^4.0.4", "postcss": "^8.4.29", "prettier": "^2.8.8", "prettier-plugin-organize-imports": "^3.2.3", diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index a4c36787d..0898ad58d 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -42,9 +42,7 @@ export default function App() { successData, successOpen, setSuccessOpen, - setErrorData, loading, - setLoading, } = useContext(alertContext); const navigate = useNavigate(); const { fetchError } = useContext(typesContext); diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index d47b7bcda..dfb1613e9 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -168,21 +168,40 @@ export default function ParameterComponent({ {nodeNames[item.family] ?? "Other"}{" "} - - {" "} - {item.type === "" ? "" : " - "} - {item.type.split(", ").length > 2 - ? item.type.split(", ").map((el, index) => ( - - - {index === item.type.split(", ").length - 1 - ? el - : (el += `, `)} - - - )) - : item.type} - + {item?.display_name && item?.display_name?.length > 0 ? ( + + {" "} + {item.display_name === "" ? "" : " - "} + {item.display_name.split(", ").length > 2 + ? item.display_name.split(", ").map((el, index) => ( + + + {index === + item.display_name.split(", ").length - 1 + ? el + : (el += `, `)} + + + )) + : item.display_name} + + ) : ( + + {" "} + {item.type === "" ? "" : " - "} + {item.type.split(", ").length > 2 + ? item.type.split(", ").map((el, index) => ( + + + {index === item.type.split(", ").length - 1 + ? el + : (el += `, `)} + + + )) + : item.type} + + )} diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index 81d99c8d5..2b6661a83 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -408,7 +408,7 @@ export default function GenericNode({ /> ) : (
setInputDescription(true)} > {data.node?.description} diff --git a/src/frontend/src/components/EditFlowSettingsComponent/index.tsx b/src/frontend/src/components/EditFlowSettingsComponent/index.tsx index fafb0f3d3..ac3932132 100644 --- a/src/frontend/src/components/EditFlowSettingsComponent/index.tsx +++ b/src/frontend/src/components/EditFlowSettingsComponent/index.tsx @@ -12,8 +12,6 @@ export const EditFlowSettings: React.FC = ({ setInvalidName, description, maxLength = 50, - flows, - tabId, setName, setDescription, }: InputProps): JSX.Element => { diff --git a/src/frontend/src/components/PaginatorComponent/index.tsx b/src/frontend/src/components/PaginatorComponent/index.tsx index fa759a8bb..c5ed90f33 100644 --- a/src/frontend/src/components/PaginatorComponent/index.tsx +++ b/src/frontend/src/components/PaginatorComponent/index.tsx @@ -11,11 +11,12 @@ import IconComponent from "../genericIconComponent"; import { Button } from "../ui/button"; export default function PaginatorComponent({ - pageSize = 10, + pageSize = 12, pageIndex = 1, - rowsCount = [10, 20, 50, 100], + rowsCount = [12, 24, 48, 96], totalRowsCount = 0, paginate, + storeComponent = false, }: PaginatorComponentType) { const [size, setPageSize] = useState(pageSize); const [maxIndex, setMaxPageIndex] = useState( @@ -30,7 +31,13 @@ export default function PaginatorComponent({ <>
-
+

Rows per page

{ - setApiKeyValue(event.target.value); - }} - readOnly={true} - value={apiKeyValue} - /> +
@@ -153,7 +133,6 @@ export default function SecretKeyModal({ { - handleInput({ target: { name: "apikeyname", value } }); setApiKeyName(value); }} value={apiKeyName} diff --git a/src/frontend/src/modals/StoreApiKeyModal/index.tsx b/src/frontend/src/modals/StoreApiKeyModal/index.tsx new file mode 100644 index 000000000..ffba35c2d --- /dev/null +++ b/src/frontend/src/modals/StoreApiKeyModal/index.tsx @@ -0,0 +1,124 @@ +import * as Form from "@radix-ui/react-form"; +import { useContext, useState } from "react"; +import IconComponent from "../../components/genericIconComponent"; +import { Button } from "../../components/ui/button"; +import { Input } from "../../components/ui/input"; +import { alertContext } from "../../contexts/alertContext"; +import { AuthContext } from "../../contexts/authContext"; +import { StoreContext } from "../../contexts/storeContext"; +import { addApiKeyStore } from "../../controllers/API"; +import { StoreApiKeyType } from "../../types/components"; +import BaseModal from "../baseModal"; + +export default function StoreApiKeyModal({ + children, + disabled = false, +}: StoreApiKeyType) { + if (disabled) return <>{children}; + const [open, setOpen] = useState(false); + const { setSuccessData, setErrorData } = useContext(alertContext); + const { storeApiKey } = useContext(AuthContext); + const { hasApiKey, validApiKey } = useContext(StoreContext); + const [apiKeyValue, setApiKeyValue] = useState(""); + + const handleSaveKey = () => { + if (apiKeyValue) { + addApiKeyStore(apiKeyValue).then( + () => { + setSuccessData({ + title: "Success! Your API Key has been saved.", + }); + storeApiKey(apiKeyValue); + setOpen(false); + }, + (error) => { + setErrorData({ + title: "There was an error saving the API Key, please try again.", + list: [error["response"]["data"]["detail"]], + }); + } + ); + } + }; + + return ( + + {children} + + API Key + + + { + event.preventDefault(); + }} + > +
+ +
+ + { + setApiKeyValue(value); + }} + placeholder="Insert your API Key" + /> + +
+
+
+
+ + Don’t have an API key? Sign up at{" "} + + langflow.store + + +
+ + + + + +
+
+
+
+
+ ); +} diff --git a/src/frontend/src/modals/baseModal/index.tsx b/src/frontend/src/modals/baseModal/index.tsx index 263f335fd..8e0d58772 100644 --- a/src/frontend/src/modals/baseModal/index.tsx +++ b/src/frontend/src/modals/baseModal/index.tsx @@ -67,7 +67,8 @@ interface BaseModalProps { | "large" | "large-h-full" | "small-h-full" - | "medium-h-full"; + | "medium-h-full" + | "smaller-h-full"; disable?: boolean; onChangeOpenModal?: (open?: boolean) => void; @@ -104,6 +105,10 @@ function BaseModal({ minWidth = "min-w-[40vw]"; height = "h-[27vh]"; break; + case "smaller-h-full": + minWidth = "min-w-[40vw]"; + height = "h-full"; + break; case "small": minWidth = "min-w-[40vw]"; height = "h-[40vh]"; diff --git a/src/frontend/src/modals/exportModal/index.tsx b/src/frontend/src/modals/exportModal/index.tsx index 65d6a30d1..f58a9cac2 100644 --- a/src/frontend/src/modals/exportModal/index.tsx +++ b/src/frontend/src/modals/exportModal/index.tsx @@ -12,7 +12,7 @@ import BaseModal from "../baseModal"; const ExportModal = forwardRef( (props: { children: ReactNode }, ref): JSX.Element => { - const { flows, tabId, downloadFlow } = useContext(FlowsContext); + const { flows, tabId, downloadFlow, version } = useContext(FlowsContext); const { reactFlowInstance } = useContext(typesContext); const { setNoticeData } = useContext(alertContext); const [checked, setChecked] = useState(true); @@ -26,7 +26,7 @@ const ExportModal = forwardRef( const [open, setOpen] = useState(false); return ( - + {props.children} Export @@ -40,8 +40,6 @@ const ExportModal = forwardRef( @@ -57,7 +55,7 @@ const ExportModal = forwardRef( Save with my API keys
- + Caution: Uncheck this box only removes API keys from fields specifically designated for API keys. @@ -73,6 +71,8 @@ const ExportModal = forwardRef( data: reactFlowInstance?.toObject()!, description, name, + last_tested_version: version, + is_component: false, }, name!, description @@ -88,6 +88,8 @@ const ExportModal = forwardRef( data: reactFlowInstance?.toObject()!, description, name, + last_tested_version: version, + is_component: false, }), name!, description diff --git a/src/frontend/src/modals/flowSettingsModal/index.tsx b/src/frontend/src/modals/flowSettingsModal/index.tsx index 1d514adc9..9cd42ad61 100644 --- a/src/frontend/src/modals/flowSettingsModal/index.tsx +++ b/src/frontend/src/modals/flowSettingsModal/index.tsx @@ -40,8 +40,6 @@ export default function FlowSettingsModal({ setInvalidName={setInvalidName} name={name} description={description} - flows={flows} - tabId={tabId} setName={setName} setDescription={setDescription} /> diff --git a/src/frontend/src/modals/shareModal/index.tsx b/src/frontend/src/modals/shareModal/index.tsx new file mode 100644 index 000000000..2f52cf4fd --- /dev/null +++ b/src/frontend/src/modals/shareModal/index.tsx @@ -0,0 +1,177 @@ +import { ReactNode, useContext, useEffect, useRef, useState } from "react"; +import EditFlowSettings from "../../components/EditFlowSettingsComponent"; +import IconComponent from "../../components/genericIconComponent"; +import { TagsSelector } from "../../components/tagsSelectorComponent"; +import { Button } from "../../components/ui/button"; +import { Checkbox } from "../../components/ui/checkbox"; +import { alertContext } from "../../contexts/alertContext"; +import { FlowsContext } from "../../contexts/flowsContext"; +import { getStoreTags, saveFlowStore } from "../../controllers/API"; +import { FlowType } from "../../types/flow"; +import { removeApiKeys } from "../../utils/reactflowUtils"; +import { getTagsIds } from "../../utils/storeUtils"; +import BaseModal from "../baseModal"; + +export default function ShareModal({ + component, + is_component, + children, + open, + setOpen, +}: { + children?: ReactNode; + is_component: boolean; + component: FlowType; + open?: boolean; + setOpen?: (open: boolean) => void; +}): JSX.Element { + const { version, addFlow } = useContext(FlowsContext); + const { setSuccessData, setErrorData } = useContext(alertContext); + const [checked, setChecked] = useState(true); + const [name, setName] = useState(component?.name ?? ""); + const [description, setDescription] = useState(component?.description ?? ""); + const [internalOpen, internalSetOpen] = useState(children ? false : true); + + const nameComponent = is_component ? "Component" : "Flow"; + + const [tags, setTags] = useState<{ id: string; name: string }[]>([]); + const [loadingTags, setLoadingTags] = useState(false); + const [sharePublic, setSharePublic] = useState(true); + const [selectedTags, setSelectedTags] = useState([]); + const tagListId = useRef<{ id: string; name: string }[]>([]); + + useEffect(() => { + handleGetTags(); + }, []); + + function handleGetTags() { + setLoadingTags(true); + getStoreTags().then((res) => { + setTags(res); + setLoadingTags(false); + }); + } + + useEffect(() => { + setName(component?.name ?? ""); + setDescription(component?.description ?? ""); + }, [component]); + + const handleShareComponent = () => { + const saveFlow: FlowType = checked + ? { + id: component!.id, + data: component!.data, + description, + name, + last_tested_version: version, + is_component: is_component, + } + : removeApiKeys({ + id: component!.id, + data: component!.data, + description, + name, + last_tested_version: version, + is_component: is_component, + }); + saveFlowStore( + saveFlow, + getTagsIds(selectedTags, tagListId), + sharePublic + ).then( + () => { + if (is_component) { + addFlow(true, saveFlow); + } + setSuccessData({ + title: `${nameComponent} shared successfully`, + }); + }, + (err) => { + setErrorData({ + title: "Error sharing " + is_component ? "component" : "flow", + list: [err["response"]["data"]["detail"]], + }); + } + ); + }; + + return ( + + {children ? children : <>} + + Share + + + +
+ +
+
+ { + setSharePublic(event); + }} + /> + +
+
+ { + setChecked(event); + }} + /> + +
+ + Caution: Uncheck this box only removes API keys from fields + specifically designated for API keys. + +
+ + + + +
+ ); +} diff --git a/src/frontend/src/pages/AdminPage/index.tsx b/src/frontend/src/pages/AdminPage/index.tsx index f655b1e2c..39a36544c 100644 --- a/src/frontend/src/pages/AdminPage/index.tsx +++ b/src/frontend/src/pages/AdminPage/index.tsx @@ -312,7 +312,6 @@ export default function AdminPage() { title="Edit" titleHeader={`${user.username}`} modalContentTitle="Attention!" - modalContent="Are you completely confident about the changes you are making to this user?" cancelText="Cancel" confirmationText="Confirm" icon={"UserCog2"} @@ -326,12 +325,20 @@ export default function AdminPage() { ); }} > -
- -
+ + + Are you completely confident about the changes + you are making to this user? + + + +
+ +
+
@@ -340,7 +347,6 @@ export default function AdminPage() { title="Edit" titleHeader={`${user.username}`} modalContentTitle="Attention!" - modalContent="Are you completely confident about the changes you are making to this user?" cancelText="Cancel" confirmationText="Confirm" icon={"UserCog2"} @@ -354,12 +360,20 @@ export default function AdminPage() { ); }} > -
- -
+ + + Are you completely confident about the changes + you are making to this user? + + + +
+ +
+
@@ -402,7 +416,6 @@ export default function AdminPage() { title="Delete" titleHeader="Delete User" modalContentTitle="Attention!" - modalContent="Are you sure you want to delete this user? This action cannot be undone." cancelText="Cancel" confirmationText="Delete" icon={"UserMinus2"} @@ -412,12 +425,20 @@ export default function AdminPage() { handleDeleteUser(user); }} > - - - + + + Are you sure you want to delete this user? + This action cannot be undone. + + + + + + +
diff --git a/src/frontend/src/pages/ApiKeysPage/index.tsx b/src/frontend/src/pages/ApiKeysPage/index.tsx index d1c91593a..01aff0f36 100644 --- a/src/frontend/src/pages/ApiKeysPage/index.tsx +++ b/src/frontend/src/pages/ApiKeysPage/index.tsx @@ -224,7 +224,6 @@ export default function ApiKeysPage() { title="Delete" titleHeader="Delete User" modalContentTitle="Attention!" - modalContent="Are you sure you want to delete this key? This action cannot be undone." cancelText="Cancel" confirmationText="Delete" icon={"UserMinus2"} @@ -234,15 +233,19 @@ export default function ApiKeysPage() { handleDeleteKey(keys); }} > - + + + Are you sure you want to delete + this key? This action cannot be + undone. + + + - +
diff --git a/src/frontend/src/pages/CommunityPage/index.tsx b/src/frontend/src/pages/CommunityPage/index.tsx deleted file mode 100644 index bd1d7a21f..000000000 --- a/src/frontend/src/pages/CommunityPage/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useContext, useEffect, useState } from "react"; -import { Button } from "../../components/ui/button"; -import { alertContext } from "../../contexts/alertContext"; -import { FlowsContext } from "../../contexts/flowsContext"; - -import { useNavigate } from "react-router-dom"; -import { CardComponent } from "../../components/cardComponent"; -import IconComponent from "../../components/genericIconComponent"; -import Header from "../../components/headerComponent"; -import { SkeletonCardComponent } from "../../components/skeletonCardComponent"; -import { getExamples } from "../../controllers/API"; -import { FlowType } from "../../types/flow"; -export default function CommunityPage(): JSX.Element { - const { flows, setTabId, downloadFlows, uploadFlows, addFlow } = - useContext(FlowsContext); - - // set null id - useEffect(() => { - setTabId(""); - }, []); - const { setErrorData } = useContext(alertContext); - const [loadingExamples, setLoadingExamples] = useState(false); - const [examples, setExamples] = useState([]); - - // Show community examples on screen - function handleExamples(): void { - setLoadingExamples(true); - getExamples() - .then((result) => { - setLoadingExamples(false); - setExamples(result); - }) - .catch((error) => - setErrorData({ - title: "there was an error loading examples, please try again", - list: [error.message], - }) - ); - } - const navigate = useNavigate(); - - // Show community examples on page start - useEffect(() => { - handleExamples(); - }, []); - return ( - <> -
- -
-
- - - Community Examples - - -
- - Discover and learn from shared examples by the Langflow community. We - welcome new example contributions that can help our community explore - new and powerful features. - -
- {loadingExamples ? ( - <> - - - - - - ) : ( - examples.map((flow, idx) => ( - { - addFlow(true, flow).then((id) => { - navigate("/flow/" + id); - }); - }} - > - - Fork Example - - } - /> - )) - )} -
-
- - ); -} diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 37bd43b58..35e1d78bd 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -1,5 +1,5 @@ import { cloneDeep } from "lodash"; -import { useContext, useEffect, useState } from "react"; +import { useContext, useEffect, useMemo, useState } from "react"; import ShadTooltip from "../../../../components/ShadTooltipComponent"; import IconComponent from "../../../../components/genericIconComponent"; import { Input } from "../../../../components/ui/input"; @@ -9,21 +9,27 @@ import { FlowsContext } from "../../../../contexts/flowsContext"; import { typesContext } from "../../../../contexts/typesContext"; import ApiModal from "../../../../modals/ApiModal"; import ExportModal from "../../../../modals/exportModal"; +import ShareModal from "../../../../modals/shareModal"; import { APIClassType, APIObjectType } from "../../../../types/api"; import { nodeColors, nodeIconsLucide, nodeNames, } from "../../../../utils/styleUtils"; -import { classNames } from "../../../../utils/utils"; +import { + classNames, + removeCountFromString, + sensitiveSort, +} from "../../../../utils/utils"; import DisclosureComponent from "../DisclosureComponent"; +import SidebarDraggableComponent from "./sideBarDraggableComponent"; export default function ExtraSidebar(): JSX.Element { - const { data, templates, getFilterEdge, setFilterEdge } = + const { data, templates, getFilterEdge, setFilterEdge, reactFlowInstance } = useContext(typesContext); - const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt } = + const { flows, tabId, uploadFlow, tabsState, saveFlow, isBuilt, version } = useContext(FlowsContext); - const { setSuccessData, setErrorData } = useContext(alertContext); + const { setErrorData } = useContext(alertContext); const [dataFilter, setFilterData] = useState(data); const [search, setSearch] = useState(""); const isPending = tabsState[tabId]?.isPending; @@ -52,8 +58,10 @@ export default function ExtraSidebar(): JSX.Element { let ret = {}; Object.keys(data).forEach((d: keyof APIObjectType, i) => { ret[d] = {}; - let keys = Object.keys(data[d]).filter((nd) => - nd.toLowerCase().includes(e.toLowerCase()) + let keys = Object.keys(data[d]).filter( + (nd) => + nd.toLowerCase().includes(e.toLowerCase()) || + data[d][nd].display_name?.toLowerCase().includes(e.toLowerCase()) ); keys.forEach((element) => { ret[d][element] = data[d][element]; @@ -76,7 +84,8 @@ export default function ExtraSidebar(): JSX.Element { }, []); function handleBlur() { - if (!search && search === "") { + // check if search is search to reset fitler on click input + if ((!search && search === "") || search === "search") { setFilterData(data); setFilterEdge([]); setSearch(""); @@ -91,6 +100,10 @@ export default function ExtraSidebar(): JSX.Element { } }, [getFilterEdge]); + useEffect(() => { + handleSearchInput(search); + }, [data]); + useEffect(() => { if (getFilterEdge?.length > 0) { setFilterData((_) => { @@ -128,6 +141,69 @@ export default function ExtraSidebar(): JSX.Element { } }, [getFilterEdge]); + useEffect(() => { + if (getFilterEdge?.length > 0) { + setFilterData((_) => { + let dataClone = cloneDeep(data); + let ret = {}; + Object.keys(dataClone).forEach((d: keyof APIObjectType, i) => { + ret[d] = {}; + if (getFilterEdge.some((x) => x.family === d)) { + ret[d] = dataClone[d]; + + const filtered = getFilterEdge + .filter((x) => x.family === d) + .pop() + .type.split(","); + + for (let i = 0; i < filtered.length; i++) { + filtered[i] = filtered[i].trimStart(); + } + + if (filtered.some((x) => x !== "")) { + let keys = Object.keys(dataClone[d]).filter((nd) => + filtered.includes(nd) + ); + Object.keys(dataClone[d]).forEach((element) => { + if (!keys.includes(element)) { + delete ret[d][element]; + } + }); + } + } + }); + setSearch(""); + return ret; + }); + } + }, [getFilterEdge, data]); + + const ModalMemo = useMemo( + () => ( + + +
+ +
+
+
+ ), + [] + ); + + const ExportMemo = useMemo( + () => ( + + +
+ +
+
+
+ ), + [] + ); + return (
@@ -143,18 +219,7 @@ export default function ExtraSidebar(): JSX.Element {
-
- - -
- -
-
-
-
+
{ExportMemo}
{flow && flow.data && ( @@ -178,24 +243,29 @@ export default function ExtraSidebar(): JSX.Element {
- + onClick={(event) => { + saveFlow(flow!); + }} + > + + +
+ +
{ModalMemo}
@@ -241,57 +311,45 @@ export default function ExtraSidebar(): JSX.Element { >
{Object.keys(dataFilter[SBSectionName]) - .sort() + .sort((a, b) => + sensitiveSort( + dataFilter[SBSectionName][a].display_name, + dataFilter[SBSectionName][b].display_name + ) + ) .map((SBItemName: string, index) => ( -
-
- onDragStart(event, { - type: SBItemName, - node: data[SBSectionName][SBItemName], - }) - } - onDragEnd={() => { - document.body.removeChild( - document.getElementsByClassName( - "cursor-grabbing" - )[0] - ); - }} - > -
- - {data[SBSectionName][SBItemName].display_name} - - -
-
-
+ + onDragStart(event, { + //split type to remove type in nodes saved with same name removing it's + type: removeCountFromString(SBItemName), + node: dataFilter[SBSectionName][SBItemName], + }) + } + color={nodeColors[SBSectionName]} + itemName={SBItemName} + //convert error to boolean + error={!!dataFilter[SBSectionName][SBItemName].error} + display_name={ + dataFilter[SBSectionName][SBItemName].display_name + } + official={ + dataFilter[SBSectionName][SBItemName].official === + false + ? false + : true + } + />
))}
diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx new file mode 100644 index 000000000..09ac317a0 --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/sideBarDraggableComponent/index.tsx @@ -0,0 +1,133 @@ +import { DragEventHandler, useContext, useRef } from "react"; +import IconComponent from "../../../../../components/genericIconComponent"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, +} from "../../../../../components/ui/select-custom"; +import { AuthContext } from "../../../../../contexts/authContext"; +import { FlowsContext } from "../../../../../contexts/flowsContext"; +import { APIClassType } from "../../../../../types/api"; +import { + createFlowComponent, + downloadNode, +} from "../../../../../utils/reactflowUtils"; +import { removeCountFromString } from "../../../../../utils/utils"; + +export default function SidebarDraggableComponent({ + sectionName, + display_name, + itemName, + error, + color, + onDragStart, + apiClass, + official, +}: { + sectionName: string; + apiClass: APIClassType; + display_name: string; + itemName: string; + error: boolean; + color: string; + onDragStart: DragEventHandler; + official: boolean; +}) { + const open = useRef(false); + const { getNodeId, deleteComponent, version } = useContext(FlowsContext); + const { autoLogin, userData } = useContext(AuthContext); + + function handleSelectChange(value: string) { + switch (value) { + case "share": + break; + case "download": + const type = removeCountFromString(itemName); + downloadNode( + createFlowComponent( + { id: getNodeId(type), type, node: apiClass }, + version + ) + ); + break; + case "delete": + deleteComponent( + autoLogin ? "auto" : userData?.id!, + removeCountFromString(itemName) + ); + break; + } + } + + return ( + + ); +} diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 47c213145..0686ffe7c 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -1,4 +1,5 @@ -import { useContext, useState } from "react"; +import { cloneDeep } from "lodash"; +import { useContext, useEffect, useState } from "react"; import { useReactFlow, useUpdateNodeInternals } from "reactflow"; import ShadTooltip from "../../../../components/ShadTooltipComponent"; import IconComponent from "../../../../components/genericIconComponent"; @@ -10,12 +11,16 @@ import { } from "../../../../components/ui/select-custom"; import { FlowsContext } from "../../../../contexts/flowsContext"; import EditNodeModal from "../../../../modals/EditNodeModal"; +import ShareModal from "../../../../modals/shareModal"; import { nodeToolbarPropsType } from "../../../../types/components"; +import { FlowType } from "../../../../types/flow"; import { + createFlowComponent, + downloadNode, expandGroupNode, updateFlowPosition, } from "../../../../utils/reactflowUtils"; -import { classNames, getRandomKeyByssmm } from "../../../../utils/utils"; +import { classNames } from "../../../../utils/utils"; export default function NodeToolbarComponent({ data, @@ -38,10 +43,13 @@ export default function NodeToolbarComponent({ data.node.template[templateField].type === "prompt" || data.node.template[templateField].type === "file" || data.node.template[templateField].type === "Any" || - data.node.template[templateField].type === "int") + data.node.template[templateField].type === "int" || + data.node.template[templateField].type === "dict" || + data.node.template[templateField].type === "NestedDict") ).length ); const updateNodeInternals = useUpdateNodeInternals(); + const { getNodeId } = useContext(FlowsContext); function canMinimize() { let countHandles: number = 0; @@ -54,27 +62,42 @@ export default function NodeToolbarComponent({ const isMinimal = canMinimize(); const isGroup = data.node?.flow ? true : false; - const { paste } = useContext(FlowsContext); + const { paste, saveComponent, version } = useContext(FlowsContext); const reactFlowInstance = useReactFlow(); const [showModalAdvanced, setShowModalAdvanced] = useState(false); + const [showconfirmShare, setShowconfirmShare] = useState(false); const [selectedValue, setSelectedValue] = useState(""); + const [flowComponent, setFlowComponent] = useState(); + + useEffect(() => { + setFlowComponent(createFlowComponent(cloneDeep(data), version)); + }, [data]); + const handleSelectChange = (event) => { - setSelectedValue(event); - if (event.includes("advanced")) { - return setShowModalAdvanced(true); - } - setShowModalAdvanced(false); - if (event.includes("show")) { - setShowNode((prev) => !prev); - updateNodeInternals(data.id); - } - if (event.includes("disabled")) { - return; - } - if (event.includes("ungroup")) { - updateFlowPosition(position, data.node?.flow!); - expandGroupNode(data, reactFlowInstance); + switch (event) { + case "advanced": + setShowModalAdvanced(true); + break; + case "show": + setShowNode((prev) => !prev); + updateNodeInternals(data.id); + break; + case "Download": + downloadNode(createFlowComponent(cloneDeep(data), version)); + break; + case "Share": + setShowconfirmShare(true); + break; + case "SaveAll": + saveComponent(cloneDeep(data)); + break; + case "disabled": + break; + case "ungroup": + updateFlowPosition(position, data.node?.flow!); + expandGroupNode(data, reactFlowInstance, getNodeId); + break; } }; @@ -145,43 +168,30 @@ export default function NodeToolbarComponent({ - {isMinimal || isGroup ? ( - + + +
+ +
+
+
+
+ + {nodeLength > 0 && ( + +
{" "} - {isMinimal && ( - -
- - {showNode ? "Minimize" : "Expand"} -
-
- )} - {isGroup && ( - -
- {" "} - Ungroup{" "} -
-
- )} - - - ) : ( - -
- -
-
- )} + )} - {showModalAdvanced && ( - { - setShowModalAdvanced(modal); - }} - > - <> - - )} + +
+ {" "} + Save{" "} +
{" "} +
+ +
+ {" "} + Share{" "} +
{" "} +
+ +
+ {" "} + Download{" "} +
{" "} +
+ {isMinimal && ( + +
+ + {showNode ? "Minimize" : "Expand"} +
+
+ )} + {isGroup && ( + +
+ {" "} + Ungroup{" "} +
+
+ )} + + + +
diff --git a/src/frontend/src/pages/FlowPage/index.tsx b/src/frontend/src/pages/FlowPage/index.tsx index 83fc4145d..9c11f66da 100644 --- a/src/frontend/src/pages/FlowPage/index.tsx +++ b/src/frontend/src/pages/FlowPage/index.tsx @@ -1,12 +1,11 @@ -import { useContext, useEffect, useState } from "react"; +import { useContext, useEffect } from "react"; import { useParams } from "react-router-dom"; import Header from "../../components/headerComponent"; import { FlowsContext } from "../../contexts/flowsContext"; -import { getVersion } from "../../controllers/API"; import Page from "./components/PageComponent"; export default function FlowPage(): JSX.Element { - const { flows, tabId, setTabId } = useContext(FlowsContext); + const { flows, tabId, setTabId, version } = useContext(FlowsContext); const { id } = useParams(); // Set flow tab id @@ -14,14 +13,6 @@ export default function FlowPage(): JSX.Element { setTabId(id!); }, [id]); - // Initialize state variable for the version - const [version, setVersion] = useState(""); - useEffect(() => { - getVersion().then((data) => { - setVersion(data.version); - }); - }, []); - return ( <>
diff --git a/src/frontend/src/pages/MainPage/components/components/index.tsx b/src/frontend/src/pages/MainPage/components/components/index.tsx new file mode 100644 index 000000000..d1b21bbd9 --- /dev/null +++ b/src/frontend/src/pages/MainPage/components/components/index.tsx @@ -0,0 +1,129 @@ +import { useContext, useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import PaginatorComponent from "../../../../components/PaginatorComponent"; +import CollectionCardComponent from "../../../../components/cardComponent"; +import CardsWrapComponent from "../../../../components/cardsWrapComponent"; +import IconComponent from "../../../../components/genericIconComponent"; +import { SkeletonCardComponent } from "../../../../components/skeletonCardComponent"; +import { Button } from "../../../../components/ui/button"; +import { alertContext } from "../../../../contexts/alertContext"; +import { FlowsContext } from "../../../../contexts/flowsContext"; +import { FlowType } from "../../../../types/flow"; + +export default function ComponentsComponent({ + is_component = true, +}: { + is_component?: boolean; +}) { + const { flows, removeFlow, uploadFlow, isLoading } = useContext(FlowsContext); + const { setErrorData } = useContext(alertContext); + const [pageSize, setPageSize] = useState(10); + const [pageIndex, setPageIndex] = useState(1); + const [allData, setAllData] = useState( + flows.filter((f) => f.is_component === is_component) + ); + + const navigate = useNavigate(); + + useEffect(() => { + setAllData(flows.filter((f) => f.is_component === is_component)); + }, [flows]); + + useEffect(() => { + const start = (pageIndex - 1) * pageSize; + const end = start + pageSize; + setData(allData.slice(start, end)); + }, [pageIndex, pageSize, allData]); + + const [data, setData] = useState([]); + + const name = is_component ? "Component" : "Flow"; + + const onFileDrop = (e) => { + e.preventDefault(); + if (e.dataTransfer.types.some((types) => types === "Files")) { + if (e.dataTransfer.files.item(0).type === "application/json") { + uploadFlow(true, e.dataTransfer.files.item(0)!); + } else { + setErrorData({ + title: "Invalid file type", + list: ["Please upload a JSON file"], + }); + } + } + }; + + return ( + +
+
+
+ {!isLoading || data?.length > 0 ? ( + data + ?.sort( + (a, b) => + new Date(b?.date_created!).getTime() - + new Date(a?.date_created!).getTime() + ) + .map((item, idx) => ( + { + removeFlow(item.id); + }} + key={idx} + data={item} + disabled={isLoading} + button={ + !is_component ? ( + + ) : ( + <> + ) + } + /> + )) + ) : !isLoading && data?.length === 0 ? ( + <>You haven't created {name}s yet. + ) : ( + <> + + + + )} +
+
+ {!isLoading && allData.length > 0 && ( +
+ { + setPageIndex(pageIndex); + setPageSize(pageSize); + }} + > +
+ )} +
+
+ ); +} diff --git a/src/frontend/src/pages/MainPage/index.tsx b/src/frontend/src/pages/MainPage/index.tsx index fed704d36..3421f2017 100644 --- a/src/frontend/src/pages/MainPage/index.tsx +++ b/src/frontend/src/pages/MainPage/index.tsx @@ -1,10 +1,9 @@ -import { useContext, useEffect, useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; +import { useContext, useEffect } from "react"; +import { Outlet, useNavigate } from "react-router-dom"; import DropdownButton from "../../components/DropdownButtonComponent"; -import { CardComponent } from "../../components/cardComponent"; import IconComponent from "../../components/genericIconComponent"; -import Header from "../../components/headerComponent"; -import { SkeletonCardComponent } from "../../components/skeletonCardComponent"; +import PageLayout from "../../components/pageLayout"; +import SidebarNav from "../../components/sidebarComponent"; import { Button } from "../../components/ui/button"; import { USER_PROJECTS_HEADER } from "../../constants/constants"; import { alertContext } from "../../contexts/alertContext"; @@ -24,10 +23,28 @@ export default function HomePage(): JSX.Element { const dropdownOptions = [ { name: "Import from JSON", - onBtnClick: () => - uploadFlow(true).then((id) => { - navigate("/flow/" + id); - }), + onBtnClick: () => { + try { + uploadFlow(true).then((id) => { + navigate("/flow/" + id); + }); + } catch (error: any) { + setErrorData({ + title: "Invalid file type", + list: [error], + }); + } + }, + }, + ]; + const sidebarNavItems = [ + { + title: "Flows", + href: "/flows", + }, + { + title: "Components", + href: "/components", }, ]; @@ -37,144 +54,51 @@ export default function HomePage(): JSX.Element { }, []); const navigate = useNavigate(); - const [isDragging, setIsDragging] = useState(false); - - const dragOver = (e) => { - e.preventDefault(); - if (e.dataTransfer.types.some((types) => types === "Files")) { - setIsDragging(true); - } - }; - - const dragEnter = (e) => { - if (e.dataTransfer.types.some((types) => types === "Files")) { - setIsDragging(true); - } - e.preventDefault(); - }; - - const dragLeave = () => { - setIsDragging(false); - }; - - const fileDrop = (e) => { - e.preventDefault(); - setIsDragging(false); - if (e.dataTransfer.types.some((types) => types === "Files")) { - if (e.dataTransfer.files.item(0).type === "application/json") { - uploadFlow(true, e.dataTransfer.files.item(0)!); - } else { - setErrorData({ - title: "Invalid file type", - list: ["Please upload a JSON file"], - }); - } - } - }; - // Personal flows display return ( - <> -
-
-
- - - {USER_PROJECTS_HEADER} - -
- - - { - addFlow(true).then((id) => { - navigate("/flow/" + id); - }); - }} - options={dropdownOptions} - /> -
+ + + + { + addFlow(true).then((id) => { + navigate("/flow/" + id); + }); + }} + options={dropdownOptions} + />
- - Manage your personal projects. Download or upload your collection. - -
- {isDragging ? ( - <> - - Drop your flow here - - ) : ( -
- {isLoading && flows.length == 0 ? ( - <> - - - - - - ) : ( - flows.map((flow, idx) => ( - - - - } - onDelete={() => { - removeFlow(flow.id); - }} - /> - )) - )} -
- )} + } + > +
+ +
+
- + ); } diff --git a/src/frontend/src/pages/StorePage/index.tsx b/src/frontend/src/pages/StorePage/index.tsx new file mode 100644 index 000000000..56dfda709 --- /dev/null +++ b/src/frontend/src/pages/StorePage/index.tsx @@ -0,0 +1,369 @@ +import { uniqueId } from "lodash"; +import { useContext, useEffect, useState } from "react"; +import PaginatorComponent from "../../components/PaginatorComponent"; +import ShadTooltip from "../../components/ShadTooltipComponent"; +import CollectionCardComponent from "../../components/cardComponent"; +import IconComponent from "../../components/genericIconComponent"; +import PageLayout from "../../components/pageLayout"; +import { SkeletonCardComponent } from "../../components/skeletonCardComponent"; +import { Button } from "../../components/ui/button"; +import { Input } from "../../components/ui/input"; + +import { TagsSelector } from "../../components/tagsSelectorComponent"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "../../components/ui/select"; +import { alertContext } from "../../contexts/alertContext"; +import { AuthContext } from "../../contexts/authContext"; +import { StoreContext } from "../../contexts/storeContext"; +import { getStoreComponents, getStoreTags } from "../../controllers/API"; +import StoreApiKeyModal from "../../modals/StoreApiKeyModal"; +import { storeComponent } from "../../types/store"; +import { cn } from "../../utils/utils"; +export default function StorePage(): JSX.Element { + const { validApiKey, setValidApiKey, hasApiKey, loadingApiKey } = + useContext(StoreContext); + const { apiKey } = useContext(AuthContext); + const { setErrorData } = useContext(alertContext); + const [loading, setLoading] = useState(true); + const [loadingTags, setLoadingTags] = useState(true); + const [filteredCategories, setFilterCategories] = useState([]); + const [inputText, setInputText] = useState(""); + const [searchData, setSearchData] = useState([]); + const [totalRowsCount, setTotalRowsCount] = useState(0); + const [pageSize, setPageSize] = useState(12); + const [pageIndex, setPageIndex] = useState(1); + const [pageOrder, setPageOrder] = useState("Popular"); + const [tags, setTags] = useState<{ id: string; name: string }[]>([]); + const [tabActive, setTabActive] = useState("All"); + const [searchNow, setSearchNow] = useState(""); + const [selectFilter, setSelectFilter] = useState("all"); + + useEffect(() => { + handleGetTags(); + }, []); + + useEffect(() => { + if (!loadingApiKey) { + if (!hasApiKey) { + setErrorData({ + title: "API Key Error", + list: [ + "You don't have an API Key. Please add one to use the Langflow Store.", + ], + }); + setLoading(false); + } else if (!validApiKey) { + setErrorData({ + title: "API Key Error", + list: [ + "Your API Key is not valid. Please add a valid API Key to use the Langflow Store.", + ], + }); + } + } + }, [loadingApiKey, validApiKey, hasApiKey]); + + useEffect(() => { + handleGetComponents(); + }, [ + tabActive, + pageOrder, + pageIndex, + pageSize, + filteredCategories, + selectFilter, + validApiKey, + hasApiKey, + apiKey, + searchNow, + loadingApiKey, + ]); + + function handleGetTags() { + setLoadingTags(true); + getStoreTags() + .then((res) => { + setTags(res); + setLoadingTags(false); + }) + .catch((err) => { + console.log(err); + setLoadingTags(false); + }); + } + + function handleGetComponents() { + if (!hasApiKey || loadingApiKey) return; + setLoading(true); + getStoreComponents({ + page: pageIndex, + limit: pageSize, + is_component: + tabActive === "All" ? null : tabActive === "Flows" ? false : true, + sort: pageOrder === "Popular" ? "-count(downloads)" : "name", + tags: filteredCategories, + liked: selectFilter === "likedbyme" && validApiKey ? true : null, + status: null, + search: inputText === "" ? null : inputText, + filterByUser: selectFilter === "createdbyme" && validApiKey ? true : null, + }) + .then((res) => { + if (!res?.authorized && validApiKey === true) { + setValidApiKey(false); + setSelectFilter("all"); + } else { + if (res?.authorized) { + setValidApiKey(true); + } + setLoading(false); + setSearchData(res?.results ?? []); + setTotalRowsCount( + filteredCategories?.length === 0 + ? Number(res?.count ?? 0) + : res?.results?.length ?? 0 + ); + } + }) + .catch((err) => { + if (err.response.status === 403 || err.response.status === 401) { + setValidApiKey(false); + } else { + setSearchData([]); + setTotalRowsCount(0); + setLoading(false); + setErrorData({ + title: "Error getting components.", + list: [err["response"]["data"]["detail"]], + }); + } + }); + } + + return ( + + {StoreApiKeyModal && ( + + + + )} + + } + > +
+
+
+
+ { + setInputText(e.target.value); + }} + onKeyDown={(e) => { + if (e.key === "Enter") { + setSearchNow(uniqueId()); + } + }} + value={inputText} + /> + +
+
+ + + + + + +
+
+ +
+ + +
+
+ + {(!loading || searchData.length !== 0) && ( + <> + {totalRowsCount} {totalRowsCount !== 1 ? "results" : "result"} + + )} + + + +
+ +
+ {!loading || searchData.length !== 0 ? ( + searchData.map((item) => { + return ( + <> + + + ); + }) + ) : ( + <> + + + + + )} +
+ + {!loading && searchData?.length === 0 && ( +
+
+
+
+ {selectFilter != "all" ? ( + <> + You haven't{" "} + {selectFilter === "createdbyme" ? "created" : "liked"}{" "} + anything with the selected filters yet. + + ) : ( + <> + There are no{" "} + {tabActive == "Flows" ? "Flows" : "Components"} with the + selected filters. + + )} +
+
+
+
+ )} +
+ {!loading && searchData.length > 0 && ( +
+ { + setPageIndex(pageIndex); + setPageSize(pageSize); + }} + > +
+ )} +
+
+ ); +} diff --git a/src/frontend/src/pages/ViewPage/index.tsx b/src/frontend/src/pages/ViewPage/index.tsx index ec64001dd..2f1af3049 100644 --- a/src/frontend/src/pages/ViewPage/index.tsx +++ b/src/frontend/src/pages/ViewPage/index.tsx @@ -1,7 +1,6 @@ -import { useContext, useEffect, useState } from "react"; +import { useContext, useEffect } from "react"; import { useParams } from "react-router-dom"; import { FlowsContext } from "../../contexts/flowsContext"; -import { getVersion } from "../../controllers/API"; import Page from "../FlowPage/components/PageComponent"; export default function ViewPage() { @@ -13,14 +12,6 @@ export default function ViewPage() { setTabId(id!); }, [id]); - // Initialize state variable for the version - const [version, setVersion] = useState(""); - useEffect(() => { - getVersion().then((data) => { - setVersion(data.version); - }); - }, []); - return (
{flows.length > 0 && diff --git a/src/frontend/src/routes.tsx b/src/frontend/src/routes.tsx index e9e6f1858..50e2da164 100644 --- a/src/frontend/src/routes.tsx +++ b/src/frontend/src/routes.tsx @@ -1,21 +1,31 @@ -import { Route, Routes } from "react-router-dom"; +import { useEffect } from "react"; +import { Route, Routes, useNavigate } from "react-router-dom"; import { ProtectedAdminRoute } from "./components/authAdminGuard"; import { ProtectedRoute } from "./components/authGuard"; import { ProtectedLoginRoute } from "./components/authLoginGuard"; import { CatchAllRoute } from "./components/catchAllRoutes"; +import { StoreGuard } from "./components/storeGuard"; import AdminPage from "./pages/AdminPage"; import LoginAdminPage from "./pages/AdminPage/LoginPage"; import ApiKeysPage from "./pages/ApiKeysPage"; -import CommunityPage from "./pages/CommunityPage"; import FlowPage from "./pages/FlowPage"; import HomePage from "./pages/MainPage"; +import ComponentsComponent from "./pages/MainPage/components/components"; import ProfileSettingsPage from "./pages/ProfileSettingsPage"; +import StorePage from "./pages/StorePage"; import ViewPage from "./pages/ViewPage"; import DeleteAccountPage from "./pages/deleteAccountPage"; import LoginPage from "./pages/loginPage"; import SignUp from "./pages/signUpPage"; const Router = () => { + const navigate = useNavigate(); + useEffect(() => { + // Redirect from root to /flows + if (window.location.pathname === "/") { + navigate("/flows"); + } + }, [navigate]); return ( { } - /> + > + } + /> + } + /> + - + + + } /> + @@ -120,3 +121,10 @@ export type Users = { create_at: Date; updated_at: Date; }; + +export type Component = { + name: string; + description: string; + data: Object; + tags: [string]; +}; diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 7130a28ec..4c96c91e9 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -216,14 +216,13 @@ export type IconComponentProps = { className?: string; iconColor?: string; onClick?: () => void; + stroke?: string; }; export type InputProps = { name: string | null; description: string | null; maxLength?: number; - flows: Array<{ id: string; name: string; description: string }>; - tabId: string; invalidName?: boolean; setName: (name: string) => void; setDescription: (description: string) => void; @@ -246,8 +245,15 @@ export type LoadingComponentProps = { remSize: number; }; -export type ContentProps = { children: ReactNode }; +export type ContentProps = { + children: ReactNode; + tolltipContent?: ReactNode; + side?: "top" | "right" | "bottom" | "left"; +}; export type HeaderProps = { children: ReactNode; description: string }; +export type TriggerProps = { + children: ReactNode; +}; export interface languageMap { [key: string]: string | undefined; @@ -261,7 +267,7 @@ export type signUpInputStateType = { export type inputHandlerEventType = { target: { - value: string | boolean; + value: string; name: string; }; }; @@ -271,21 +277,35 @@ export type PaginatorComponentType = { rowsCount?: number[]; totalRowsCount: number; paginate: (pageIndex: number, pageSize: number) => void; + storeComponent?: boolean; }; export type ConfirmationModalType = { title: string; titleHeader: string; asChild?: boolean; - modalContent: string; modalContentTitle: string; cancelText: string; confirmationText: string; - children: ReactElement; + children: [ + React.ReactElement, + React.ReactElement + ]; icon: string; - data: any; + data?: any; index: number; onConfirm: (index, data) => void; + open?: boolean; + onClose?: (close: boolean) => void; + size?: + | "x-small" + | "smaller" + | "small" + | "medium" + | "large" + | "large-h-full" + | "small-h-full" + | "medium-h-full"; }; export type UserManagementType = { @@ -332,14 +352,20 @@ export type ApiKeyType = { onCloseModal: () => void; }; -export type ApiKeyInputType = { - apikeyname: string; +export type StoreApiKeyType = { + children: ReactElement; + disabled?: boolean; }; export type groupedObjType = { family: string; type: string; }; +export type nodeGroupedObjType = { + displayName: string; + node: string[] | string; +}; + type test = { [char: string]: string; }; @@ -526,8 +552,7 @@ export type groupDataType = { }; export type cardComponentPropsType = { - flow: FlowType; - id: string; + data: FlowType; onDelete?: () => void; button?: JSX.Element; }; diff --git a/src/frontend/src/types/contexts/auth.ts b/src/frontend/src/types/contexts/auth.ts index 4946efa44..f062181d2 100644 --- a/src/frontend/src/types/contexts/auth.ts +++ b/src/frontend/src/types/contexts/auth.ts @@ -14,4 +14,7 @@ export type AuthContextType = { authenticationErrorCount: number; autoLogin: boolean; setAutoLogin: (autoLogin: boolean) => void; + apiKey: string | null; + setApiKey: (apiKey: string | null) => void; + storeApiKey: (apiKey: string) => void; }; diff --git a/src/frontend/src/types/contexts/store.ts b/src/frontend/src/types/contexts/store.ts new file mode 100644 index 000000000..1bd762c64 --- /dev/null +++ b/src/frontend/src/types/contexts/store.ts @@ -0,0 +1,9 @@ +export type storeContextType = { + setHasStore: (store: boolean) => void; + hasStore: boolean; + setHasApiKey: (key: boolean) => void; + hasApiKey: boolean; + setValidApiKey: (key: boolean) => void; + validApiKey: boolean; + loadingApiKey: boolean; +}; diff --git a/src/frontend/src/types/entities/index.ts b/src/frontend/src/types/entities/index.ts index 3730492ce..179debc06 100644 --- a/src/frontend/src/types/entities/index.ts +++ b/src/frontend/src/types/entities/index.ts @@ -1,6 +1,12 @@ +import { FlowType } from "../flow"; + export type sidebarNavigationItemType = { name: string; href: string; icon: React.ForwardRefExoticComponent>; current: boolean; }; + +export type localStorageUserType = { + components: { [key: string]: FlowType }; +}; diff --git a/src/frontend/src/types/flow/index.ts b/src/frontend/src/types/flow/index.ts index e2b3a43b0..471de4706 100644 --- a/src/frontend/src/types/flow/index.ts +++ b/src/frontend/src/types/flow/index.ts @@ -7,6 +7,10 @@ export type FlowType = { data: ReactFlowJsonObject | null; description: string; style?: FlowStyleType; + is_component: boolean; + parent?: string; + date_created?: string; + last_tested_version?: string; }; export type NodeType = { id: string; diff --git a/src/frontend/src/types/store/index.ts b/src/frontend/src/types/store/index.ts new file mode 100644 index 000000000..331e16e30 --- /dev/null +++ b/src/frontend/src/types/store/index.ts @@ -0,0 +1,20 @@ +export type storeComponent = { + id: string; + is_component: boolean; + tags?: { id: string; name: string }[]; + metadata?: any; + downloads_count?: number; + name: string; + description: string; + liked_by_count?: number; + liked_by_user?: boolean; + user_created?: { username: string }; + last_tested_version?: string; + private?: boolean; +}; + +export type StoreComponentResponse = { + count: number; + authorized: boolean; + results: storeComponent[]; +}; diff --git a/src/frontend/src/types/tabs/index.ts b/src/frontend/src/types/tabs/index.ts index 1dc3a68d3..08c21846b 100644 --- a/src/frontend/src/types/tabs/index.ts +++ b/src/frontend/src/types/tabs/index.ts @@ -1,5 +1,5 @@ import { tweakType } from "../components"; -import { FlowType } from "../flow"; +import { FlowType, NodeDataType } from "../flow"; export type FlowsContextType = { saveFlow: (flow: FlowType, silent?: boolean) => Promise; @@ -36,6 +36,9 @@ export type FlowsContextType = { setLastCopiedSelection: (selection: { nodes: any; edges: any }) => void; setTweak: (tweak: tweakType) => tweakType | void; getTweak: tweakType; + saveComponent: (component: NodeDataType) => Promise; + deleteComponent: (id: string, key: string) => void; + version: string; }; export type FlowsState = { diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index f2370dcd7..74b5366f6 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -8,6 +8,7 @@ import { ReactFlowJsonObject, XYPosition, } from "reactflow"; +import ShortUniqueId from "short-unique-id"; import { specialCharsRegex } from "../constants/constants"; import { APITemplateType, TemplateVariableType } from "../types/api"; import { @@ -162,46 +163,51 @@ export function updateIds( ) { let idsMap = {}; - newFlow.nodes.forEach((node: NodeType) => { - // Generate a unique node ID - let newId = getNodeId(node.data.node?.flow ? "GroupNode" : node.data.type); - idsMap[node.id] = newId; - node.id = newId; - node.data.id = newId; - // Add the new node to the list of nodes in state - }); + if (newFlow.nodes) + newFlow.nodes.forEach((node: NodeType) => { + // Generate a unique node ID + let newId = getNodeId( + node.data.node?.flow ? "GroupNode" : node.data.type + ); + idsMap[node.id] = newId; + node.id = newId; + node.data.id = newId; + // Add the new node to the list of nodes in state + }); - newFlow.edges.forEach((edge: Edge) => { - edge.source = idsMap[edge.source]; - edge.target = idsMap[edge.target]; - const sourceHandleObject: sourceHandleType = scapeJSONParse( - edge.sourceHandle! - ); - edge.sourceHandle = scapedJSONStringfy({ - ...sourceHandleObject, - id: edge.source, + if (newFlow.edges) + newFlow.edges.forEach((edge: Edge) => { + edge.source = idsMap[edge.source]; + edge.target = idsMap[edge.target]; + const sourceHandleObject: sourceHandleType = scapeJSONParse( + edge.sourceHandle! + ); + edge.sourceHandle = scapedJSONStringfy({ + ...sourceHandleObject, + id: edge.source, + }); + if (edge.data?.sourceHandle?.id) { + edge.data.sourceHandle.id = edge.source; + } + const targetHandleObject: targetHandleType = scapeJSONParse( + edge.targetHandle! + ); + edge.targetHandle = scapedJSONStringfy({ + ...targetHandleObject, + id: edge.target, + }); + if (edge.data?.targetHandle?.id) { + edge.data.targetHandle.id = edge.target; + } + edge.id = + "reactflow__edge-" + + edge.source + + edge.sourceHandle + + "-" + + edge.target + + edge.targetHandle; }); - if (edge.data?.sourceHandle?.id) { - edge.data.sourceHandle.id = edge.source; - } - const targetHandleObject: targetHandleType = scapeJSONParse( - edge.targetHandle! - ); - edge.targetHandle = scapedJSONStringfy({ - ...targetHandleObject, - id: edge.target, - }); - if (edge.data?.targetHandle?.id) { - edge.data.targetHandle.id = edge.target; - } - edge.id = - "reactflow__edge-" + - edge.source + - edge.sourceHandle + - "-" + - edge.target + - edge.targetHandle; - }); + return idsMap; } export function buildTweaks(flow: FlowType) { @@ -518,6 +524,7 @@ export function generateFlow( const newFlow: FlowType = { data: newFlowData, + is_component: false, name: name, description: "", //generating local id instead of using the id from the server, can change in the future @@ -695,7 +702,6 @@ function isHandleConnected( /* this function receives a flow and a handleId and check if there is a connection with this handle */ - scapedJSONStringfy({ type: field.type, fieldName: key, id: nodeId }); if (field.proxy) { if ( edges.some( @@ -899,17 +905,45 @@ export function ungroupNode( BaseFlow.edges = edges; } +function updateProxyIdsOnTemplate( + template: APITemplateType, + idsMap: { [key: string]: string } +) { + Object.keys(template).forEach((key) => { + if (template[key].proxy && idsMap[template[key].proxy!.id]) { + template[key].proxy!.id = idsMap[template[key].proxy!.id]; + } + }); +} + +function updateEdgesIds(edges: Edge[], idsMap: { [key: string]: string }) { + edges.forEach((edge) => { + let targetHandle: targetHandleType = edge.data.targetHandle; + if (targetHandle.proxy && idsMap[targetHandle.proxy!.id]) { + targetHandle.proxy!.id = idsMap[targetHandle.proxy!.id]; + } + edge.data.targetHandle = targetHandle; + edge.targetHandle = scapedJSONStringfy(targetHandle); + }); +} + export function expandGroupNode( groupNode: NodeDataType, - ReactFlowInstance: ReactFlowInstance + ReactFlowInstance: ReactFlowInstance, + getNodeId: (type: string) => string ) { const { template, flow } = _.cloneDeep(groupNode.node!); + const idsMap = updateIds(flow!.data!, getNodeId); + updateProxyIdsOnTemplate(template, idsMap); + let flowEdges = ReactFlowInstance.getEdges(); + updateEdgesIds(flowEdges, idsMap); const gNodes: NodeType[] = flow?.data?.nodes!; const gEdges = flow!.data!.edges; + //TODO update ids of intern nodes and proxy on edges before expanding console.log(gEdges); //redirect edges to correct proxy node let updatedEdges: Edge[] = []; - ReactFlowInstance.getEdges().forEach((edge) => { + flowEdges.forEach((edge) => { let newEdge = _.cloneDeep(edge); if (newEdge.target === groupNode.id) { const targetHandle: targetHandleType = newEdge.data.targetHandle; @@ -995,7 +1029,6 @@ export function expandGroupNode( ...gEdges, ...updatedEdges, ]; - console.log(edges); ReactFlowInstance.setNodes(nodes); ReactFlowInstance.setEdges(edges); } @@ -1030,3 +1063,45 @@ export function getGroupStatus( }); return status; } + +export function createFlowComponent( + nodeData: NodeDataType, + version: string +): FlowType { + nodeData.node!.official = false; + const flowNode: FlowType = { + data: { + edges: [], + nodes: [ + { + data: nodeData, + id: nodeData.id, + position: { x: 0, y: 0 }, + type: "genericNode", + }, + ], + viewport: { x: 1, y: 1, zoom: 1 }, + }, + description: nodeData.node?.description || "", + name: nodeData.node?.display_name || nodeData.type || "", + id: nodeData.id || "", + is_component: true, + last_tested_version: version, + }; + return flowNode; +} + +export function downloadNode(NodeFLow: FlowType) { + const element = document.createElement("a"); + const file = new Blob([JSON.stringify(NodeFLow)], { + type: "application/json", + }); + element.href = URL.createObjectURL(file); + element.download = `${NodeFLow.name}.json`; + element.click(); +} + +export function updateComponentNameAndType( + data: any, + component: NodeDataType +) {} diff --git a/src/frontend/src/utils/storeUtils.ts b/src/frontend/src/utils/storeUtils.ts new file mode 100644 index 000000000..b31f3ab44 --- /dev/null +++ b/src/frontend/src/utils/storeUtils.ts @@ -0,0 +1,23 @@ +import { cloneDeep } from "lodash"; +import { FlowType } from "../types/flow"; + +export default function cloneFLowWithParent( + flow: FlowType, + parent: string, + is_component: boolean +) { + let childFLow = cloneDeep(flow); + childFLow.parent = parent; + childFLow.id = ""; + childFLow.is_component = is_component; + return childFLow; +} + +export function getTagsIds( + tags: string[], + tagListId: { current: { name: string; id: string }[] } +) { + return tags + .map((tag) => tagListId.current.find((tagObj) => tagObj.name === tag))! + .map((tag) => tag!.id); +} diff --git a/src/frontend/src/utils/styleUtils.ts b/src/frontend/src/utils/styleUtils.ts index 86011f6e0..ca6790229 100644 --- a/src/frontend/src/utils/styleUtils.ts +++ b/src/frontend/src/utils/styleUtils.ts @@ -2,6 +2,8 @@ import { ArrowUpToLine, Bell, BookMarked, + BookmarkPlus, + Boxes, Check, CheckCircle2, ChevronDown, @@ -31,11 +33,14 @@ import { FileText, FileUp, Fingerprint, + FolderPlus, Gift, + GitBranchPlus, GitFork, GithubIcon, Group, Hammer, + Heart, HelpCircle, Home, Info, @@ -44,7 +49,9 @@ import { Layers, Lightbulb, Link, + Loader2, Lock, + LogIn, LucideSend, Maximize2, Menu, @@ -55,20 +62,25 @@ import { Minus, MoonIcon, MoreHorizontal, + Network, Paperclip, Pencil, Plus, Redo, Rocket, Save, + SaveAll, Scissors, Search, Settings2, + Share2, Shield, Sparkles, Square, + Store, SunIcon, TerminalSquare, + ToyBrick, Trash2, Undo, Ungroup, @@ -81,6 +93,7 @@ import { Users2, Variable, Wand2, + Workflow, Wrench, X, XCircle, @@ -233,6 +246,7 @@ export const nodeIconsLucide: iconsType = { ChatVertexAI: VertexAIIcon, VertexAIEmbeddings: VertexAIIcon, agents: Rocket, + Workflow, User, WikipediaAPIWrapper: SvgWikipedia, chains: Link, @@ -258,6 +272,8 @@ export const nodeIconsLucide: iconsType = { custom_components: GradientSparkles, custom: Edit, Trash2, + Boxes, + Network, X, XCircle, Info, @@ -285,6 +301,7 @@ export const nodeIconsLucide: iconsType = { Clipboard, Code2, Variable, + Store, Download, Eraser, Lock, @@ -293,6 +310,7 @@ export const nodeIconsLucide: iconsType = { DownloadCloud, File, FileText, + FolderPlus, GitFork, GithubIcon, FileDown, @@ -317,6 +335,7 @@ export const nodeIconsLucide: iconsType = { Key, Unplug, Group, + LogIn, ChevronUp, Ungroup, BookMarked, @@ -324,4 +343,12 @@ export const nodeIconsLucide: iconsType = { Square, Minimize2, Maximize2, + SaveAll, + Share2, + GitBranchPlus, + Loader2, + BookmarkPlus, + Heart, + Link, + ToyBrick, }; diff --git a/src/frontend/src/utils/utils.ts b/src/frontend/src/utils/utils.ts index c32962747..478028bca 100644 --- a/src/frontend/src/utils/utils.ts +++ b/src/frontend/src/utils/utils.ts @@ -9,6 +9,7 @@ import { import { IVarHighlightType, groupedObjType, + nodeGroupedObjType, tweakType, } from "../types/components"; import { FlowType, NodeType } from "../types/flow"; @@ -107,13 +108,15 @@ export function groupByFamily( const baseClassesSet = new Set(baseClasses.split("\n")); let arrOfPossibleInputs: Array<{ category: string; - nodes: string[]; + nodes: nodeGroupedObjType[]; full: boolean; + display_name?: string; }> = []; let arrOfPossibleOutputs: Array<{ category: string; - nodes: string[]; + nodes: nodeGroupedObjType[]; full: boolean; + display_name?: string; }> = []; let checkedNodes = new Map(); const excludeTypes = new Set([ @@ -126,18 +129,23 @@ export function groupByFamily( "int", ]); - const checkBaseClass = (template: TemplateVariableType) => - template.type && - template.show && - ((!excludeTypes.has(template.type) && baseClassesSet.has(template.type)) || - (template.input_types && - template.input_types.some((inputType) => - baseClassesSet.has(inputType) - ))); + const checkBaseClass = (template: TemplateVariableType) => { + return ( + template.type && + template.show && + ((!excludeTypes.has(template.type) && + baseClassesSet.has(template.type)) || + (template.input_types && + template.input_types.some((inputType) => { + baseClassesSet.has(inputType); + }))) + ); + }; if (flow) { for (const node of flow) { const nodeData = node.data; + const foundNode = checkedNodes.get(nodeData.type); checkedNodes.set(nodeData.type, { hasBaseClassInTemplate: @@ -148,16 +156,18 @@ export function groupByFamily( nodeData.node!.base_classes.some((baseClass) => baseClassesSet.has(baseClass) ), + displayName: nodeData.node?.display_name, }); } } for (const [d, nodes] of Object.entries(data)) { - let tempInputs: string[] = [], - tempOutputs: string[] = []; + let tempInputs: nodeGroupedObjType[] = [], + tempOutputs: nodeGroupedObjType[] = []; for (const [n, node] of Object.entries(nodes!)) { let foundNode = checkedNodes.get(n); + if (!foundNode) { foundNode = { hasBaseClassInTemplate: Object.values(node!.template).some( @@ -166,15 +176,18 @@ export function groupByFamily( hasBaseClassInBaseClasses: node!.base_classes.some((baseClass) => baseClassesSet.has(baseClass) ), + displayName: node?.display_name, }; - checkedNodes.set(n, foundNode); } - if (foundNode.hasBaseClassInTemplate) tempInputs.push(n); - if (foundNode.hasBaseClassInBaseClasses) tempOutputs.push(n); + if (foundNode.hasBaseClassInTemplate) + tempInputs.push({ node: n, displayName: foundNode.displayName }); + if (foundNode.hasBaseClassInBaseClasses) + tempOutputs.push({ node: n, displayName: foundNode.displayName }); } const totalNodes = Object.keys(nodes!).length; + if (tempInputs.length) arrOfPossibleInputs.push({ category: d, @@ -192,11 +205,15 @@ export function groupByFamily( return left ? arrOfPossibleOutputs.map((output) => ({ family: output.category, - type: output.full ? "" : output.nodes.join(", "), + type: output.full + ? "" + : output.nodes.map((item) => item.node).join(", "), + display_name: "", })) : arrOfPossibleInputs.map((input) => ({ family: input.category, - type: input.full ? "" : input.nodes.join(", "), + type: input.full ? "" : input.nodes.map((item) => item.node).join(", "), + display_name: input.nodes.map((item) => item.displayName).join(", "), })); } @@ -556,6 +573,77 @@ export function tabsArray(codes: string[], method: number) { ]; } +export function checkLocalStorageKey(key: string): boolean { + return localStorage.getItem(key) !== null; +} + +export function IncrementObjectKey( + object: object, + key: string +): { newKey: string; increment: number } { + let count = 1; + const type = removeCountFromString(key); + let newKey = type + " " + `(${count})`; + while (object[newKey]) { + count++; + newKey = type + " " + `(${count})`; + } + return { newKey, increment: count }; +} + +export function removeCountFromString(input: string): string { + // Define a regex pattern to match the count in parentheses + const pattern = /\s*\(\w+\)\s*$/; + + // Use the `replace` method to remove the matched pattern + const result = input.replace(pattern, ""); + + return result.trim(); // Trim any leading/trailing spaces +} + +export function createRandomKey(key: string, uid: string): string { + return removeCountFromString(key) + ` (${uid})`; +} + +export function sensitiveSort(a: string, b: string): number { + // Extract the name and number from each string using regular expressions + const regex = /(.+) \((\w+)\)/; + const matchA = a.match(regex); + const matchB = b.match(regex); + + if (matchA && matchB) { + // Compare the names alphabetically + const nameA = matchA[1]; + const nameB = matchB[1]; + if (nameA !== nameB) { + return nameA.localeCompare(nameB); + } + + // If the names are the same, compare the numbers numerically + const numberA = parseInt(matchA[2]); + const numberB = parseInt(matchB[2]); + return numberA - numberB; + } else { + // Handle cases where one or both strings do not match the expected pattern + // Simple strings are treated as pure alphabetical comparisons + return a.localeCompare(b); + } +} +// this function is used to get the set of keys from an object +export function getSetFromObject(obj: object, key?: string): Set { + const set = new Set(); + if (key) { + for (const objKey in obj) { + set.add(obj[objKey][key]); + } + } else { + for (const key in obj) { + set.add(key); + } + } + return set; +} + export function getFieldTitle( template: APITemplateType, templateField: string diff --git a/src/frontend/tests/onlyFront/saveComponents.spec.ts b/src/frontend/tests/onlyFront/saveComponents.spec.ts new file mode 100644 index 000000000..a2539090b --- /dev/null +++ b/src/frontend/tests/onlyFront/saveComponents.spec.ts @@ -0,0 +1,254 @@ +import { Page, expect, test } from "@playwright/test"; +import { readFileSync } from "fs"; + +test.describe("save component tests", () => { + async function saveComponent(page: Page, pattern: RegExp, n: number) { + for (let i = 0; i < n; i++) { + await page.getByTestId(pattern).click(); + //more node options + await page + .locator( + "//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div" + ) + .click(); + await page.getByLabel("Save").click(); + } + } + + /// + test("save group component tests", async ({ page }) => { + //make front work withoput backend + await page.routeFromHAR("harFiles/langflow.har", { + url: "**/api/v1/**", + update: false, + }); + await page.route("**/api/v1/flows/", async (route) => { + const json = { + id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210", + }; + await route.fulfill({ json, status: 201 }); + }); + await page.goto("http:localhost:3000/"); + await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); + // Read your file into a buffer. + const jsonContent = readFileSync( + "tests/onlyFront/assets/flow.json", + "utf-8" + ); + + // Create the DataTransfer and File + const dataTransfer = await page.evaluateHandle((data) => { + const dt = new DataTransfer(); + // Convert the buffer to a hex array + const file = new File([data], "flow.json", { + type: "application/json", + }); + dt.items.add(file); + return dt; + }, jsonContent); + + // Now dispatch + await page.dispatchEvent('//*[@id="root"]/div/div[2]/div[2]', "drop", { + dataTransfer, + }); + expect( + await page + .locator(".main-page-flows-display") + .evaluate((el) => el.children) + ).toBeTruthy(); + await page.getByRole("button", { name: "Edit Flow" }).click(); + //inside the flow + await page + .locator( + "//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[1]/div/div[1]/div" + ) + .click({ + modifiers: ["Control"], + }); + await page + .locator( + "//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[2]/div/div[1]/div" + ) + .click({ + modifiers: ["Control"], + }); + await page + .locator( + "//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div[3]/div/div[1]/div" + ) + .click({ + modifiers: ["Control"], + }); + await page.getByRole("button", { name: "Group" }).click(); + expect( + await page + .locator( + "//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[1]/div/div[2]/div/div" + ) + .isVisible() + ).toBeTruthy(); + await page.getByPlaceholder("Type something...").first().click(); + await page.getByPlaceholder("Type something...").first().fill("save"); + await page.locator(".react-flow__pane").click(); + await page + .locator(".side-bar-buttons-arrangement > div:nth-child(3)") + .click(); + //more option click + await page + .locator( + "//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div" + ) + .click(); + await page.getByLabel("Save").click(); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("save"); + await page.waitForTimeout(2000); + await page + .locator('//*[@id="custom_componentssave"]') + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.waitForTimeout(2000); + expect( + (await page.getByTestId(/.*rf__node-AgentInitializer.*/).all()).length + ).toBe(2); + await page.locator(".isolate > button").first().click(); + expect( + (await page.getByTestId(/.*rf__node-AgentInitializer.*/).all()).length + ).toBe(1); + await page.getByTestId(/.*rf__node-AgentInitializer.*/).click(); + await page.getByTestId(/.*rf__node-AgentInitializer.*/).press("Backspace"); + await page + .locator('//*[@id="custom_componentssave"]') + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.getByTestId(/.*rf__node-AgentInitializer.*/).click(); + await page + .locator( + "//html/body/div/div/div[2]/div/main/div/div/div/div[1]/div[1]/div[2]/div/span/button[3]/div/div" + ) + .click(); + await page.getByLabel("Ungroup").click(); + expect((await page.getByTestId(/.*rf__node-.*/).all()).length).toBe(3); + expect( + (await page.getByTestId(/.*rf__edge-reactflow.*/).all()).length + ).toBe(2); + }); + + test("save default component with custom values", async ({ page }) => { + await page.routeFromHAR("harFiles/langflow.har", { + url: "**/api/v1/**", + update: false, + }); + await page.route("**/api/v1/flows/", async (route) => { + const json = { + id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210", + }; + await route.fulfill({ json, status: 201 }); + }); + await page.goto("http://localhost:3000/"); + await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); + await page.locator('//*[@id="new-project-btn"]').click(); + await page.waitForTimeout(2000); + + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("Chroma"); + + await page + .locator('//*[@id="vectorstoresChroma"]') + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await page.locator("#input-8").click(); + await page.locator("#input-8").fill("test"); + await saveComponent(page, /.*rf__node-Chroma.*/, 1); + await page.getByTestId(/.*rf__node-Chroma.*/).press("Backspace"); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill(""); + await page.getByPlaceholder("Search").fill("Chroma"); + await page + .locator('//*[@id="custom_componentsChroma"]') + .dragTo(page.locator('//*[@id="react-flow-id"]')); + expect(await page.locator("#input-8").inputValue()).toBe("test"); + }); + + test("save same component multiple times", async ({ page }) => { + await page.routeFromHAR("harFiles/langflow.har", { + url: "**/api/v1/**", + update: false, + }); + await page.route("**/api/v1/flows/", async (route) => { + const json = { + id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210", + }; + await route.fulfill({ json, status: 201 }); + }); + await page.goto("http://localhost:3000/"); + await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); + await page.locator('//*[@id="new-project-btn"]').click(); + await page.waitForTimeout(2000); + + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("Chroma"); + + await page + .locator('//*[@id="vectorstoresChroma"]') + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await saveComponent(page, /.*rf__node-Chroma.*/, 3); + await page.getByTestId(/.*rf__node-Chroma.*/).press("Backspace"); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill(""); + await page.getByPlaceholder("Search").fill("Chroma"); + expect( + await page.locator('//*[@id="custom_componentsChroma"]').isVisible() + ).toBeTruthy(); + expect( + await page.locator('[id="custom_componentsChroma\\ \\(1\\)"]').isVisible() + ).toBeTruthy(); + expect( + await page.locator('[id="custom_componentsChroma\\ \\(2\\)"]').isVisible() + ).toBeTruthy(); + await page + .locator('[id="custom_componentsChroma\\ \\(2\\)"]') + .dragTo(page.locator('//*[@id="react-flow-id"]')); + expect( + (await page.getByTestId(/.*rf__node-Chroma.*/).allInnerTexts()).includes( + "Chroma (2)" + ) + ).toBeTruthy(); + }); + + test("save default component and delete it", async ({ page }) => { + await page.routeFromHAR("harFiles/langflow.har", { + url: "**/api/v1/**", + update: false, + }); + await page.route("**/api/v1/flows/", async (route) => { + const json = { + id: "e9ac1bdc-429b-475d-ac03-d26f9a2a3210", + }; + await route.fulfill({ json, status: 201 }); + }); + await page.goto("http://localhost:3000/"); + await page.locator("span").filter({ hasText: "My Collection" }).isVisible(); + await page.locator('//*[@id="new-project-btn"]').click(); + await page.waitForTimeout(2000); + + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill("Chroma"); + + await page + .locator('//*[@id="vectorstoresChroma"]') + .dragTo(page.locator('//*[@id="react-flow-id"]')); + await saveComponent(page, /.*rf__node-Chroma.*/, 1); + await page.getByTestId(/.*rf__node-Chroma.*/).press("Backspace"); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill(""); + await page.getByPlaceholder("Search").fill("Chroma"); + await page.locator("#custom_componentsChroma").getByRole("combobox").click({ + button: "right", + }); + await page.getByLabel("Delete").click(); + await page.getByPlaceholder("Search").click(); + await page.getByPlaceholder("Search").fill(" "); + await page.getByPlaceholder("Search").fill("Chroma"); + expect( + await page.locator("#custom_componentsChroma").isVisible() + ).toBeFalsy(); + }); +}); diff --git a/tests/conftest.py b/tests/conftest.py index ddf30e2a4..0acc3116f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,56 +1,41 @@ -from contextlib import contextmanager import json -from contextlib import suppress -from pathlib import Path -from typing import AsyncGenerator, TYPE_CHECKING +# we need to import tmpdir +import tempfile +from contextlib import contextmanager, suppress +from pathlib import Path +from typing import TYPE_CHECKING, AsyncGenerator + +import orjson +import pytest +from fastapi.testclient import TestClient +from httpx import AsyncClient from langflow.graph.graph.base import Graph from langflow.services.auth.utils import get_password_hash from langflow.services.database.models.flow.flow import Flow, FlowCreate from langflow.services.database.models.user.user import User, UserCreate -import orjson from langflow.services.database.utils import session_getter -from langflow.services.getters import get_db_service -import pytest -from fastapi.testclient import TestClient -from httpx import AsyncClient -from sqlmodel import SQLModel, Session, create_engine +from langflow.services.deps import get_db_service +from sqlmodel import Session, SQLModel, create_engine from sqlmodel.pool import StaticPool from typer.testing import CliRunner -# we need to import tmpdir -import tempfile - if TYPE_CHECKING: - from langflow.services.database.manager import DatabaseService + from langflow.services.database.service import DatabaseService def pytest_configure(): - pytest.BASIC_EXAMPLE_PATH = ( - Path(__file__).parent.absolute() / "data" / "basic_example.json" - ) - pytest.COMPLEX_EXAMPLE_PATH = ( - Path(__file__).parent.absolute() / "data" / "complex_example.json" - ) - pytest.OPENAPI_EXAMPLE_PATH = ( - Path(__file__).parent.absolute() / "data" / "Openapi.json" - ) - pytest.GROUPED_CHAT_EXAMPLE_PATH = ( - Path(__file__).parent.absolute() / "data" / "grouped_chat.json" - ) - pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = ( - Path(__file__).parent.absolute() / "data" / "one_group_chat.json" - ) - pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = ( - Path(__file__).parent.absolute() / "data" / "vector_store_grouped.json" - ) + pytest.BASIC_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "basic_example.json" + pytest.COMPLEX_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "complex_example.json" + pytest.OPENAPI_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "Openapi.json" + pytest.GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "grouped_chat.json" + pytest.ONE_GROUPED_CHAT_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "one_group_chat.json" + pytest.VECTOR_STORE_GROUPED_EXAMPLE_PATH = Path(__file__).parent.absolute() / "data" / "vector_store_grouped.json" pytest.BASIC_CHAT_WITH_PROMPT_AND_HISTORY = ( Path(__file__).parent.absolute() / "data" / "BasicChatwithPromptandHistory.json" ) - pytest.VECTOR_STORE_PATH = ( - Path(__file__).parent.absolute() / "data" / "Vector_store.json" - ) + pytest.VECTOR_STORE_PATH = Path(__file__).parent.absolute() / "data" / "Vector_store.json" pytest.CODE_WITH_SYNTAX_ERROR = """ def get_text(): retun "Hello World" @@ -68,9 +53,7 @@ async def async_client() -> AsyncGenerator: @pytest.fixture(name="session") def session_fixture(): - engine = create_engine( - "sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool - ) + engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool) SQLModel.metadata.create_all(engine) with Session(engine) as session: yield session @@ -106,9 +89,7 @@ def distributed_client_fixture(session: Session, monkeypatch, distributed_env): monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false") # monkeypatch langflow.services.task.manager.USE_CELERY to True # monkeypatch.setattr(manager, "USE_CELERY", True) - monkeypatch.setattr( - celery_app, "celery_app", celery_app.make_celery("langflow", Config) - ) + monkeypatch.setattr(celery_app, "celery_app", celery_app.make_celery("langflow", Config)) # def get_session_override(): # return session @@ -243,7 +224,7 @@ def test_user(client): username="testuser", password="testpassword", ) - response = client.post("/api/v1/users", json=user_data.dict()) + response = client.post("/api/v1/users", json=user_data.model_dump()) assert response.status_code == 201 return response.json() @@ -259,11 +240,7 @@ def active_user(client): is_superuser=False, ) # check if user exists - if ( - active_user := session.query(User) - .filter(User.username == user.username) - .first() - ): + if active_user := session.query(User).filter(User.username == user.username).first(): return active_user session.add(user) session.commit() @@ -292,7 +269,7 @@ def flow(client, json_flow: str, active_user): user_id=active_user.id, description="description", ) - flow = Flow(**flow_data.dict()) + flow = Flow(**flow_data.model_dump()) with session_getter(get_db_service()) as session: session.add(flow) session.commit() @@ -306,7 +283,7 @@ def added_flow(client, json_flow_with_prompt_and_history, logged_in_headers): flow = orjson.loads(json_flow_with_prompt_and_history) data = flow["data"] flow = FlowCreate(name="Basic Chat", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) + response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data @@ -318,9 +295,7 @@ def added_vector_store(client, json_vector_store, logged_in_headers): vector_store = orjson.loads(json_vector_store) data = vector_store["data"] vector_store = FlowCreate(name="Vector Store", description="description", data=data) - response = client.post( - "api/v1/flows/", json=vector_store.dict(), headers=logged_in_headers - ) + response = client.post("api/v1/flows/", json=vector_store.model_dump(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == vector_store.name assert response.json()["data"] == vector_store.data diff --git a/tests/locust/locustfile.py b/tests/locust/locustfile.py index aca0d1de9..1fc91ee2c 100644 --- a/tests/locust/locustfile.py +++ b/tests/locust/locustfile.py @@ -66,9 +66,7 @@ class NameTest(FastHttpUser): result1, session_id = self.process(name, self.flow_id, payload1) payload2 = { - "inputs": { - "text": "What is my name? Please, answer like this: Your name is " - }, + "inputs": {"text": "What is my name? Please, answer like this: Your name is "}, "session_id": session_id, "sync": False, } @@ -88,9 +86,7 @@ class NameTest(FastHttpUser): logged_in_headers = {"Authorization": f"Bearer {a_token}"} print("Logged in") with open( - Path(__file__).parent.parent - / "data" - / "BasicChatwithPromptandHistory.json", + Path(__file__).parent.parent / "data" / "BasicChatwithPromptandHistory.json", "r", ) as f: json_flow = f.read() @@ -115,11 +111,7 @@ class NameTest(FastHttpUser): ) print(response.json()) user_id = next( - ( - user["id"] - for user in response.json()["users"] - if user["username"] == "superuser" - ), + (user["id"] for user in response.json()["users"] if user["username"] == "superuser"), None, ) # Create api key diff --git a/tests/test_api_key.py b/tests/test_api_key.py index 43b91fa43..7988793d4 100644 --- a/tests/test_api_key.py +++ b/tests/test_api_key.py @@ -6,9 +6,7 @@ from langflow.services.database.models.api_key import ApiKeyCreate def api_key(client, logged_in_headers, active_user): api_key = ApiKeyCreate(name="test-api-key") - response = client.post( - "api/v1/api_key", data=api_key.json(), headers=logged_in_headers - ) + response = client.post("api/v1/api_key", data=api_key.json(), headers=logged_in_headers) assert response.status_code == 200, response.text return response.json() @@ -28,9 +26,7 @@ def test_get_api_keys(client, logged_in_headers, api_key): def test_create_api_key(client, logged_in_headers): api_key_name = "test-api-key" - response = client.post( - "api/v1/api_key", json={"name": api_key_name}, headers=logged_in_headers - ) + response = client.post("api/v1/api_key", json={"name": api_key_name}, headers=logged_in_headers) assert response.status_code == 200 data = response.json() assert "name" in data and data["name"] == api_key_name diff --git a/tests/test_chains_template.py b/tests/test_chains_template.py index dd6c2f058..6627fb26c 100644 --- a/tests/test_chains_template.py +++ b/tests/test_chains_template.py @@ -1,6 +1,5 @@ from fastapi.testclient import TestClient - # def test_chains_settings(client: TestClient, logged_in_headers): # response = client.get("api/v1/all", headers=logged_in_headers) # assert response.status_code == 200 @@ -129,10 +128,7 @@ def test_llm_math_chain(client: TestClient, logged_in_headers): assert template["_type"] == "LLMMathChain" # Test the description object - assert ( - chain["description"] - == "Chain that interprets a prompt and executes python code to do math." - ) + assert chain["description"] == "Chain that interprets a prompt and executes python code to do math." def test_series_character_chain(client: TestClient, logged_in_headers): @@ -238,10 +234,7 @@ def test_mid_journey_prompt_chain(client: TestClient, logged_in_headers): "info": "", } # Test the description object - assert ( - chain["description"] - == "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts." - ) + assert chain["description"] == "MidJourneyPromptChain is a chain you can use to generate new MidJourney prompts." def test_time_travel_guide_chain(client: TestClient, logged_in_headers): diff --git a/tests/test_cli.py b/tests/test_cli.py index ee938db12..63122d254 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,7 +3,7 @@ from tempfile import tempdir from langflow.__main__ import app import pytest -from langflow.services import getters +from langflow.services import deps @pytest.fixture(scope="module") @@ -26,7 +26,7 @@ def test_components_path(runner, client, default_settings): ["run", "--components-path", str(temp_dir), *default_settings], ) assert result.exit_code == 0, result.stdout - settings_service = getters.get_settings_service() + settings_service = deps.get_settings_service() assert str(temp_dir) in settings_service.settings.COMPONENTS_PATH diff --git a/tests/test_custom_component.py b/tests/test_custom_component.py index a1028cf13..c60247668 100644 --- a/tests/test_custom_component.py +++ b/tests/test_custom_component.py @@ -113,9 +113,7 @@ def test_custom_component_init(): """ function_entrypoint_name = "build" - custom_component = CustomComponent( - code=code_default, function_entrypoint_name=function_entrypoint_name - ) + custom_component = CustomComponent(code=code_default, function_entrypoint_name=function_entrypoint_name) assert custom_component.code == code_default assert custom_component.function_entrypoint_name == function_entrypoint_name @@ -124,9 +122,7 @@ def test_custom_component_build_template_config(): """ Test the build_template_config property of the CustomComponent class. """ - custom_component = CustomComponent( - code=code_default, function_entrypoint_name="build" - ) + custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") config = custom_component.build_template_config assert isinstance(config, dict) @@ -135,9 +131,7 @@ def test_custom_component_get_function(): """ Test the get_function property of the CustomComponent class. """ - custom_component = CustomComponent( - code="def build(): pass", function_entrypoint_name="build" - ) + custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build") my_function = custom_component.get_function assert isinstance(my_function, types.FunctionType) @@ -222,9 +216,7 @@ def test_custom_component_get_function_entrypoint_args(): Test the get_function_entrypoint_args property of the CustomComponent class. """ - custom_component = CustomComponent( - code=code_default, function_entrypoint_name="build" - ) + custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") args = custom_component.get_function_entrypoint_args assert len(args) == 4 assert args[0]["name"] == "self" @@ -237,9 +229,7 @@ def test_custom_component_get_function_entrypoint_return_type(): Test the get_function_entrypoint_return_type property of the CustomComponent class. """ - custom_component = CustomComponent( - code=code_default, function_entrypoint_name="build" - ) + custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") return_type = custom_component.get_function_entrypoint_return_type assert return_type == ["Document"] @@ -248,9 +238,7 @@ def test_custom_component_get_main_class_name(): """ Test the get_main_class_name property of the CustomComponent class. """ - custom_component = CustomComponent( - code=code_default, function_entrypoint_name="build" - ) + custom_component = CustomComponent(code=code_default, function_entrypoint_name="build") class_name = custom_component.get_main_class_name assert class_name == "YourComponent" @@ -260,9 +248,7 @@ def test_custom_component_get_function_valid(): Test the get_function property of the CustomComponent class with valid code and function_entrypoint_name. """ - custom_component = CustomComponent( - code="def build(): pass", function_entrypoint_name="build" - ) + custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build") my_function = custom_component.get_function assert callable(my_function) @@ -297,9 +283,7 @@ def test_code_parser_parse_callable_details_no_args(): parser = CodeParser("") node = ast.FunctionDef( name="test", - args=ast.arguments( - args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[] - ), + args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[], decorator_list=[], returns=None, @@ -345,9 +329,7 @@ def test_code_parser_parse_function_def_not_init(): parser = CodeParser("") stmt = ast.FunctionDef( name="test", - args=ast.arguments( - args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[] - ), + args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[], decorator_list=[], returns=None, @@ -365,9 +347,7 @@ def test_code_parser_parse_function_def_init(): parser = CodeParser("") stmt = ast.FunctionDef( name="__init__", - args=ast.arguments( - args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[] - ), + args=ast.arguments(args=[], vararg=None, kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]), body=[], decorator_list=[], returns=None, @@ -402,9 +382,7 @@ def test_custom_component_get_code_tree_syntax_error(): Test the get_code_tree method of the CustomComponent class raises the CodeSyntaxError when given incorrect syntax. """ - custom_component = CustomComponent( - code="import os as", function_entrypoint_name="build" - ) + custom_component = CustomComponent(code="import os as", function_entrypoint_name="build") with pytest.raises(CodeSyntaxError): custom_component.get_code_tree(custom_component.code) @@ -458,9 +436,7 @@ def test_custom_component_build_not_implemented(): Test the build method of the CustomComponent class raises the NotImplementedError. """ - custom_component = CustomComponent( - code="def build(): pass", function_entrypoint_name="build" - ) + custom_component = CustomComponent(code="def build(): pass", function_entrypoint_name="build") with pytest.raises(NotImplementedError): custom_component.build() @@ -494,9 +470,7 @@ def test_flow(db): } # Create flow - flow = FlowCreate( - id=uuid4(), name="Test Flow", description="Fixture flow", data=flow_data - ) + flow = FlowCreate(id=uuid4(), name="Test Flow", description="Fixture flow", data=flow_data) # Add to database db.add(flow) diff --git a/tests/test_custom_types.py b/tests/test_custom_types.py index b65f58d0a..ba54b7023 100644 --- a/tests/test_custom_types.py +++ b/tests/test_custom_types.py @@ -18,9 +18,7 @@ def test_python_function_tool(): with pytest.raises(SyntaxError): code = pytest.CODE_WITH_SYNTAX_ERROR func = get_function(code) - func = PythonFunctionTool( - name="Test", description="Testing", code=code, func=func - ) + func = PythonFunctionTool(name="Test", description="Testing", code=code, func=func) def test_python_function(): diff --git a/tests/test_database.py b/tests/test_database.py index 2aaeed8fe..4a1219799 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -1,16 +1,14 @@ -from langflow.services.database.models.base import orjson_dumps -from langflow.services.database.utils import session_getter -from langflow.services.getters import get_db_service +from uuid import UUID, uuid4 + import orjson import pytest - -from uuid import UUID, uuid4 -from sqlmodel import Session - from fastapi.testclient import TestClient - from langflow.api.v1.schemas import FlowListCreate +from langflow.services.database.models.base import orjson_dumps from langflow.services.database.models.flow import Flow, FlowCreate, FlowUpdate +from langflow.services.database.utils import session_getter +from langflow.services.deps import get_db_service +from sqlmodel import Session @pytest.fixture(scope="module") @@ -27,21 +25,17 @@ def json_style(): ) -def test_create_flow( - client: TestClient, json_flow: str, active_user, logged_in_headers -): +def test_create_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) + response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data # flow is optional so we can create a flow without a flow flow = FlowCreate(name="Test Flow", description="description") - response = client.post( - "api/v1/flows/", json=flow.dict(exclude_unset=True), headers=logged_in_headers - ) + response = client.post("api/v1/flows/", json=flow.dict(exclude_unset=True), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data @@ -51,13 +45,13 @@ def test_read_flows(client: TestClient, json_flow: str, active_user, logged_in_h flow_data = orjson.loads(json_flow) data = flow_data["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) + response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) + response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) assert response.status_code == 201 assert response.json()["name"] == flow.name assert response.json()["data"] == flow.data @@ -71,7 +65,7 @@ def test_read_flow(client: TestClient, json_flow: str, active_user, logged_in_he flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) + response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) flow_id = response.json()["id"] # flow_id should be a UUID but is a string # turn it into a UUID flow_id = UUID(flow_id) @@ -82,14 +76,12 @@ def test_read_flow(client: TestClient, json_flow: str, active_user, logged_in_he assert response.json()["data"] == flow.data -def test_update_flow( - client: TestClient, json_flow: str, active_user, logged_in_headers -): +def test_update_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) + response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) flow_id = response.json()["id"] updated_flow = FlowUpdate( @@ -97,9 +89,7 @@ def test_update_flow( description="updated description", data=data, ) - response = client.patch( - f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers - ) + response = client.patch(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers) assert response.status_code == 200 assert response.json()["name"] == updated_flow.name @@ -107,22 +97,18 @@ def test_update_flow( # assert response.json()["data"] == updated_flow.data -def test_delete_flow( - client: TestClient, json_flow: str, active_user, logged_in_headers -): +def test_delete_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] flow = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post("api/v1/flows/", json=flow.dict(), headers=logged_in_headers) + response = client.post("api/v1/flows/", json=flow.model_dump(), headers=logged_in_headers) flow_id = response.json()["id"] response = client.delete(f"api/v1/flows/{flow_id}", headers=logged_in_headers) assert response.status_code == 200 assert response.json()["message"] == "Flow deleted successfully" -def test_create_flows( - client: TestClient, session: Session, json_flow: str, logged_in_headers -): +def test_create_flows(client: TestClient, session: Session, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] # Create test data @@ -133,9 +119,7 @@ def test_create_flows( ] ) # Make request to endpoint - response = client.post( - "api/v1/flows/batch/", json=flow_list.dict(), headers=logged_in_headers - ) + response = client.post("api/v1/flows/batch/", json=flow_list.model_dump(), headers=logged_in_headers) # Check response status code assert response.status_code == 201 # Check response data @@ -149,9 +133,7 @@ def test_create_flows( assert response_data[1]["data"] == data -def test_upload_file( - client: TestClient, session: Session, json_flow: str, logged_in_headers -): +def test_upload_file(client: TestClient, session: Session, json_flow: str, logged_in_headers): flow = orjson.loads(json_flow) data = flow["data"] # Create test data @@ -161,7 +143,7 @@ def test_upload_file( FlowCreate(name="Flow 2", description="description", data=data), ] ) - file_contents = orjson_dumps(flow_list.dict()) + file_contents = orjson_dumps(flow_list.model_dump()) response = client.post( "api/v1/flows/upload/", files={"file": ("examples.json", file_contents, "application/json")}, @@ -200,7 +182,7 @@ def test_download_file( with session_getter(db_manager) as session: for flow in flow_list.flows: flow.user_id = active_user.id - db_flow = Flow.from_orm(flow) + db_flow = Flow.model_validate(flow) session.add(db_flow) session.commit() # Make request to endpoint @@ -218,9 +200,7 @@ def test_download_file( assert response_data[1]["data"] == data -def test_create_flow_with_invalid_data( - client: TestClient, active_user, logged_in_headers -): +def test_create_flow_with_invalid_data(client: TestClient, active_user, logged_in_headers): flow = {"name": "a" * 256, "data": "Invalid flow data"} response = client.post("api/v1/flows/", json=flow, headers=logged_in_headers) assert response.status_code == 422 @@ -232,29 +212,19 @@ def test_get_nonexistent_flow(client: TestClient, active_user, logged_in_headers assert response.status_code == 404 -def test_update_flow_idempotency( - client: TestClient, json_flow: str, active_user, logged_in_headers -): +def test_update_flow_idempotency(client: TestClient, json_flow: str, active_user, logged_in_headers): flow_data = orjson.loads(json_flow) data = flow_data["data"] flow_data = FlowCreate(name="Test Flow", description="description", data=data) - response = client.post( - "api/v1/flows/", json=flow_data.dict(), headers=logged_in_headers - ) + response = client.post("api/v1/flows/", json=flow_data.model_dump(), headers=logged_in_headers) flow_id = response.json()["id"] updated_flow = FlowCreate(name="Updated Flow", description="description", data=data) - response1 = client.put( - f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers - ) - response2 = client.put( - f"api/v1/flows/{flow_id}", json=updated_flow.dict(), headers=logged_in_headers - ) + response1 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers) + response2 = client.put(f"api/v1/flows/{flow_id}", json=updated_flow.model_dump(), headers=logged_in_headers) assert response1.json() == response2.json() -def test_update_nonexistent_flow( - client: TestClient, json_flow: str, active_user, logged_in_headers -): +def test_update_nonexistent_flow(client: TestClient, json_flow: str, active_user, logged_in_headers): flow_data = orjson.loads(json_flow) data = flow_data["data"] uuid = uuid4() @@ -263,9 +233,7 @@ def test_update_nonexistent_flow( description="description", data=data, ) - response = client.patch( - f"api/v1/flows/{uuid}", json=updated_flow.dict(), headers=logged_in_headers - ) + response = client.patch(f"api/v1/flows/{uuid}", json=updated_flow.model_dump(), headers=logged_in_headers) assert response.status_code == 404 diff --git a/tests/test_endpoints.py b/tests/test_endpoints.py index 1de7c9deb..aba0a1a78 100644 --- a/tests/test_endpoints.py +++ b/tests/test_endpoints.py @@ -3,9 +3,9 @@ import uuid from langflow.processing.process import Result from langflow.services.auth.utils import get_password_hash from langflow.services.database.models.api_key.api_key import ApiKey -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service from langflow.services.database.utils import session_getter -from langflow.services.getters import get_db_service +from langflow.services.deps import get_db_service import pytest from fastapi.testclient import TestClient from langflow.interface.tools.constants import CUSTOM_TOOLS @@ -31,10 +31,7 @@ def poll_task_status(client, headers, href, max_attempts=20, sleep_time=1): href, headers=headers, ) - if ( - task_status_response.status_code == 200 - and task_status_response.json()["status"] == "SUCCESS" - ): + if task_status_response.status_code == 200 and task_status_response.json()["status"] == "SUCCESS": return task_status_response.json() time.sleep(sleep_time) return None # Return None if task did not complete in time @@ -130,11 +127,7 @@ def created_api_key(active_user): ) db_manager = get_db_service() with session_getter(db_manager) as session: - if ( - existing_api_key := session.query(ApiKey) - .filter(ApiKey.api_key == api_key.api_key) - .first() - ): + if existing_api_key := session.query(ApiKey).filter(ApiKey.api_key == api_key.api_key).first(): return existing_api_key session.add(api_key) session.commit() @@ -193,9 +186,7 @@ def test_process_flow_invalid_id(client, monkeypatch, created_api_key): } invalid_id = uuid.uuid4() - response = client.post( - f"api/v1/process/{invalid_id}", headers=headers, json=post_data - ) + response = client.post(f"api/v1/process/{invalid_id}", headers=headers, json=post_data) assert response.status_code == 404 assert f"Flow {invalid_id} not found" in response.json()["detail"] @@ -236,9 +227,7 @@ def test_process_flow_without_autologin(client, flow, monkeypatch, created_api_k monkeypatch.setattr(endpoints, "process_graph_cached", mock_process_graph_cached) monkeypatch.setattr(crud, "update_total_uses", mock_update_total_uses) - monkeypatch.setattr( - endpoints, "process_graph_cached_task", mock_process_graph_cached_task - ) + monkeypatch.setattr(endpoints, "process_graph_cached_task", mock_process_graph_cached_task) api_key = created_api_key.api_key headers = {"x-api-key": api_key} @@ -510,9 +499,7 @@ def test_basic_chat_with_two_session_ids_and_names(client, added_flow, created_a @pytest.mark.async_test -def test_vector_store_in_process( - distributed_client, added_vector_store, created_api_key -): +def test_vector_store_in_process(distributed_client, added_vector_store, created_api_key): # Run the /api/v1/process/{flow_id} endpoint headers = {"x-api-key": created_api_key.api_key} post_data = {"inputs": {"input": "What is Langflow?"}} @@ -563,9 +550,7 @@ def test_async_task_processing(distributed_client, added_flow, created_api_key): # Test function without loop @pytest.mark.async_test -def test_async_task_processing_vector_store( - client, added_vector_store, created_api_key -): +def test_async_task_processing_vector_store(client, added_vector_store, created_api_key): headers = {"x-api-key": created_api_key.api_key} post_data = {"inputs": {"input": "How do I upload examples?"}} @@ -594,6 +579,4 @@ def test_async_task_processing_vector_store( # Validate that the task completed successfully and the result is as expected assert "result" in task_status_json, task_status_json assert "output" in task_status_json["result"], task_status_json["result"] - assert "Langflow" in task_status_json["result"]["output"], task_status_json[ - "result" - ] + assert "Langflow" in task_status_json["result"]["output"], task_status_json["result"] diff --git a/tests/test_frontend_nodes.py b/tests/test_frontend_nodes.py index 00fe9fcb1..a45339d4b 100644 --- a/tests/test_frontend_nodes.py +++ b/tests/test_frontend_nodes.py @@ -39,9 +39,7 @@ def test_template_field_defaults(sample_template_field: TemplateField): assert sample_template_field.name == "test_field" -def test_template_to_dict( - sample_template: Template, sample_template_field: TemplateField -): +def test_template_to_dict(sample_template: Template, sample_template_field: TemplateField): template_dict = sample_template.to_dict() assert template_dict["_type"] == "test_template" assert len(template_dict) == 2 # _type and test_field diff --git a/tests/test_graph.py b/tests/test_graph.py index 1a24e0e4b..1c63e62e8 100644 --- a/tests/test_graph.py +++ b/tests/test_graph.py @@ -47,13 +47,7 @@ def sample_nodes(): return [ { "id": "node1", - "data": { - "node": { - "template": { - "some_field": {"show": True, "advanced": False, "name": "Name1"} - } - } - }, + "data": {"node": {"template": {"some_field": {"show": True, "advanced": False, "name": "Name1"}}}}, }, { "id": "node2", @@ -71,11 +65,7 @@ def sample_nodes(): }, { "id": "node3", - "data": { - "node": { - "template": {"unrelated_field": {"show": True, "advanced": True}} - } - }, + "data": {"node": {"template": {"unrelated_field": {"show": True, "advanced": True}}}}, }, ] @@ -158,15 +148,9 @@ def test_get_node_neighbors_basic(basic_graph): # Root Node is an Agent, it requires an LLMChain and tools # We need to check if there is a Chain in the one of the neighbors' # data attribute in the type key - assert any( - "ConversationBufferMemory" in neighbor.data["type"] - for neighbor, val in neighbors.items() - if val - ) + assert any("ConversationBufferMemory" in neighbor.data["type"] for neighbor, val in neighbors.items() if val) - assert any( - "OpenAI" in neighbor.data["type"] for neighbor, val in neighbors.items() if val - ) + assert any("OpenAI" in neighbor.data["type"] for neighbor, val in neighbors.items() if val) def test_get_node(basic_graph): @@ -339,9 +323,7 @@ def test_find_last_node(grouped_chat_json_flow): def test_ungroup_node(grouped_chat_json_flow): grouped_chat_data = json.loads(grouped_chat_json_flow).get("data") - group_node = grouped_chat_data["nodes"][ - 2 - ] # Assuming the first node is a group node + group_node = grouped_chat_data["nodes"][2] # Assuming the first node is a group node base_flow = copy.deepcopy(grouped_chat_data) ungroup_node(group_node["data"], base_flow) # after ungroup_node is called, the base_flow and grouped_chat_data should be different @@ -393,14 +375,9 @@ def test_process_flow_one_group(one_grouped_chat_json_flow): assert "edges" in processed_flow # Now get the node that has ChatOpenAI in its id - chat_openai_node = next( - (node for node in processed_flow["nodes"] if "ChatOpenAI" in node["id"]), None - ) + chat_openai_node = next((node for node in processed_flow["nodes"] if "ChatOpenAI" in node["id"]), None) assert chat_openai_node is not None - assert ( - chat_openai_node["data"]["node"]["template"]["openai_api_key"]["value"] - == "test" - ) + assert chat_openai_node["data"]["node"]["template"]["openai_api_key"]["value"] == "test" def test_process_flow_vector_store_grouped(vector_store_grouped_json_flow): @@ -449,17 +426,11 @@ def test_update_template(sample_template, sample_nodes): assert node1_updated["data"]["node"]["template"]["some_field"]["show"] is True assert node1_updated["data"]["node"]["template"]["some_field"]["advanced"] is False - assert ( - node1_updated["data"]["node"]["template"]["some_field"]["display_name"] - == "Name1" - ) + assert node1_updated["data"]["node"]["template"]["some_field"]["display_name"] == "Name1" assert node2_updated["data"]["node"]["template"]["other_field"]["show"] is False assert node2_updated["data"]["node"]["template"]["other_field"]["advanced"] is True - assert ( - node2_updated["data"]["node"]["template"]["other_field"]["display_name"] - == "DisplayName2" - ) + assert node2_updated["data"]["node"]["template"]["other_field"]["display_name"] == "DisplayName2" # Ensure node3 remains unchanged assert node3_updated == sample_nodes[2] @@ -490,9 +461,7 @@ def test_set_new_target_handle(): "data": { "node": { "flow": True, - "template": { - "field_1": {"proxy": {"field": "new_field", "id": "new_id"}} - }, + "template": {"field_1": {"proxy": {"field": "new_field", "id": "new_id"}}}, } } } @@ -512,9 +481,7 @@ def test_update_source_handle(): "nodes": [{"id": "some_node"}, {"id": "last_node"}], "edges": [{"source": "some_node"}], } - updated_edge = update_source_handle( - new_edge, flow_data["nodes"], flow_data["edges"] - ) + updated_edge = update_source_handle(new_edge, flow_data["nodes"], flow_data["edges"]) assert updated_edge["source"] == "last_node" assert updated_edge["data"]["sourceHandle"]["id"] == "last_node" diff --git a/tests/test_llms_template.py b/tests/test_llms_template.py index 78131cb05..e66c8c650 100644 --- a/tests/test_llms_template.py +++ b/tests/test_llms_template.py @@ -243,7 +243,7 @@ def test_openai(client: TestClient, logged_in_headers): "placeholder": "", "show": False, "multiline": False, - "value": 6, + "value": 2, "password": False, "name": "max_retries", "type": "int", @@ -385,7 +385,7 @@ def test_chat_open_ai(client: TestClient, logged_in_headers): "placeholder": "", "show": False, "multiline": False, - "value": 6, + "value": 2, "password": False, "name": "max_retries", "type": "int", diff --git a/tests/test_login.py b/tests/test_login.py index f505f4100..399c7b761 100644 --- a/tests/test_login.py +++ b/tests/test_login.py @@ -1,5 +1,5 @@ from langflow.services.database.utils import session_getter -from langflow.services.getters import get_db_service +from langflow.services.deps import get_db_service import pytest from langflow.services.database.models.user import User from langflow.services.auth.utils import get_password_hash @@ -9,9 +9,7 @@ from langflow.services.auth.utils import get_password_hash def test_user(): return User( username="testuser", - password=get_password_hash( - "testpassword" - ), # Assuming password needs to be hashed + password=get_password_hash("testpassword"), # Assuming password needs to be hashed is_active=True, is_superuser=False, ) @@ -23,17 +21,13 @@ def test_login_successful(client, test_user): session.add(test_user) session.commit() - response = client.post( - "api/v1/login", data={"username": "testuser", "password": "testpassword"} - ) + response = client.post("api/v1/login", data={"username": "testuser", "password": "testpassword"}) assert response.status_code == 200 assert "access_token" in response.json() def test_login_unsuccessful_wrong_username(client): - response = client.post( - "api/v1/login", data={"username": "wrongusername", "password": "testpassword"} - ) + response = client.post("api/v1/login", data={"username": "wrongusername", "password": "testpassword"}) assert response.status_code == 401 assert response.json()["detail"] == "Incorrect username or password" @@ -43,8 +37,6 @@ def test_login_unsuccessful_wrong_password(client, test_user, session): session.add(test_user) session.commit() - response = client.post( - "api/v1/login", data={"username": "testuser", "password": "wrongpassword"} - ) + response = client.post("api/v1/login", data={"username": "testuser", "password": "wrongpassword"}) assert response.status_code == 401 assert response.json()["detail"] == "Incorrect username or password" diff --git a/tests/test_process.py b/tests/test_process.py index 0588800dc..29ec267a1 100644 --- a/tests/test_process.py +++ b/tests/test_process.py @@ -1,5 +1,5 @@ from langflow.processing.process import process_tweaks -from langflow.services.getters import get_session_service +from langflow.services.deps import get_session_service def test_no_tweaks(): diff --git a/tests/test_prompts_template.py b/tests/test_prompts_template.py index 2fc161c6d..b9e55ce77 100644 --- a/tests/test_prompts_template.py +++ b/tests/test_prompts_template.py @@ -1,5 +1,5 @@ from fastapi.testclient import TestClient -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service def test_prompts_settings(client: TestClient, logged_in_headers): diff --git a/tests/test_setup_superuser.py b/tests/test_setup_superuser.py index 8cdcfc0c8..95a8deffb 100644 --- a/tests/test_setup_superuser.py +++ b/tests/test_setup_superuser.py @@ -1,17 +1,12 @@ -from unittest.mock import patch, MagicMock +from unittest.mock import MagicMock, patch + from langflow.services.database.models.user.user import User -from langflow.services.settings.constants import ( - DEFAULT_SUPERUSER, - DEFAULT_SUPERUSER_PASSWORD, -) -from langflow.services.utils import ( - teardown_superuser, -) +from langflow.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD +from langflow.services.utils import teardown_superuser - -# @patch("langflow.services.getters.get_session") +# @patch("langflow.services.deps.get_session") # @patch("langflow.services.utils.create_super_user") -# @patch("langflow.services.getters.get_settings_service") +# @patch("langflow.services.deps.get_settings_service") # # @patch("langflow.services.utils.verify_password") # def test_setup_superuser( # mock_get_session, mock_create_super_user, mock_get_settings_service @@ -92,11 +87,9 @@ from langflow.services.utils import ( # assert str(actual_expr) == str(expected_expr) -@patch("langflow.services.getters.get_settings_service") -@patch("langflow.services.getters.get_session") -def test_teardown_superuser_default_superuser( - mock_get_session, mock_get_settings_service -): +@patch("langflow.services.deps.get_settings_service") +@patch("langflow.services.deps.get_session") +def test_teardown_superuser_default_superuser(mock_get_session, mock_get_settings_service): mock_settings_service = MagicMock() mock_settings_service.auth_settings.AUTO_LOGIN = True mock_settings_service.auth_settings.SUPERUSER = DEFAULT_SUPERUSER @@ -120,11 +113,9 @@ def test_teardown_superuser_default_superuser( mock_session.commit.assert_called_once() -@patch("langflow.services.getters.get_settings_service") -@patch("langflow.services.getters.get_session") -def test_teardown_superuser_no_default_superuser( - mock_get_session, mock_get_settings_service -): +@patch("langflow.services.deps.get_settings_service") +@patch("langflow.services.deps.get_session") +def test_teardown_superuser_no_default_superuser(mock_get_session, mock_get_settings_service): ADMIN_USER_NAME = "admin_user" mock_settings_service = MagicMock() mock_settings_service.auth_settings.AUTO_LOGIN = False diff --git a/tests/test_template.py b/tests/test_template.py index 6e85f987c..e01d9e65b 100644 --- a/tests/test_template.py +++ b/tests/test_template.py @@ -65,9 +65,7 @@ def test_build_template_from_function(): assert "base_classes" in result # Test with add_function=True - result_with_function = build_template_from_function( - "ExampleClass1", type_to_loader_dict, add_function=True - ) + result_with_function = build_template_from_function("ExampleClass1", type_to_loader_dict, add_function=True) assert result_with_function is not None assert "Callable" in result_with_function["base_classes"] diff --git a/tests/test_user.py b/tests/test_user.py index 14e99ec11..e93b594cc 100644 --- a/tests/test_user.py +++ b/tests/test_user.py @@ -1,11 +1,11 @@ from datetime import datetime -from langflow.services.auth.utils import create_super_user, get_password_hash +import pytest +from langflow.services.auth.utils import create_super_user, get_password_hash +from langflow.services.database.models.user import UserUpdate from langflow.services.database.models.user.user import User from langflow.services.database.utils import session_getter -from langflow.services.getters import get_db_service, get_settings_service -import pytest -from langflow.services.database.models.user import UserUpdate +from langflow.services.deps import get_db_service, get_settings_service @pytest.fixture @@ -85,15 +85,11 @@ def test_deactivated_user_cannot_access(client, deactivated_user, logged_in_head assert response.json()["detail"] == "The user doesn't have enough privileges" -def test_data_consistency_after_update( - client, active_user, logged_in_headers, super_user_headers -): +def test_data_consistency_after_update(client, active_user, logged_in_headers, super_user_headers): user_id = active_user.id update_data = UserUpdate(is_active=False) - response = client.patch( - f"/api/v1/users/{user_id}", json=update_data.dict(), headers=super_user_headers - ) + response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=super_user_headers) assert response.status_code == 200, response.json() # Fetch the updated user from the database @@ -167,17 +163,13 @@ def test_patch_user(client, active_user, logged_in_headers): username="newname", ) - response = client.patch( - f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers - ) + response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers) assert response.status_code == 200, response.json() update_data = UserUpdate( profile_image="new_image", ) - response = client.patch( - f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers - ) + response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers) assert response.status_code == 200, response.json() @@ -189,7 +181,7 @@ def test_patch_reset_password(client, active_user, logged_in_headers): response = client.patch( f"/api/v1/users/{user_id}/reset-password", - json=update_data.dict(), + json=update_data.model_dump(), headers=logged_in_headers, ) assert response.status_code == 200, response.json() @@ -205,9 +197,7 @@ def test_patch_user_wrong_id(client, active_user, logged_in_headers): username="newname", ) - response = client.patch( - f"/api/v1/users/{user_id}", json=update_data.dict(), headers=logged_in_headers - ) + response = client.patch(f"/api/v1/users/{user_id}", json=update_data.model_dump(), headers=logged_in_headers) assert response.status_code == 422, response.json() assert response.json() == { "detail": [ diff --git a/tests/test_vectorstore_template.py b/tests/test_vectorstore_template.py index 9dd131dbc..3b5c7ed42 100644 --- a/tests/test_vectorstore_template.py +++ b/tests/test_vectorstore_template.py @@ -1,5 +1,5 @@ from fastapi.testclient import TestClient -from langflow.services.getters import get_settings_service +from langflow.services.deps import get_settings_service # check that all agents are in settings.agents diff --git a/tests/test_websocket.py b/tests/test_websocket.py index 5016eb704..c4c9ee322 100644 --- a/tests/test_websocket.py +++ b/tests/test_websocket.py @@ -31,9 +31,7 @@ def test_websocket_endpoint(client: TestClient, active_user, logged_in_headers): # Assuming your websocket_endpoint uses chat_service which caches data from stream_build access_token = logged_in_headers["Authorization"].split(" ")[1] with pytest.raises(WebSocketDisconnect): - with client.websocket_connect( - f"api/v1/chat/non_existing_client_id?token={access_token}" - ) as websocket: + with client.websocket_connect(f"api/v1/chat/non_existing_client_id?token={access_token}") as websocket: websocket.send_json({"type": "test"}) data = websocket.receive_json() assert "Please, build the flow before sending messages" in data["message"]