diff --git a/.infra/rdev/values.yaml b/.infra/rdev/values.yaml index 2ad44ad17..47cd306f7 100644 --- a/.infra/rdev/values.yaml +++ b/.infra/rdev/values.yaml @@ -2,7 +2,7 @@ stack: services: explorer: image: - tag: sha-3940dd93 + tag: sha-47925f47 replicaCount: 1 env: # env vars common to all deployment stages diff --git a/client/package-lock.json b/client/package-lock.json index 7d71df696..9bddc8f36 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -41,11 +41,14 @@ "memoize-one": "^5.1.1", "openseadragon": "^4.1.1", "pako": "^2.0.3", + "re-resizable": "^6.10.1", "react": "^18.3.1", "react-async": "^10.0.1", "react-dom": "^18.3.1", + "react-draggable": "^4.4.5", "react-flip-toolkit": "^7.0.12", "react-icons": "^4.2.0", + "react-markdown": "^9.0.1", "react-redux": "^7.2.0", "redux": "^4.0.5", "redux-thunk": "^2.3.0", @@ -53,7 +56,8 @@ "regl": "^2.1.0", "sha1": "^1.1.1", "tinyqueue": "^2.0.3", - "whatwg-fetch": "^3.2.0" + "whatwg-fetch": "^3.2.0", + "zod": "^3.23.8" }, "devDependencies": { "@babel/core": "^7.13.16", @@ -212,41 +216,6 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-crypto/sha256-js": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", @@ -278,41 +247,6 @@ "tslib": "^2.6.2" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.637.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.637.0.tgz", @@ -365,6 +299,30 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-sso": { "version": "3.637.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.637.0.tgz", @@ -465,6 +423,54 @@ "@aws-sdk/client-sts": "^3.637.0" } }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso-oidc/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/client-sts": { "version": "3.637.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.637.0.tgz", @@ -515,6 +521,30 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/client-sts/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/core": { "version": "3.635.0", "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.635.0.tgz", @@ -535,6 +565,48 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/core/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/signature-v4": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.2.1.tgz", + "integrity": "sha512-NsV1jF4EvmO5wqmaSzlnTVetemBS3FZHdyc5CExbDljcyJCEEkJr8ANu2JvtNbVg/9MvKAWV44kTrGS+Pi4INg==", + "dependencies": { + "@smithy/is-array-buffer": "^3.0.0", + "@smithy/protocol-http": "^4.1.5", + "@smithy/types": "^3.6.0", + "@smithy/util-hex-encoding": "^3.0.0", + "@smithy/util-middleware": "^3.0.8", + "@smithy/util-uri-escape": "^3.0.0", + "@smithy/util-utf8": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-sdk/core/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-env": { "version": "3.620.1", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.620.1.tgz", @@ -568,6 +640,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/credential-provider-ini": { "version": "3.637.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.637.0.tgz", @@ -677,6 +761,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-host-header/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-logger": { "version": "3.609.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.609.0.tgz", @@ -704,6 +800,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-recursion-detection/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/middleware-user-agent": { "version": "3.637.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.637.0.tgz", @@ -719,6 +827,18 @@ "node": ">=16.0.0" } }, + "node_modules/@aws-sdk/middleware-user-agent/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.614.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.614.0.tgz", @@ -6742,6 +6862,30 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/core/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/core/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/credential-provider-imds": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-3.2.0.tgz", @@ -6769,6 +6913,18 @@ "tslib": "^2.6.2" } }, + "node_modules/@smithy/fetch-http-handler/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/hash-node": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-3.0.3.tgz", @@ -6783,6 +6939,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/hash-node/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/invalid-dependency": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-3.0.3.tgz", @@ -6816,6 +6984,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/middleware-content-length/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/middleware-endpoint": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz", @@ -6852,19 +7032,31 @@ "node": ">=16.0.0" } }, - "node_modules/@smithy/middleware-serde": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", - "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "node_modules/@smithy/middleware-retry/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/middleware-stack": { + "node_modules/@smithy/middleware-serde": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz", + "integrity": "sha512-puUbyJQBcg9eSErFXjKNiGILJGtiqmuuNKEYNYfUD57fUl4i9+mfmThtQhvFXU0hCVG0iEJhvQUipUf+/SsFdA==", + "dependencies": { + "@smithy/types": "^3.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz", "integrity": "sha512-r4klY9nFudB0r9UdSMaGSyjyQK5adUyPnQN/ZM6M75phTxOdnc/AhpvGD1fQUvgmqjQEBGCwpnPbDm8pH5PapA==", @@ -6905,22 +7097,22 @@ "node": ">=16.0.0" } }, - "node_modules/@smithy/property-provider": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", - "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", + "node_modules/@smithy/node-http-handler/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { "node": ">=16.0.0" } }, - "node_modules/@smithy/protocol-http": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.0.tgz", - "integrity": "sha512-dPVoHYQ2wcHooGXg3LQisa1hH0e4y0pAddPMeeUPipI1tEOqL6A4N0/G7abeq+K8wrwSgjk4C0wnD1XZpJm5aA==", + "node_modules/@smithy/property-provider": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-3.1.3.tgz", + "integrity": "sha512-zahyOVR9Q4PEoguJ/NrFP4O7SMAfYO1HLhB18M+q+Z4KFd4V2obiMnlVoUFzFLSPeVt1POyNWneHHrZaTMoc/g==", "dependencies": { "@smithy/types": "^3.3.0", "tslib": "^2.6.2" @@ -6977,24 +7169,6 @@ "node": ">=16.0.0" } }, - "node_modules/@smithy/signature-v4": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-4.1.0.tgz", - "integrity": "sha512-aRryp2XNZeRcOtuJoxjydO6QTaVhxx/vjaR+gx7ZjaFgrgPRyZ3HCTbfwqYj6ZWEBHkCSUfcaymKPURaByukag==", - "dependencies": { - "@smithy/is-array-buffer": "^3.0.0", - "@smithy/protocol-http": "^4.1.0", - "@smithy/types": "^3.3.0", - "@smithy/util-hex-encoding": "^3.0.0", - "@smithy/util-middleware": "^3.0.3", - "@smithy/util-uri-escape": "^3.0.0", - "@smithy/util-utf8": "^3.0.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@smithy/smithy-client": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-3.2.0.tgz", @@ -7011,10 +7185,22 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/smithy-client/node_modules/@smithy/protocol-http": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-4.1.5.tgz", + "integrity": "sha512-hsjtwpIemmCkm3ZV5fd/T0bPIugW1gJXwZ/hpuVubt2hEUApIoUTrf6qIdh9MAWlw0vjMrA1ztJLAwtNaZogvg==", + "dependencies": { + "@smithy/types": "^3.6.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/types": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.3.0.tgz", - "integrity": "sha512-IxvBBCTFDHbVoK7zIxqA1ZOdc4QfM5HM7rGleCuHi7L1wnKv5Pn69xXJQ9hgxH60ZVygH9/JG0jRgtUncE3QUA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-3.6.0.tgz", + "integrity": "sha512-8VXK/KzOHefoC65yRgCn5vG1cysPJjHnOVt9d0ybFQSmJgQj152vMn4EkYhGuaOmnnZvCPav/KnYyE6/KsNZ2w==", "dependencies": { "tslib": "^2.6.2" }, @@ -7045,6 +7231,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-base64/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-body-length-browser": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-3.0.0.tgz", @@ -7144,11 +7342,11 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.3.tgz", - "integrity": "sha512-l+StyYYK/eO3DlVPbU+4Bi06Jjal+PFLSMmlWM1BEwyLxZ3aKkf1ROnoIakfaA7mC6uw3ny7JBkau4Yc+5zfWw==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-3.0.8.tgz", + "integrity": "sha512-p7iYAPaQjoeM+AKABpYWeDdtwQNxasr4aXQEA/OmbOaug9V0odRVDy3Wx4ci8soljE/JXQo+abV0qZpW8NX0yA==", "dependencies": { - "@smithy/types": "^3.3.0", + "@smithy/types": "^3.6.0", "tslib": "^2.6.2" }, "engines": { @@ -7186,6 +7384,18 @@ "node": ">=16.0.0" } }, + "node_modules/@smithy/util-stream/node_modules/@smithy/util-utf8": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", + "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "dependencies": { + "@smithy/util-buffer-from": "^3.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@smithy/util-uri-escape": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz", @@ -7198,15 +7408,38 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-3.0.0.tgz", - "integrity": "sha512-rUeT12bxFnplYDe815GXbq/oixEGHfRFFtcTF3YdDi/JaENIM6aSYYLJydG83UNzLXeRI5K8abYd/8Sp/QM0kA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", "dependencies": { - "@smithy/util-buffer-from": "^3.0.0", + "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" }, "engines": { - "node": ">=16.0.0" + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-utf8/node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" } }, "node_modules/@storybook/addon-actions": { @@ -9727,6 +9960,14 @@ "@types/d3-selection": "^1" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/detect-port": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/detect-port/-/detect-port-1.3.5.tgz", @@ -9760,8 +10001,15 @@ "node_modules/@types/estree": { "version": "0.0.51", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } }, "node_modules/@types/express": { "version": "4.17.21", @@ -9838,7 +10086,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dev": true, "dependencies": { "@types/unist": "*" } @@ -10020,6 +10267,14 @@ "@types/lodash": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -10038,6 +10293,11 @@ "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true }, + "node_modules/@types/ms": { + "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": "20.16.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", @@ -10185,8 +10445,7 @@ "node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", - "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", - "dev": true + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" }, "node_modules/@types/uuid": { "version": "9.0.8", @@ -10699,8 +10958,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@vitest/expect": { "version": "0.34.7", @@ -11877,6 +12135,15 @@ "@babel/core": "^7.10.5" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -12281,6 +12548,15 @@ "node": ">=4" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chai": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", @@ -12334,6 +12610,42 @@ "tslib": "^2.0.3" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -12738,6 +13050,15 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -13444,27 +13765,6 @@ "d3-selection": "1" } }, - "node_modules/d3-dsv": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz", - "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==", - "dependencies": { - "commander": "2", - "iconv-lite": "0.4", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json", - "csv2tsv": "bin/dsv2dsv", - "dsv2dsv": "bin/dsv2dsv", - "dsv2json": "bin/dsv2json", - "json2csv": "bin/json2dsv", - "json2dsv": "bin/json2dsv", - "json2tsv": "bin/json2dsv", - "tsv2csv": "bin/dsv2dsv", - "tsv2json": "bin/dsv2json" - } - }, "node_modules/d3-ease": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.3.tgz", @@ -13543,6 +13843,27 @@ "xmlhttprequest": "1" } }, + "node_modules/d3-request/node_modules/d3-dsv": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", + "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", + "dependencies": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" + } + }, "node_modules/d3-scale": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-1.0.7.tgz", @@ -13627,6 +13948,27 @@ "d3-transition": "1" } }, + "node_modules/d3/node_modules/d3-dsv": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.0.8.tgz", + "integrity": "sha512-IVCJpQ+YGe3qu6odkPQI0KPqfxkhbP/oM1XhhE/DFiYmcXKfCRub4KXyiuehV1d4drjWVXHUWx4gHqhdZb6n/A==", + "dependencies": { + "commander": "2", + "iconv-lite": "0.4", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json", + "csv2tsv": "bin/dsv2dsv", + "dsv2dsv": "bin/dsv2dsv", + "dsv2json": "bin/dsv2json", + "json2csv": "bin/json2dsv", + "json2dsv": "bin/json2dsv", + "json2tsv": "bin/json2dsv", + "tsv2csv": "bin/dsv2dsv", + "tsv2json": "bin/dsv2json" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -13700,6 +14042,18 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dedent": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", @@ -13953,7 +14307,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "engines": { "node": ">=6" } @@ -14021,6 +14374,18 @@ "node": ">= 4.0.0" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -15586,6 +15951,15 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -15724,8 +16098,7 @@ "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "node_modules/fast-deep-equal": { "version": "3.1.3", @@ -16878,21 +17251,78 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", - "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", - "dev": true, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.2.tgz", + "integrity": "sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==", "dependencies": { - "@types/hast": "^3.0.0" + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-object": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/unified" } }, - "node_modules/he": { - "version": "1.2.0", + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==" + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/hast-util-to-jsx-runtime/node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/hast-util-to-string": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.0.tgz", + "integrity": "sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==", + "dev": true, + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/he": { + "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, @@ -16991,6 +17421,15 @@ "resolved": "https://registry.npmjs.org/html-to-image/-/html-to-image-1.11.11.tgz", "integrity": "sha512-9gux8QhvjRO/erSnDPv28noDZcPZmYE7e1vFsBLKLlRlKDSqNJYebj6Qz1TGd5lsRV+X+xYyjCKjuZdABinWjA==" }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -17326,6 +17765,11 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -17376,6 +17820,28 @@ "node": ">=8" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -17524,6 +17990,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-docker": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", @@ -17596,6 +18071,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", @@ -17710,6 +18194,17 @@ "node": ">=6" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -18730,6 +19225,15 @@ "node": ">=8" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -18847,6 +19351,278 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/mdast-util-from-markdown/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.3.tgz", + "integrity": "sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/mdast-util-phrasing/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/mdast-util-to-markdown/node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-string": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.1.0.tgz", @@ -18895,42 +19671,463 @@ "integrity": "sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==", "dev": true, "dependencies": { - "map-or-similar": "^1.5.0" + "map-or-similar": "^1.5.0" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-types": "^2.0.0" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "dev": true, - "engines": { - "node": ">= 0.6" + "node_modules/micromark-util-subtokenize": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.2.tgz", + "integrity": "sha512-xKxhkB62vwHUuuxHe9Xqty3UaAsizV2YKq5OV344u3hFBbf8zIYrhYOWhAQb94MtMPkjTOzzjJ/hid9/dR5vFA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/micromatch": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", @@ -19865,6 +21062,25 @@ "node": ">=0.10.0" } }, + "node_modules/parse-entities": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.1.tgz", + "integrity": "sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -20826,6 +22042,15 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/property-information": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", + "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -20957,6 +22182,15 @@ "node": ">= 0.8" } }, + "node_modules/re-resizable": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/re-resizable/-/re-resizable-6.10.1.tgz", + "integrity": "sha512-m33nSWRH57UZLmep5M/LatkZ2NRqimVD/bOOpvymw5Zf33+eTSEixsUugscOZzAtK0/nx+OSuOf8VbKJx/4ptw==", + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -21049,6 +22283,27 @@ "react": "^18.3.1" } }, + "node_modules/react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-draggable/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, "node_modules/react-element-to-jsx-string": { "version": "15.0.0", "resolved": "https://registry.npmjs.org/react-element-to-jsx-string/-/react-element-to-jsx-string-15.0.0.tgz", @@ -21114,6 +22369,75 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, + "node_modules/react-markdown": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.0.1.tgz", + "integrity": "sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-markdown/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/react-markdown/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/react-markdown/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/react-popper": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-2.3.0.tgz", @@ -21788,6 +23112,37 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.1.tgz", + "integrity": "sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-slug": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/remark-slug/-/remark-slug-6.1.0.tgz", @@ -22983,6 +24338,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stringify-object": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", @@ -23094,6 +24462,14 @@ "webpack": "^5.0.0" } }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -23719,6 +25095,24 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "dev": true }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-dedent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", @@ -24017,6 +25411,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unified/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, "node_modules/unique-string": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", @@ -24042,6 +25459,40 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, "node_modules/unist-util-visit": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-2.0.3.tgz", @@ -24375,6 +25826,42 @@ "node": ">= 0.8" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/vfile/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, "node_modules/walk-up-path": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-3.0.1.tgz", @@ -25010,6 +26497,23 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/client/package.json b/client/package.json index d45f13ae7..be8a932f4 100644 --- a/client/package.json +++ b/client/package.json @@ -85,11 +85,14 @@ "memoize-one": "^5.1.1", "openseadragon": "^4.1.1", "pako": "^2.0.3", + "re-resizable": "^6.10.1", "react": "^18.3.1", "react-async": "^10.0.1", "react-dom": "^18.3.1", + "react-draggable": "^4.4.5", "react-flip-toolkit": "^7.0.12", "react-icons": "^4.2.0", + "react-markdown": "^9.0.1", "react-redux": "^7.2.0", "redux": "^4.0.5", "redux-thunk": "^2.3.0", @@ -97,7 +100,8 @@ "regl": "^2.1.0", "sha1": "^1.1.1", "tinyqueue": "^2.0.3", - "whatwg-fetch": "^3.2.0" + "whatwg-fetch": "^3.2.0", + "zod": "^3.23.8" }, "devDependencies": { "@babel/core": "^7.13.16", diff --git a/client/src/actions/index.ts b/client/src/actions/index.ts index d1e8bbf05..5e0782f4f 100644 --- a/client/src/actions/index.ts +++ b/client/src/actions/index.ts @@ -585,6 +585,11 @@ function fetchJson(pathAndQuery: string, apiPrefix?: string): Promise { ) as Promise; } +export const revertToAction = (targetCount: number) => ({ + type: "@@undoable/revert-to-action", + targetCount, +}); + export default { doInitialDataLoad, requestDifferentialExpression, diff --git a/client/src/components/Agent/AgentComponent.tsx b/client/src/components/Agent/AgentComponent.tsx new file mode 100644 index 000000000..dd85f289a --- /dev/null +++ b/client/src/components/Agent/AgentComponent.tsx @@ -0,0 +1,442 @@ +import React, { + FC, + useState, + useMemo, + FormEvent, + useRef, + useEffect, +} from "react"; +import { useDispatch, useStore, useSelector } from "react-redux"; +import ReactMarkdown from "react-markdown"; +import Draggable from "react-draggable"; +import { Resizable } from "re-resizable"; +import { createUITools } from "./tools"; +import { AgentRunner, ChatMessage } from "./AgentRunner"; +import { MessageType } from "./agent"; +import { AppDispatch, RootState } from "../../reducers"; +import { revertToAction } from "../../actions"; + +export const AgentComponent: FC = () => { + const dispatch = useDispatch(); + const { getState } = useStore(); + const [input, setInput] = useState(""); + const [chatHistory, setChatHistory] = useState([]); + const [isLoading, setIsLoading] = useState(false); + const [isDrawerOpen, setIsDrawerOpen] = useState(true); + const inputRef = useRef(null); + const [firstActionCount, setFirstActionCount] = useState(0); + + const cardRef = useRef(null); + const chatHistoryRef = useRef(null); + const [editingMessageId, setEditingMessageId] = useState(null); + const [editText, setEditText] = useState(""); + const editInputRef = useRef(null); + const [position, setPosition] = useState({ x: 0, y: 0 }); + const [size, setSize] = useState({ width: 400, height: 300 }); + + const agentRunner = useMemo(() => { + const tools = createUITools(dispatch, getState); + return new AgentRunner(tools); + }, [dispatch, getState]); + + const actionCount = useSelector((state: RootState) => state.actionCount); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === "Escape") { + inputRef.current?.blur(); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => { + document.removeEventListener("keydown", handleKeyDown); + }; + }, []); + + const scrollToBottom = () => { + if (chatHistoryRef.current) { + chatHistoryRef.current.scrollTop = chatHistoryRef.current.scrollHeight; + } + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + if (!input.trim() || isLoading) return; + + setIsLoading(true); + + const userMessage = { + role: "user" as const, + content: input, + timestamp: Date.now(), + type: MessageType.Message, + messageId: Date.now(), + actionCount, + }; + if (agentRunner.chatHistory.length === 0) { + setFirstActionCount(actionCount); + } + + agentRunner.chatHistory.push(userMessage); + setChatHistory(agentRunner.chatHistory); + setTimeout(scrollToBottom, 100); + + const currentInput = input; + setInput(""); + + try { + await agentRunner.processQuery(currentInput); + const lastMessage = + agentRunner.chatHistory[agentRunner.chatHistory.length - 1]; + if (lastMessage.role === "assistant") { + lastMessage.actionCount = actionCount; + } + setChatHistory(agentRunner.chatHistory); + setTimeout(scrollToBottom, 0); + } catch (error) { + console.error("Agent Runner Error:", error); + const errorMessage = { + role: "assistant" as const, + content: "Sorry, something went wrong. Please try again.", + timestamp: Date.now(), + type: MessageType.Message, + messageId: Date.now(), + actionCount, + }; + agentRunner.chatHistory.push(errorMessage); + setChatHistory(agentRunner.chatHistory); + setTimeout(scrollToBottom, 100); + } finally { + setIsLoading(false); + setTimeout(() => { + inputRef.current?.focus(); + }, 0); + } + }; + + const handleReset = () => { + dispatch(revertToAction(firstActionCount)); + setChatHistory([]); + agentRunner.clearHistory(); + setInput(""); + }; + + const handleEditStart = (message: ChatMessage) => { + setEditingMessageId(message.messageId); + setEditText(message.content); + setTimeout(() => { + editInputRef.current?.focus(); + }, 0); + }; + + const handleEditSubmit = async (message: ChatMessage) => { + dispatch(revertToAction(message.actionCount)); + agentRunner.replaceMessage(message.messageId, editText, actionCount); + setChatHistory(agentRunner.chatHistory); + + setEditingMessageId(null); + setEditText(""); + setIsLoading(true); + + try { + await agentRunner.processQuery(editText, 20, true); + setChatHistory(agentRunner.chatHistory); + setTimeout(scrollToBottom, 100); + } catch (error) { + console.error("Agent Runner Error:", error); + const errorMessage = { + role: "assistant" as const, + content: "Sorry, something went wrong. Please try again.", + timestamp: Date.now(), + type: MessageType.Message, + messageId: Date.now(), + actionCount, + }; + agentRunner.chatHistory.push(errorMessage); + setChatHistory(agentRunner.chatHistory); + setTimeout(scrollToBottom, 100); + } finally { + setIsLoading(false); + } + }; + + return ( + setPosition({ x: data.x, y: data.y })} + > + { + setSize({ + width: size.width + d.width, + height: size.height + d.height, + }); + }} + minWidth={isDrawerOpen ? 200 : 20} + minHeight={isDrawerOpen ? 200 : 0} + maxWidth={isDrawerOpen ? "95vw" : 200} + maxHeight={isDrawerOpen ? "95vh" : 60} + > +
+
+ + CELL x AI + + +
+
+
+ {chatHistory.map((message) => ( +
+ {message.role === "user" && ( + + )} +
+ {editingMessageId === message.messageId ? ( +
{ + e.preventDefault(); + handleEditSubmit(message).catch(console.error); + }} + style={{ + display: "flex", + gap: "8px", + padding: "8px 0", + width: "100%", + }} + > + setEditText(e.target.value)} + style={{ + padding: "8px 12px", + borderRadius: "4px", + border: "1px solid #ddd", + color: "black", + width: "100%", + }} + onKeyDown={(e) => { + if (e.key === "Escape") { + setEditingMessageId(null); + setEditText(""); + } + }} + /> +
+ ) : ( + {message.content} + )} +
+
+ ))} +
+
+
+
+ + setInput(e.target.value)} + placeholder="Type your query..." + disabled={isLoading} + style={{ + flex: 1, + padding: "12px", + borderRadius: "4px", + border: "1px solid #ddd", + width: "100%", + }} + /> + +
+
+
+
+
+ ); +}; diff --git a/client/src/components/Agent/AgentRunner.ts b/client/src/components/Agent/AgentRunner.ts new file mode 100644 index 000000000..693613bde --- /dev/null +++ b/client/src/components/Agent/AgentRunner.ts @@ -0,0 +1,152 @@ +import { UITool } from "./UITool"; +import { + AgentMessage, + BaseMessage, + getNextAgentStep, + MessageType, +} from "./agent"; + +export interface ChatMessage extends BaseMessage { + role: "user" | "assistant"; + content: string; + timestamp: number; + type: MessageType; + actionCount: number; +} + +// We need an intermediate message history PER QUERY and then we need a persistent chat history +// Which will contain the user's initial inputs and the agent's final outputs. +export class AgentRunner { + private messages: AgentMessage[] = []; + + public chatHistory: ChatMessage[] = []; + + private tools: Record; + + constructor(tools: UITool[]) { + this.tools = tools.reduce((acc, tool) => { + acc[tool.name] = tool; + return acc; + }, {} as Record); + } + + async processQuery( + userInput: string, + maxSteps = 20, + isEdit = false + ): Promise { + /* eslint-disable no-await-in-loop -- Steps must be executed sequentially */ + + const userMessageId = Date.now(); + + // Only add to internal messages array - chat history is handled by the component + if (!isEdit) { + this.messages.push({ + role: "user", + content: userInput, + type: MessageType.Message, + messageId: userMessageId, + }); + } + + let stepCount = 0; + while (stepCount < maxSteps) { + stepCount += 1; + const step = await getNextAgentStep(this.messages); + + if (step.type === "message" || step.type === "summary") { + const assistantMessageId = Date.now(); + const finalResponse = step.content ?? ""; + const assistantMessage = { + role: "assistant", + content: finalResponse, + timestamp: Date.now(), + type: step.type as MessageType, + messageId: assistantMessageId, + }; + + // Add to messages without timestamp + this.messages.push({ + role: assistantMessage.role as "user" | "assistant" | "function", + content: assistantMessage.content, + type: assistantMessage.type, + messageId: assistantMessageId, + }); + + // Add to chat history with timestamp + this.chatHistory.push(assistantMessage as ChatMessage); + return finalResponse; + } + + if (step.type === "tool" && step.tool) { + const toolMessageId = Date.now(); + const tool = this.tools[step.tool.name]; + if (!tool) { + throw new Error(`Unknown tool: ${step.tool.name}`); + } + + const result = await tool.invoke(JSON.stringify(step.tool.result)); + this.messages.push({ + role: "function", + name: step.tool.name, + content: JSON.stringify(step.tool.result), + type: MessageType.Tool, + messageId: toolMessageId, + }); + this.messages.push({ + role: "assistant", + content: result, + type: MessageType.Message, + messageId: toolMessageId + 1, + }); + } + } + const timeoutMessage = `Conversation exceeded maximum of ${maxSteps} steps`; + this.messages.push({ + role: "assistant", + content: timeoutMessage, + type: MessageType.Message, + messageId: Date.now(), + }); + /* eslint-enable no-await-in-loop -- Steps must be executed sequentially */ + + return timeoutMessage; + } + + clearHistory(): void { + this.messages = []; + this.chatHistory = []; + } + + replaceMessage( + messageId: number, + newContent: string, + actionCount: number + ): void { + // Replace in messages array + const messageIndex = this.messages.findIndex( + (m) => m.messageId === messageId + ); + if (messageIndex !== -1) { + this.messages = this.messages.slice(0, messageIndex + 1); + this.messages[messageIndex] = { + ...this.messages[messageIndex], + content: newContent, + }; + } + + // Replace in chat history + const chatIndex = this.chatHistory.findIndex( + (m) => m.messageId === messageId + ); + if (chatIndex !== -1) { + this.chatHistory = this.chatHistory.slice(0, chatIndex + 1); + this.chatHistory[chatIndex] = { + ...this.chatHistory[chatIndex], + content: newContent, + timestamp: Date.now(), + actionCount, + }; + } + } +} diff --git a/client/src/components/Agent/UITool.ts b/client/src/components/Agent/UITool.ts new file mode 100644 index 000000000..59a0a8a85 --- /dev/null +++ b/client/src/components/Agent/UITool.ts @@ -0,0 +1,11 @@ +export class UITool { + constructor( + public name: string, + private action: (args?: Record) => Promise + ) {} + + async invoke(argsJson: string): Promise { + const args = argsJson ? JSON.parse(argsJson) : undefined; + return this.action(args); + } +} diff --git a/client/src/components/Agent/actions.ts b/client/src/components/Agent/actions.ts new file mode 100644 index 000000000..a5e9f0e6f --- /dev/null +++ b/client/src/components/Agent/actions.ts @@ -0,0 +1,392 @@ +import actions from "actions"; +import { Dataframe } from "util/dataframe"; +import { isDataframeDictEncodedColumn } from "util/dataframe/types"; +import { Field } from "common/types/schema"; +import { Query } from "annoMatrix/query"; +import { AppDispatch, GetState } from "../../reducers"; +import { subsetAction, resetSubsetAction } from "../../actions/viewStack"; + +export const performSubset = + () => (dispatch: AppDispatch, getState: GetState) => { + const state = getState(); + const crossfilter = state.obsCrossfilter; + if (!crossfilter) return; + const selectedCount = crossfilter.countSelected(); + const subsetPossible = + selectedCount !== 0 && selectedCount !== crossfilter.size(); + + if (subsetPossible) { + dispatch(subsetAction()); + } + }; + +export const performUnsubset = () => (dispatch: AppDispatch) => { + dispatch(resetSubsetAction()); +}; + +export const performCategoricalSelection = + (args: Record) => + async (dispatch: AppDispatch, getState: GetState): Promise => { + if (args?.error) { + return `${args.error}. I will halt execution and report this error to the user.`; + } + + const { annoMatrix, obsCrossfilter } = getState(); + const { schema } = annoMatrix; + + const columnName = args.column_name; + const colSchema = schema.annotations.obsByName[columnName]; + const { categories: allCategoryValues } = colSchema; + const categoryValue = args.category_value; + + const [categoryData]: [Dataframe] = await Promise.all([ + annoMatrix.fetch(Field.obs, columnName), + ]); + + // our data + const column = categoryData.icol(0); + + const labelName = isDataframeDictEncodedColumn(column) + ? column.invCodeMapping[categoryValue] + : categoryValue; + + if ( + obsCrossfilter.annoMatrix.nObs === + obsCrossfilter.obsCrossfilter.countSelected() + ) { + await dispatch( + actions.selectCategoricalAllMetadataAction( + "categorical metadata filter none of these", + columnName, + allCategoryValues, + false + ) + ); + } + + await dispatch( + actions.selectCategoricalMetadataAction( + "categorical metadata filter select", + columnName, + allCategoryValues, + labelName, + true + ) + ); + return `Performed categorical selection on ${args.category_value}.`; + }; + +export const performHistogramSelection = + (args: Record) => + async (dispatch: AppDispatch, getState: GetState): Promise => { + const { annoMatrix } = getState(); + let query; + let df: Dataframe; + const { schema } = annoMatrix; + const varIndex = schema?.annotations?.var?.index; + if (args.histogram_type === "geneset") { + if (args?.status === "need_available_genesets") { + const { genesets } = getState(); + const genesetNames = Array.from(genesets.genesets.keys()); + if (genesetNames.length === 0) { + return "There are no genesets available to perform selection on. I must now create a new geneset and then try again. I will continue execution."; + } + return `I have decided to perform histogram selection on a geneset. Here are the available geneset names: ${genesetNames}. I must select one of these genesets to perform selection on. If there are no matching genesets, I must create a new geneset. I will continue execution.`; + } + + const geneset = getState().genesets.genesets.get(args.histogram_name); + if (!geneset || geneset.genes.size === 0) { + return "The selected geneset is empty. Please select a different geneset or add genes to this one."; + } + + query = [ + Field.X, + { + summarize: { + method: "mean", + field: "var", + column: varIndex, + values: [...geneset.genes.keys()], + }, + }, + ]; + } else if (args.histogram_type === "metadata") { + query = [Field.obs, args.histogram_name]; + } else if (args.histogram_type === "gene") { + query = [ + "X", + { + where: { + column: varIndex, + field: Field.var, + value: args.histogram_name, + }, + }, + ]; + } else { + return "Invalid histogram type specified."; + } + + try { + df = await annoMatrix.fetch(query[0] as Field, query[1] as Query); + } catch (error) { + return `${args.histogram_type} is not a valid histogram type for ${args.histogram_name}. I must now try a different histogram type for the same data: ${args.histogram_name}. I will continue execution.`; + } + + const column = df.icol(0); + let summary; + + if (args.histogram_type === "geneset") { + const values = column.asArray() as number[]; + if (!values || !values.length) { + return "No valid data found for this geneset."; + } + + try { + const min = values.reduce((a, b) => Math.min(a, b), values[0]); + const max = values.reduce((a, b) => Math.max(a, b), values[0]); + summary = { min, max }; + } catch (error) { + console.error("Error calculating min/max:", error); + return "Error processing geneset data."; + } + } else { + summary = column.summarizeContinuous(); + } + + const { min, max } = summary; + + let lo = args.range_low ? parseFloat(args.range_low) : min; + let hi = args.range_high ? parseFloat(args.range_high) : max; + + lo = Math.max(lo, min); + hi = Math.min(hi, max); + + try { + await dispatch( + actions.selectContinuousMetadataAction( + "type continuous metadata histogram start", + query, + [lo, hi] + ) + ); + + await dispatch( + actions.selectContinuousMetadataAction( + "type continuous metadata histogram brush", + query, + [lo, hi] + ) + ); + + await dispatch( + actions.selectContinuousMetadataAction( + "type continuous metadata histogram end", + query, + [lo, hi] + ) + ); + } catch (error) { + console.log(error); + } + + return `Successfully performed histogram selection for ${args.histogram_name} with range ${lo} to ${hi}.`; + }; + +export const performExpandCategory = (args: Record) => () => { + const categoryElement = document.querySelector( + `[data-testid="category-${args.category_name}"]` + ); + if (categoryElement instanceof HTMLElement) { + const isNotExpandedElement = categoryElement.querySelector( + '[data-testid="category-expand-is-not-expanded"]' + ); + if (isNotExpandedElement) { + ( + categoryElement.querySelector( + `[data-testid="${args.category_name}:category-expand"]` + ) as HTMLElement + )?.click(); + } + } +}; + +// export const performPanning = +// () => (dispatch: AppDispatch, getState: GetState) => { +// console.log("Performed panning"); +// }; + +// export const performZoomIn = +// () => (dispatch: AppDispatch, getState: GetState) => { +// console.log("Performed zoom in"); +// }; + +// export const performZoomOut = +// () => (dispatch: AppDispatch, getState: GetState) => { +// console.log("Performed zoom out"); +// }; + +export const performColorByGene = + (args: Record) => + async (dispatch: AppDispatch, getState: GetState): Promise => { + const { gene } = args; + + // Get valid gene names from the matrix + const { + annoMatrix, + colors: { colorAccessor }, + } = getState(); + const { schema } = annoMatrix; + const varIndex = schema.annotations.var.index; + const df = await annoMatrix.fetch(Field.var, varIndex); + + // Check if there is a filtered column + const isFilteredCol = "feature_is_filtered"; + const isFiltered = + annoMatrix.getMatrixColumns(Field.var).includes(isFilteredCol) && + (await annoMatrix.fetch(Field.var, isFilteredCol)); + + let validGenes; + if (isFiltered) { + const isFilteredArray = isFiltered.col(isFilteredCol).asArray(); + validGenes = df + .col(varIndex) + .asArray() + .filter((_, index: number) => !isFilteredArray[index] && _) as string[]; + } else { + validGenes = df.col(varIndex).asArray() as string[]; + } + + // Verify gene is valid before proceeding + if (!validGenes.includes(gene)) { + return "The requested gene name is not present in the dataset. Please instruct the user to select a valid gene."; + } + if (colorAccessor === gene) { + return "The visualization is already colored by the requested gene."; + } + dispatch({ type: "single user defined gene start" }); + dispatch(actions.requestUserDefinedGene(gene)); + dispatch({ type: "single user defined gene complete" }); + dispatch(actions.requestSingleGeneExpressionCountsForColoringPOST(gene)); + return "Successfully colored the visualization by the requested gene."; + }; + +export const performExpandGene = + (args: Record) => async (dispatch: AppDispatch) => { + const { gene } = args; + dispatch({ type: "single user defined gene start" }); + dispatch(actions.requestUserDefinedGene(gene)); + dispatch({ type: "single user defined gene complete" }); + await new Promise((resolve) => setTimeout(resolve, 500)); + const element = document.querySelector(`[data-testid="maximize-${gene}"]`); + if (element instanceof HTMLElement) { + element.click(); + } + }; + +export const performColorByGeneset = + (args: Record) => + async (dispatch: AppDispatch, getState: GetState): Promise => { + const { geneset } = args; + if (args?.status === "need_available_genesets") { + const { genesets } = getState(); + const genesetNames = Array.from(genesets.genesets.keys()); + if (genesetNames.length === 0) { + return "There are no genesets available. I must now create a new geneset and then try again. I will continue execution."; + } + return `I have decided to perform the color by geneset action. If there are no matching genesets, I must create a new geneset. IMPORTANT: Here are the available geneset names: ${genesetNames}. I will continue execution.`; + } + + dispatch({ + type: "color by geneset mean expression", + geneset, + }); + return `Successfully colored by geneset: ${geneset}`; + }; + +export const performColorByMetadata = + (args: Record) => + async (dispatch: AppDispatch, getState: GetState): Promise => { + if (args?.error) { + return `${args.error}. I will halt execution and report this error to the user.`; + } + const { + colors: { colorAccessor }, + } = getState(); + // Determine if the metadata is categorical or continuous + const { annoMatrix } = getState(); + const { schema } = annoMatrix; + const metadata = schema.annotations.obsByName[args.metadata_name]; + const { categories: allCategoryValues } = metadata; + const isCategorical = allCategoryValues !== undefined; + + if (isCategorical) { + const categoryElement = document.querySelector( + `[data-testid="category-${args.metadata_name}"]` + ); + if (categoryElement instanceof HTMLElement) { + const isNotExpandedElement = categoryElement.querySelector( + '[data-testid="category-expand-is-not-expanded"]' + ); + if (isNotExpandedElement) { + ( + categoryElement.querySelector( + `[data-testid="${args.metadata_name}:category-expand"]` + ) as HTMLElement + )?.click(); + } + } + + if (colorAccessor === args.metadata_name) { + return `The visualization is already colored by the requested metadata: ${args.metadata_name}.`; + } + + dispatch({ + type: "color by categorical metadata", + colorAccessor: args.metadata_name, + }); + } else { + dispatch({ + type: "color by continuous metadata", + colorAccessor: args.metadata_name, + }); + } + return `Successfully colored by metadata: ${args.metadata_name}`; + }; + +export const performCreateGeneset = + (args: Record) => + async (dispatch: AppDispatch) => { + const genesetName = args.geneset_name as string; + const genesToPopulateGeneset = args.genes_to_populate_geneset as string[]; + const genesetDescription = args.geneset_description as string; + dispatch({ + type: "geneset: create", + genesetName: genesetName.trim(), + genesetDescription, + }); + + if (genesToPopulateGeneset && genesToPopulateGeneset.length > 0) { + const genesTmpHardcodedFormat = genesToPopulateGeneset.map((gene) => ({ + geneSymbol: gene.trim(), + })); + await dispatch( + actions.genesetAddGenes(genesetName, genesTmpHardcodedFormat) + ); + } + }; + +// export const performXYScatterplot = +// () => (dispatch: AppDispatch, getState: GetState) => { +// console.log("Performed XY scatterplot"); +// }; + +// export const performShowCellGuide = +// () => (dispatch: AppDispatch, getState: GetState) => { +// console.log("Performed show cell guide"); +// }; + +// export const performShowGeneCard = +// () => (dispatch: AppDispatch, getState: GetState) => { +// console.log("Performed show gene card"); +// }; diff --git a/client/src/components/Agent/agent.ts b/client/src/components/Agent/agent.ts new file mode 100644 index 000000000..7d3b925dd --- /dev/null +++ b/client/src/components/Agent/agent.ts @@ -0,0 +1,57 @@ +import { z } from "zod"; + +const AgentStepResponseSchema = z.object({ + type: z.enum(["tool", "message", "summary"]), + tool: z + .object({ + name: z.string(), + result: z.record( + z.string(), + z.union([z.string(), z.array(z.string()), z.number(), z.null()]) + ), + }) + .optional(), + content: z.string().optional(), +}); + +export type AgentStepResponse = z.infer; + +export interface BaseMessage { + messageId: number; +} + +export interface AgentMessage extends BaseMessage { + role: "user" | "assistant" | "function"; + content: string; + name?: string; + type: MessageType; +} + +export enum MessageType { + Message = "message", + Summary = "summary", + Tool = "tool", +} + +export async function getNextAgentStep( + messages: AgentMessage[], + toolResults?: string +): Promise { + const response = await fetch( + `${window.CELLXGENE.API.prefix}${window.CELLXGENE.API.version}agent/step`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ messages, toolResults }), + } + ); + + if (!response.ok) { + throw new Error(`API error: ${response.statusText}`); + } + + const data = await response.json(); + return AgentStepResponseSchema.parse(data); +} diff --git a/client/src/components/Agent/tools.ts b/client/src/components/Agent/tools.ts new file mode 100644 index 000000000..18878fa16 --- /dev/null +++ b/client/src/components/Agent/tools.ts @@ -0,0 +1,160 @@ +import { AppDispatch, GetState } from "../../reducers"; +import { + performSubset, + performUnsubset, + performCategoricalSelection, + performHistogramSelection, + // performPanning, + // performZoomIn, + // performZoomOut, + performColorByGene, + performExpandGene, + performExpandCategory, + performColorByGeneset, + performColorByMetadata, + performCreateGeneset, + // performXYScatterplot, + // performShowCellGuide, + // performShowGeneCard, +} from "./actions"; +import { UITool } from "./UITool"; + +type ToolImplementation = { + action: ( + dispatch: AppDispatch, + getState: GetState, + args?: Record + ) => Promise; +}; + +// This maps the tool names (defined in the backend) to their frontend implementations +const TOOL_IMPLEMENTATIONS: Record = { + subset: { + action: async (dispatch) => { + dispatch(performSubset()); + return "Created subset from selection"; + }, + }, + + unsubset: { + action: async (dispatch) => { + dispatch(performUnsubset()); + return "I reset the subset back to full dataset"; + }, + }, + + categorical_selection: { + action: async (dispatch, _getState, args): Promise => { + if (!args) throw new Error("Categorical selection args are required"); + const message = await dispatch(performCategoricalSelection(args)); + return message; + }, + }, + + histogram_selection: { + action: async (dispatch, _getState, args) => { + if (!args) throw new Error("Histogram selection args are required"); + const message = await dispatch(performHistogramSelection(args)); + return message; + }, + }, + + // panning: { + // action: async (dispatch) => { + // dispatch(performPanning()); + // return "Performed panning"; + // }, + // }, + + // zoom_in: { + // action: async (dispatch) => { + // dispatch(performZoomIn()); + // return "Performed zoom in"; + // }, + // }, + + // zoom_out: { + // action: async (dispatch) => { + // dispatch(performZoomOut()); + // return "Performed zoom out"; + // }, + // }, + + color_by_gene: { + action: async (dispatch, _getState, args) => { + if (!args) throw new Error("Gene name is required"); + const returnMessage = await dispatch(performColorByGene(args)); + return returnMessage; + }, + }, + + expand_gene: { + action: async (dispatch, _getState, args) => { + if (!args) throw new Error("Gene name is required"); + await dispatch(performExpandGene(args)); + return `Expanded gene: ${args.gene}`; + }, + }, + + color_by_geneset: { + action: async (dispatch, _getState, args) => { + if (!args) throw new Error("Geneset name is required"); + const message = await dispatch(performColorByGeneset(args)); + return message; + }, + }, + + color_by_metadata: { + action: async (dispatch, _getState, args) => { + if (!args) throw new Error("Metadata name is required"); + const message = await dispatch(performColorByMetadata(args)); + return message; + }, + }, + + expand_category: { + action: async (dispatch, _getState, args) => { + if (!args) throw new Error("Category name is required"); + await dispatch(performExpandCategory(args)); + return `Expanded category: ${args.category_name}`; + }, + }, + + create_geneset: { + action: async (dispatch, _getState, args) => { + if (!args) throw new Error("Geneset args are required"); + await dispatch(performCreateGeneset(args)); + return "Created new geneset"; + }, + }, + + // xy_scatterplot: { + // action: async (dispatch) => { + // dispatch(performXYScatterplot()); + // return "Created XY scatterplot"; + // }, + // }, + + // show_cell_guide: { + // action: async (dispatch) => { + // dispatch(performShowCellGuide()); + // return "Showed cell guide"; + // }, + // }, + + // show_gene_card: { + // action: async (dispatch) => { + // dispatch(performShowGeneCard()); + // return "Showed gene card"; + // }, + // }, +}; + +export function createUITools(dispatch: AppDispatch, getState: GetState) { + return Object.entries(TOOL_IMPLEMENTATIONS).map( + ([name, implementation]) => + new UITool(name, (args?: Record) => + implementation.action(dispatch, getState, args) + ) + ); +} diff --git a/client/src/components/Agent/types.ts b/client/src/components/Agent/types.ts new file mode 100644 index 000000000..c6c64cdb4 --- /dev/null +++ b/client/src/components/Agent/types.ts @@ -0,0 +1,21 @@ +import { AppDispatch, GetState } from "../../reducers"; + +export interface ToolParameter { + name: string; + description: string; + required: boolean; + type: "string" | "number" | "boolean" | "array"; +} + +export interface ToolConfig { + name: string; + description: string; + parameters?: ToolParameter[]; + action: ActionFunction; +} + +export type ActionFunction = ( + dispatch: AppDispatch, + getState: GetState, + args?: Record +) => Promise; diff --git a/client/src/components/App/App.tsx b/client/src/components/App/App.tsx index 822146cdf..45fdd9963 100644 --- a/client/src/components/App/App.tsx +++ b/client/src/components/App/App.tsx @@ -6,6 +6,8 @@ import actions from "actions"; import { RootState, AppDispatch } from "reducers"; import Controls from "common/components/Controls/Controls"; import { theme } from "util/theme"; +import { getFeatureFlag } from "util/featureFlags/featureFlags"; +import { FEATURES } from "util/featureFlags/features"; import DatasetSelector from "../DatasetSelector/DatasetSelector"; import DiffexNotice from "../DiffexNotice/DiffexNotice"; import BottomBanner from "../BottomBanner/BottomBanner"; @@ -22,6 +24,7 @@ import { selectIsSeamlessEnabled } from "../../selectors/datasetMetadata"; import Graph from "../Graph/Graph"; import Scatterplot from "../scatterplot/scatterplot"; import PanelEmbedding from "../PanelEmbedding/PanelEmbedding"; +import { AgentComponent } from "../Agent/AgentComponent"; interface StateProps { loading: RootState["controls"]["loading"]; @@ -73,6 +76,7 @@ class App extends React.Component { scatterplotXXaccessor, scatterplotYYaccessor, } = this.props; + return ( @@ -98,27 +102,52 @@ class App extends React.Component { )} {loading || error ? null : ( <> + {getFeatureFlag(FEATURES.AGENT) && ( +
+ +
+ )} ( - <> +
- - {scatterplotXXaccessor && scatterplotYYaccessor && ( - - )} - +
+ + {scatterplotXXaccessor && scatterplotYYaccessor && ( + + )} + +
- +
)} > diff --git a/client/src/reducers/index.ts b/client/src/reducers/index.ts index 028ca0307..1a27661ef 100644 --- a/client/src/reducers/index.ts +++ b/client/src/reducers/index.ts @@ -84,7 +84,8 @@ const RootReducer: Reducer = (state: RootState, action: Action) => { const store = createStore(RootReducer, applyMiddleware(thunk, annoMatrixGC)); -export type RootState = { +export interface RootState { + actionCount: number; config: ReturnType; annoMatrix: ReturnType; obsCrossfilter: ReturnType; @@ -104,7 +105,7 @@ export type RootState = { pointDilation: ReturnType; datasetMetadata: ReturnType; showBottomBanner: ReturnType; -}; +} export type AppDispatch = ThunkDispatch; diff --git a/client/src/reducers/undoable.ts b/client/src/reducers/undoable.ts index 225b958eb..997aed497 100644 --- a/client/src/reducers/undoable.ts +++ b/client/src/reducers/undoable.ts @@ -12,10 +12,11 @@ Requires three parameters: See below for details. * debug: if truish, will print helpful log messages about history manipulation -This meta reducer accepts three actions types: +This meta reducer accepts four actions types: * @@undoable/undo - move back in history * @@undoable/redo - move forward in history * @@undoable/clear - clear history +* @@undoable/revert-to-action - revert to a specific action count --- @@ -87,6 +88,7 @@ export interface UndoableState { [futureKey]: [string, unknown][][]; [pendingKey]: [string, unknown][] | null; [filterStateKey]: FilterStateType | undefined; + actionCount: number; } const Undoable = ( @@ -126,6 +128,7 @@ const Undoable = ( [pastKey]: newPast, [futureKey]: newFuture, [pendingKey]: null, + actionCount: currentState.actionCount - 1, }; return nextState; } @@ -151,6 +154,7 @@ const Undoable = ( [pastKey]: newPast, [futureKey]: newFuture, [pendingKey]: null, + actionCount: currentState.actionCount + 1, }; return nextState; } @@ -167,6 +171,7 @@ const Undoable = ( [futureKey]: [], [filterStateKey]: undefined, [pendingKey]: null, + actionCount: 0, }; } @@ -188,6 +193,7 @@ const Undoable = ( [futureKey]: future, [filterStateKey]: filterState, [pendingKey]: pending, + actionCount: currentState.actionCount, }; } @@ -211,6 +217,7 @@ const Undoable = ( [futureKey]: [], [filterStateKey]: filterState, [pendingKey]: null, + actionCount: (currentState.actionCount || 0) + 1, }; return nextState; } @@ -227,6 +234,7 @@ const Undoable = ( return { ...currentState, [pendingKey]: currentUndoableState, + actionCount: currentState.actionCount, }; } @@ -239,6 +247,7 @@ const Undoable = ( return { ...currentState, [pendingKey]: null, + actionCount: currentState.actionCount, }; } @@ -257,20 +266,57 @@ const Undoable = ( [pastKey]: newPast, [futureKey]: [], [pendingKey]: null, + actionCount: (currentState.actionCount || 0) + 1, }; return nextState; } + /* + Revert to a specific action count + */ + function revertToAction( + currentState: UndoableState, + targetCount: number + ): UndoableState { + const past = currentState[pastKey]; + const currentCount = currentState.actionCount || 0; + + if (targetCount >= currentCount) return currentState; + + // Calculate how many steps we need to go back + const stepsBack = currentCount - targetCount; + + // Get the state at that point + const targetState = past[past.length - stepsBack] || []; + + // Store everything after this point in the future stack + const newFuture = [ + ...past.slice(past.length - stepsBack + 1), + Object.entries(currentState).filter((kv) => undoableKeysSet.has(kv[0])), + ]; + + return { + ...currentState, + ...fromEntries(targetState), + [pastKey]: past.slice(0, past.length - stepsBack), + [futureKey]: newFuture, + [pendingKey]: null, + actionCount: targetCount, + }; + } + return ( currentState: UndoableState = { [pastKey]: [], [futureKey]: [], [filterStateKey]: undefined, [pendingKey]: null, + actionCount: 0, }, action: AnyAction ) => { - if (debug > 1) console.log("---- ACTION", action.type); + if (debug && typeof debug === "number" && debug > 1) + console.log("---- ACTION", action.type); const aType = action.type; switch (aType) { case "@@undoable/undo": { @@ -282,7 +328,14 @@ const Undoable = ( } case "@@undoable/clear": { - return clear(currentState); + return { + ...clear(currentState), + actionCount: 0, + }; + } + + case "@@undoable/revert-to-action": { + return revertToAction(currentState, action.targetCount); } default: { diff --git a/client/src/reducers/undoableConfig.ts b/client/src/reducers/undoableConfig.ts index 5c1cdf831..cbc72ae04 100644 --- a/client/src/reducers/undoableConfig.ts +++ b/client/src/reducers/undoableConfig.ts @@ -28,8 +28,6 @@ const skipOnActions = new Set([ "graph brush change", "continuous metadata histogram brush", - "request user defined gene success", - "category value mouse hover start", "category value mouse hover end", @@ -56,6 +54,11 @@ const skipOnActions = new Set([ /* geneInfo actions */ "minimize/maximize gene info", "open gene info", + + "single user defined gene start", + "single user defined gene complete", + "type continuous metadata histogram start", + "type continuous metadata histogram brush", ]); /* @@ -120,6 +123,9 @@ const saveOnActions = new Set([ /* geneInfo actions */ "load gene info", "clear gene info", + + "type continuous metadata histogram end", + "request user defined gene success", ]); /** diff --git a/client/src/util/featureFlags/features.ts b/client/src/util/featureFlags/features.ts index cf27f073a..5ad36e95c 100644 --- a/client/src/util/featureFlags/features.ts +++ b/client/src/util/featureFlags/features.ts @@ -1,3 +1,4 @@ export enum FEATURES { TEST = "test", + AGENT = "agent", } diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..231968dcb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,99 @@ +{ + "name": "single-cell-explorer", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "react-draggable": "^4.4.5" + } + }, + "node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "engines": { + "node": ">=6" + } + }, + "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/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/react": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", + "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "peer": true, + "dependencies": { + "scheduler": "^0.25.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-draggable": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-draggable/-/react-draggable-4.4.5.tgz", + "integrity": "sha512-OMHzJdyJbYTZo4uQE393fHcqqPYsEtkjfMgvCHr6rejT+Ezn4OZbNyGH50vv+SunC1RMvwOTSWkEODQLzw1M9g==", + "dependencies": { + "clsx": "^1.1.1", + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "react": ">= 16.3.0", + "react-dom": ">= 16.3.0" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "peer": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..c721c9aa8 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "react-draggable": "^4.4.5" + } +} diff --git a/server/app/api/v3.py b/server/app/api/v3.py index b3a093239..80ed72511 100644 --- a/server/app/api/v3.py +++ b/server/app/api/v3.py @@ -11,6 +11,7 @@ ) from flask_restful import Api, Resource +import server.common.agent as common_agent import server.common.rest as common_rest from server.app.api.util import get_data_adaptor, get_dataset_artifact_s3_uri from server.common.constants import CELLGUIDE_CXG_KEY_NAME @@ -177,6 +178,14 @@ class UnsMetaAPI(DatasetResource): def get(self, data_adaptor): return common_rest.uns_metadata_get(request, data_adaptor) + # Add agent endpoint + + +class AgentAPI(S3URIResource): # Inherit from S3URIResource instead of Resource + @rest_get_s3uri_data_adaptor + def post(self, data_adaptor): + return common_agent.agent_step_post(request, data_adaptor) + def rest_get_dataset_explorer_location_data_adaptor(func): @wraps(func) @@ -249,6 +258,7 @@ def add_resource(resource, url): add_resource(DiffExpObs2API, "/diffexp/obs2") add_resource(LayoutObsAPI, "/layout/obs") add_resource(UnsMetaAPI, "/uns/meta") + add_resource(AgentAPI, "/agent/step") return api @@ -300,3 +310,11 @@ def register_api_v3(app, app_config, api_url_prefix): view_func=lambda dataset, filename: send_from_directory("../common/web/static", filename), methods=["GET"], ) + + # Add agent endpoint for each dataroot + app.add_url_rule( + f"{api_url_prefix}/{url_dataroot}//api/v0.3/agent/step", + f"agent_step_{url_dataroot}", + view_func=lambda: common_agent.agent_step_post(request), + methods=["POST"], + ) diff --git a/server/common/agent.py b/server/common/agent.py new file mode 100644 index 000000000..5fd62c5b8 --- /dev/null +++ b/server/common/agent.py @@ -0,0 +1,170 @@ +import logging +import sys +from http import HTTPStatus +from typing import List, Literal, Optional + +from flask import abort, current_app, jsonify, make_response +from langchain.agents import create_openai_tools_agent +from langchain.agents.output_parsers.tools import ToolAgentAction +from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder +from langchain.schema import AIMessage, FunctionMessage, HumanMessage +from langchain_core.agents import AgentFinish +from langchain_openai import ChatOpenAI +from pydantic import BaseModel + +from server.common.openai_utils import get_cached_openai_api_key +from server.common.tools import create_tools + + +class AgentMessage(BaseModel): + role: Literal["user", "assistant", "function"] + content: str + name: Optional[str] = None + type: Literal["summary", "tool", "message"] = "message" + + +def get_system_prompt() -> str: + """Create the system prompt string""" + return """You are an assistant that helps users control an interface for visualizing single-cell data. Your primary task is to respond with the appropriate tool call and its input. The client will execute the tool and return to you for the next steps. + +Guidelines: + +- When an execution workflow is complete (i.e. there are no more steps to be taken), use the no_more_steps tool. +- Execute ONE action at a time and wait for user input +- Be concise in your responses - avoid listing all possible next actions unless specifically asked +- Process all requested actions until complete +- If a tool requires additional information, invoke it to get the needed information +- If asked about capabilities, list the available tools +- Do not perform subsetting unless specifically requested +- Assist only with queries related to single-cell data analysis and visualization + +Concepts: + +- **Subsetting**: Filtering the data to only the currently selected or highlighted data points. +- **Selection**: Highlighting a subset of data points based on categorical or continuous data. For continuous data, selections use histograms in the UI to select value ranges. +- **Coloring**: Applying colors to data points based on a specific feature. +- **Expanding**: Displaying more information about a particular feature. + +Terminology: + +- **Genesets**: Collections of genes curated by the user. +- **Genes**: Individual genes curated by the user. +- **Metadata**: Any other information about the data points that is not a gene or geneset.""" + + +def get_prompt_template() -> ChatPromptTemplate: + """Create the chat prompt template""" + return ChatPromptTemplate.from_messages( + [ + ("system", get_system_prompt()), + ( + "system", + "The following conversation history provides context. Use it to understand references and previous actions, but do not re-execute completed workflows unless specifically requested:", + ), + MessagesPlaceholder(variable_name="chat_history", optional=True), + ( + "system", + "Now focus on the current request. When an execution workflow is complete, use the no_more_steps tool.", + ), + AIMessage(content=""), + MessagesPlaceholder(variable_name="input"), + MessagesPlaceholder(variable_name="agent_scratchpad"), + ] + ) + + +def agent_step_post(request, data_adaptor): + """Handle agent step requests""" + try: + data = request.get_json() + messages: List[AgentMessage] = [AgentMessage(**msg) for msg in data["messages"]] + + # Convert messages to LangChain format + formatted_messages = [] + for msg in messages: + if msg.role == "user": + formatted_messages.append(HumanMessage(content=msg.content)) + elif msg.role == "assistant": + formatted_messages.append(AIMessage(content=msg.content)) + elif msg.role == "function": + formatted_messages.append(FunctionMessage(content=msg.content, name=msg.name or "function")) + + # Find index of last summary message + last_summary_idx = -1 + for i in range(len(messages) - 1, -1, -1): + if messages[i].type == "summary": + last_summary_idx = i + break + + # Split messages into chat history and current request + chat_history = formatted_messages[: last_summary_idx + 1] + current_request = formatted_messages[last_summary_idx + 1 :] + print("chat_history:") + for msg in chat_history: + print(f" {msg}") + print("current_request:") + for msg in current_request: + print(f" {msg}") + # Initialize LLM and create agent with data_adaptor-aware tools + + api_key = get_cached_openai_api_key() + llm = ChatOpenAI(temperature=0, model_name="gpt-4o", api_key=api_key) + tools = create_tools(data_adaptor) + agent = create_openai_tools_agent(llm, tools, get_prompt_template()) + # Get structured response with split history + next_step = agent.invoke( + { + "input": current_request, + "chat_history": chat_history, + "intermediate_steps": [], + } + ) + + response = {} + if isinstance(next_step, AgentFinish): + # Remove start summary tag from output + response = { + "type": "message", + "content": next_step.return_values["output"], + } + elif isinstance(next_step, list) and isinstance(next_step[0], ToolAgentAction): + tool_action = next_step[0] + if tool_action.tool == "no_more_steps": + response = {"type": "summary", "content": tool_action.tool_input["summary"]} + else: + # Find the matching tool + selected_tool = next(tool for tool in tools if tool.name == tool_action.tool) + + # Execute the tool with its arguments + # If the tool input is a string, then there is no argument schema and so we have no arguments. + tool_arguments = {} if isinstance(tool_action.tool_input, str) else tool_action.tool_input + + try: + # Execute the tool and get results + tool_result = selected_tool.func(**tool_arguments) + + response = { + "type": "tool", + "tool": {"name": tool_action.tool, "result": tool_result}, + } + except Exception as tool_error: + # Handle tool execution errors + error_message = f"Error executing tool {tool_action.tool}: {str(tool_error)}" + current_app.logger.error(error_message, exc_info=True) + return abort_and_log(HTTPStatus.INTERNAL_SERVER_ERROR, error_message, loglevel=logging.ERROR) + else: + raise ValueError(f"Unknown agent step type: {type(next_step)}") + + return make_response(jsonify(response), HTTPStatus.OK) + + except Exception as e: + return abort_and_log(HTTPStatus.INTERNAL_SERVER_ERROR, str(e), loglevel=logging.ERROR) + + +def abort_and_log(code, logmsg, loglevel=logging.DEBUG): + """Log the message, then abort with HTTP code""" + print("ABORTING") + exc_info = sys.exc_info() + print(exc_info) + current_app.logger.log(loglevel, logmsg, exc_info=exc_info) + return abort(code) diff --git a/server/common/config/config_model.py b/server/common/config/config_model.py index c035e8e3c..810e75b40 100644 --- a/server/common/config/config_model.py +++ b/server/common/config/config_model.py @@ -13,7 +13,7 @@ from typing import Dict, List, Optional, Union from urllib.parse import quote_plus -from pydantic import BaseModel, Extra, Field, root_validator, validator +from pydantic.v1 import BaseModel, Extra, Field, root_validator, validator from server.common.utils.data_locator import discover_s3_region_name from server.common.utils.utils import custom_format_warning, find_available_port, is_port_available diff --git a/server/common/openai_utils.py b/server/common/openai_utils.py new file mode 100644 index 000000000..c5f7c367c --- /dev/null +++ b/server/common/openai_utils.py @@ -0,0 +1,19 @@ +import os + +from flask import current_app + +from server.common.utils.aws_secret_utils import get_secret_key + + +def get_openai_api_key(): + deployment_stage = os.getenv("DEPLOYMENT_STAGE", "test") + if deployment_stage == "test": + return os.getenv("OPENAI_API_KEY") + return get_secret_key(f"{deployment_stage}/explorer/agent", region_name="us-west-2")["OPENAI_API_KEY"] + + +def get_cached_openai_api_key(): + """Get OpenAI API key from cache or fetch it if not cached""" + if not hasattr(current_app, "openai_api_key"): + current_app.openai_api_key = get_openai_api_key() + return current_app.openai_api_key diff --git a/server/common/tools.py b/server/common/tools.py new file mode 100644 index 000000000..cc98b6b2a --- /dev/null +++ b/server/common/tools.py @@ -0,0 +1,411 @@ +from enum import Enum +from functools import partial +from typing import Annotated, List, Type, TypeVar + +from langchain_core.tools import Tool +from langchain_openai import ChatOpenAI +from pydantic import BaseModel, Field + +from server.common.openai_utils import get_cached_openai_api_key + + +class ColorByGeneSchema(BaseModel): + gene: Annotated[ + str, + Field( + description="The gene symbol to color the visualization by. This should be a valid gene symbol (e.g., CD4, IL2, etc.)." + ), + ] + + +class SummarySchema(BaseModel): + summary: Annotated[ + str, + Field( + description="A summary of the actions taken in this workflow execution after the tag in a concise manner to the user." + ), + ] + + +class HistogramType(Enum): + METADATA = "metadata" + GENE = "gene" + GENESET = "geneset" + + +class HistogramSelectionSchema(BaseModel): + range_low: Annotated[ + float, + Field(description="The low end of the range to select. If not provided, the tool will set the low end to 0."), + ] + range_high: Annotated[ + float | None, + Field( + description="The high end of the range to select. If not provided, the tool will set the high end to None." + ), + ] + histogram_type: Annotated[ + HistogramType, + Field( + description="The type of histogram to select - gene, geneset, or metadata. IMPORTANT: GENES MUST BE GENE SYMBOLS." + ), + ] + available_genesets: Annotated[ + List[str] | None, + Field(description="The list of available genesets to select from. Only used if the histogram type is geneset."), + ] + histogram_name: Annotated[ + str, + Field( + description="The name of the histogram to select. This can be a metadata column name, gene, or geneset name. IMPORTANT: GENES MUST BE GENE SYMBOLS." + ), + ] + + +class ColorByGenesetSchema(BaseModel): + geneset: Annotated[ + str, + Field(description="The name of the geneset to color the visualization by."), + ] + available_genesets: Annotated[ + List[str] | None, + Field(description="The list of available genesets to color the visualization by."), + ] + + +class ExpandGeneSchema(BaseModel): + gene: Annotated[ + str, + Field(description="The gene symbol to expand."), + ] + + +class CreateGenesetSchema(BaseModel): + geneset_name: Annotated[ + str, + Field(description="The name of the geneset to create."), + ] + geneset_description: Annotated[ + str, + Field( + description="The description of the geneset to create. If not provided by the user, the tool will set the description to an empty string." + ), + ] + genes_to_populate_geneset: Annotated[ + List[str], + Field( + description="The genes to populate the geneset with. If not provided by the user, the tool will set the genes to an empty list." + ), + ] + + +class CategoricalSelectionSchema(BaseModel): + category_value: Annotated[ + str, + Field(description="The category a user wishes to perform selection on."), + ] + column_name: Annotated[ + str | None, + Field( + description="The column name that the category value belongs to. If not provided, the tool will attempt to infer the column name from the category value.", + default=None, + ), + ] + + +class ExpandCategorySchema(BaseModel): + category_name: Annotated[ + str, + Field(description="The name of the category to expand."), + ] + + +class MetadataColorBySchema(BaseModel): + metadata_name: Annotated[ + str, + Field(description="The name of the metadata to color the visualization by."), + ] + + +def subset(): + return {"status": "success"} + + +def unsubset(): + return {"status": "success"} + + +def select_category(data_adaptor, category_value: str, column_name: str | None = None): + schema = data_adaptor.get_schema() + + prompt = "IMPORTANT: Be flexible with matching - for example, 'B cells' should match 'B cell', plurals should match singular forms, and common variations should be recognized. Only return an error if there is no conceptually matching category.\n" + prompt += "IMPORTANT: You must return the exact category value from the dataset, not the user's input value.\n" + prompt += f"The category value the user wishes to perform selection on is: {category_value}.\n" + if column_name is not None: + prompt += f"The column name that the category value belongs to is: {column_name}.\n" + cols = [i for i in schema["annotations"]["obs"]["columns"] if "categories" in i] + prompt += f"The valid categorical metadata labels are: {cols}.\n" + prompt += "Please select one of the valid category labels along with the name of the column that most closely matches the user's request." + + class CategorySelectionSchema(BaseModel): + category_value: str + column_name: str + error: str | None = None + + return call_llm_with_structured_output(prompt, CategorySelectionSchema) + + +def histogram_selection( + data_adaptor, + histogram_name: str, + histogram_type: HistogramType, + range_low: float, + range_high: float | None = None, + available_genesets: List[str] | None = None, +): + if histogram_type == HistogramType.GENESET.value: + if available_genesets is None: + return { + "histogram_type": histogram_type, + "status": "need_available_genesets", + } + prompt = f"The geneset the user wishes to perform histogram selection on is: {histogram_name}." + prompt += f"The available genesets are: {available_genesets}." + prompt += "Please select one of the available genesets to perform histogram selection on." + + class GenesetSelectionSchema(BaseModel): + geneset: str + + return { + "histogram_name": call_llm_with_structured_output(prompt, GenesetSelectionSchema)["geneset"], + "histogram_type": histogram_type, + "range_low": range_low, + "range_high": range_high, + } + elif histogram_type == HistogramType.METADATA.value: + schema = data_adaptor.get_schema() + prompt = f"The metadata name the user wishes to perform histogram selection on is: {histogram_name}." + cols = [i["name"] for i in schema["annotations"]["obs"]["columns"] if "categories" not in i and i != "name_0"] + prompt += f"The valid metadata columns are: {cols}. Please select one of the column names to perform histogram selection on." + prompt += "Metadata can be categorical or continuous. Please only select one of the continuous columns to perform histogram selection on." + + class MetadataSelectionSchema(BaseModel): + metadata_name: str + + response = { + "histogram_name": call_llm_with_structured_output(prompt, MetadataSelectionSchema)["metadata_name"], + "histogram_type": histogram_type, + "range_low": range_low, + "range_high": range_high, + } + print(response) + return response + elif histogram_type == HistogramType.GENE.value: + return { + "histogram_name": histogram_name.upper(), + "histogram_type": histogram_type, + "range_low": range_low, + "range_high": range_high, + } + + +def panning(): + return {"status": "success"} + + +def zoom_in(): + return {"status": "success"} + + +def zoom_out(): + return {"status": "success"} + + +def color_by_gene(gene: str): + return { + "gene": gene.upper(), + } + + +def expand_gene(gene: str): + return { + "gene": gene.upper(), + } + + +def color_by_geneset(geneset: str, available_genesets: List[str] | None = None): + if available_genesets is None: + return { + "status": "need_available_genesets", + } + + prompt = f"The geneset the user wishes to color by is: {geneset}." + prompt += f"The available genesets are: {available_genesets}." + prompt += "Please select one of the available genesets to color by." + + class GenesetSelectionSchema(BaseModel): + geneset: str + + return call_llm_with_structured_output(prompt, GenesetSelectionSchema) + + +def color_by_metadata(data_adaptor, metadata_name: str): + schema = data_adaptor.get_schema() + prompt = "IMPORTANT: Be flexible with matching - for example, 'cell types' should match 'cell_type', plurals should match singular forms, and common variations should be recognized. Only return an error if there is no conceptually matching metadata.\n" + prompt += "IMPORTANT: You must return the exact metadata name from the dataset, not the user's input value.\n" + prompt += f"The metadata name the user wishes to color by is: {metadata_name}.\n" + cols = [i["name"] for i in schema["annotations"]["obs"]["columns"]] + prompt += f"The valid metadata columns are: {cols}.\n" + prompt += "Please select one of the valid metadata columns that most closely matches the user's request." + + class MetadataSelectionSchema(BaseModel): + metadata_name: str + error: str | None = None + + return call_llm_with_structured_output(prompt, MetadataSelectionSchema) + + +def create_geneset(geneset_name: str, geneset_description: str, genes_to_populate_geneset: List[str]): + return { + "geneset_name": geneset_name, + "geneset_description": geneset_description, + "genes_to_populate_geneset": [i.upper() for i in genes_to_populate_geneset], + } + + +def expand_category(data_adaptor, category_name: str): + schema = data_adaptor.get_schema() + prompt = f"The category name the user wishes to perform expand by is: {category_name}." + categorical_cols = [i["name"] for i in schema["annotations"]["obs"]["columns"] if "categories" in i] + prompt += f"The valid categorical metadata columns are: {categorical_cols}." + prompt += "Please select one of the valid categorical metadata columns to expand by." + + class CategorySelectionSchema(BaseModel): + category_name: str + + return call_llm_with_structured_output(prompt, CategorySelectionSchema) + + +def xy_scatterplot(): + return {"status": "success"} + + +def show_cell_guide(): + return {"status": "success"} + + +def show_gene_card(): + return {"status": "success"} + + +def no_more_steps(summary: str): + return {"status": "no_more_steps", "summary": summary} + + +T = TypeVar("T", bound=BaseModel) + + +def call_llm_with_structured_output(query: str, schema: Type[T]) -> T: + api_key = get_cached_openai_api_key() + llm = ChatOpenAI(temperature=0, model_name="gpt-4o", api_key=api_key).with_structured_output(schema) + return llm.invoke(query).model_dump() + + +def create_tools(data_adaptor): + return [ + # TODO, FIXME: This is still summarizing actions that were already taken. + Tool( + name="no_more_steps", + description="When a workflow is complete, use this tool to summarize ONLY the actions taken after the tag. Use the conversation history before this tag for context, but do not summarize previous actions.", + func=no_more_steps, + args_schema=SummarySchema, + ), + Tool( + name="subset", + description="Filter dataset to show only currently selected data points", + func=subset, + ), + Tool( + name="unsubset", + description="Reset the current subset and return to the full dataset", + func=unsubset, + ), + Tool( + name="categorical_selection", + description="Highlight data points matching a specific category value (does not filter/subset the data)", + func=partial(select_category, data_adaptor), + args_schema=CategoricalSelectionSchema, + ), + Tool( + name="histogram_selection", + description="Perform a histogram selection. For geneset histograms, returns a flag if available genesets aren't provided, expecting them in a subsequent call.", + func=partial(histogram_selection, data_adaptor), + args_schema=HistogramSelectionSchema, + ), + Tool( + name="color_by_gene", + description="Color the visualization by gene expression", + func=color_by_gene, + args_schema=ColorByGeneSchema, + ), + Tool( + name="expand_gene", + description="Expand the gene element to show more information about the gene", + func=expand_gene, + args_schema=ExpandGeneSchema, + ), + Tool( + name="color_by_geneset", + description="Color by average expression of a geneset. Returns a flag if available genesets aren't provided, expecting them in a subsequent call.", + func=color_by_geneset, + args_schema=ColorByGenesetSchema, + ), + Tool( + name="color_by_metadata", + description="Color the visualization by metadata", + func=partial(color_by_metadata, data_adaptor), + args_schema=MetadataColorBySchema, + ), + Tool( + name="expand_category", + description="Expand the category element to show more information about the category", + func=partial(expand_category, data_adaptor), + args_schema=ExpandCategorySchema, + ), + Tool( + name="create_geneset", + description="Create a new geneset", + func=create_geneset, + args_schema=CreateGenesetSchema, + ), + # Tool( + # name="xy_scatterplot", + # description="Create an XY scatterplot", + # func=xy_scatterplot, + # ), + # Tool( + # name="show_cell_guide", + # description="Show the cell guide", + # func=show_cell_guide, + # ), + # Tool( + # name="show_gene_card", + # description="Show the gene card", + # func=show_gene_card, + # ), + # Tool( + # name="panning", + # description="Perform panning on the current view", + # func=panning, + # ), + # Tool( + # name="zoom_in", + # description="Zoom in on the current view", + # func=zoom_in, + # ), + # Tool( + # name="zoom_out", + # description="Zoom out on the current view", + # func=zoom_out, + # ), + ] diff --git a/server/common/utils/aws_secret_utils.py b/server/common/utils/aws_secret_utils.py index 73c508b14..411de3e48 100644 --- a/server/common/utils/aws_secret_utils.py +++ b/server/common/utils/aws_secret_utils.py @@ -6,7 +6,7 @@ from server.common.errors import SecretKeyRetrievalError # type: ignore -def get_secret_key(region_name, secret_name): # type: ignore +def get_secret_key(secret_name, region_name="us-west-2"): # type: ignore session = boto3.session.Session() client = session.client(service_name="secretsmanager", region_name=region_name) diff --git a/server/requirements.txt b/server/requirements.txt index 0bf98747a..09974c32e 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -15,7 +15,7 @@ gunicorn[gevent]==20.0.4 numba==0.60.0 numpy>=1.24.0,<2.1.0 pandas>=2.2.2 -pydantic==1.10.13 +pydantic==2.9.2 requests==2.32.3 scipy==1.14.1 flask-server-timing==0.1.2 @@ -23,3 +23,6 @@ s3fs==0.4.2 tiledb # unpinned to match Portal's requirements Werkzeug==3.0.6 python-json-logger==2.0.7 +langchain +langchain-openai +