diff --git a/scripts/license_finder.yml b/scripts/license_finder.yml index 07b0770471..964ab598a8 100644 --- a/scripts/license_finder.yml +++ b/scripts/license_finder.yml @@ -187,3 +187,9 @@ is compatible with Apache 2.0 :versions: [] :when: 2025-12-23 15:02:58.461300000 Z +- - :approve + - robust-predicates + - :who: bender-rodriguez-unit1 + :why: "The Unlicense (public domain); transitive dependency of mermaid via d3-delaunay. Compatible with Apache-2.0 project license." + :versions: [] + :when: 2026-05-14 22:55:00.000000000 Z diff --git a/web_src/.eslint-budget-baseline.json b/web_src/.eslint-budget-baseline.json index 8f0a7d091b..8db2825cac 100644 --- a/web_src/.eslint-budget-baseline.json +++ b/web_src/.eslint-budget-baseline.json @@ -1,16 +1,16 @@ { - "maxAllowedTotalIssues": 710, + "maxAllowedTotalIssues": 713, "maxAllowedByRule": { - "complexity": 233, + "complexity": 235, "@typescript-eslint/no-explicit-any": 160, "@typescript-eslint/no-non-null-asserted-optional-chain": 80, "max-lines-per-function": 77, - "max-statements": 75, + "max-statements": 76, "react-hooks/exhaustive-deps": 33, "react-refresh/only-export-components": 23, "max-lines": 17, "max-params": 6, "max-depth": 6 }, - "updatedAt": "2026-05-13T14:19:18.922Z" + "updatedAt": "2026-05-15T04:55:34.555Z" } diff --git a/web_src/package-lock.json b/web_src/package-lock.json index 84ac065752..47ddb49cab 100644 --- a/web_src/package-lock.json +++ b/web_src/package-lock.json @@ -56,7 +56,9 @@ "js-yaml": "^4.1.0", "lodash.debounce": "^4.0.8", "lucide-react": "^0.545.0", + "mermaid": "^11.15.0", "posthog-js": "^1.368.2", + "radix-ui": "^1.4.3", "react": "^19.1.0", "react-day-picker": "^9.11.0", "react-diff-view": "^3.3.2", @@ -65,6 +67,7 @@ "react-resizable-panels": "^3.0.6", "react-router-dom": "^7.13.0", "react-use-websocket": "^4.0.0", + "recharts": "^3.8.1", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", @@ -131,6 +134,19 @@ "node": ">=6.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", @@ -433,6 +449,18 @@ "node": ">=18" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==", + "license": "Apache-2.0" + }, "node_modules/@chromatic-com/storybook": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-4.1.1.tgz", @@ -1608,6 +1636,23 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.0" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1727,6 +1772,15 @@ "react": ">=16" } }, + "node_modules/@mermaid-js/parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.1.1.tgz", + "integrity": "sha512-VuHdsYMK1bT6X2JbcAaWAhugTRvRBRyuZgd+c22swUeI9g/ntaxF7CY7dYarhZovofCbUNO0G7JesfmNtjYOCw==", + "license": "MIT", + "dependencies": { + "@chevrotain/types": "~11.1.1" + } + }, "node_modules/@monaco-editor/loader": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.5.0.tgz", @@ -2428,6 +2482,29 @@ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, + "node_modules/@radix-ui/react-accessible-icon": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accessible-icon/-/react-accessible-icon-1.1.7.tgz", + "integrity": "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-accordion": { "version": "1.2.12", "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", @@ -2905,6 +2982,34 @@ } } }, + "node_modules/@radix-ui/react-form": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-form/-/react-form-0.1.8.tgz", + "integrity": "sha512-QM70k4Zwjttifr5a4sZFts9fn8FzHYvQ5PiB19O2HsYibaHSVt9fH9rzB0XZo/YcM+b7t/p7lYCT/F5eOeF5yQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-hover-card": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", @@ -3103,6 +3208,70 @@ } } }, + "node_modules/@radix-ui/react-one-time-password-field": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-one-time-password-field/-/react-one-time-password-field-0.1.8.tgz", + "integrity": "sha512-ycS4rbwURavDPVjCb5iS3aG4lURFDILi6sKI/WITUMZ13gMmn/xGjpLoqBAalhJaDk8I3UbCM5GzKHrnzwHbvg==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-password-toggle-field": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-password-toggle-field/-/react-password-toggle-field-0.1.3.tgz", + "integrity": "sha512-/UuCrDBWravcaMix4TdT+qlNdVwOM1Nck9kWx/vafXsdfj1ChfhOdfi3cy9SGBpWgTXwYCuboT/oYpJy3clqfw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-is-hydrated": "0.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popover": { "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", @@ -3279,6 +3448,62 @@ } } }, + "node_modules/@radix-ui/react-progress": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.7.tgz", + "integrity": "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -3569,6 +3794,89 @@ } } }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toolbar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toolbar/-/react-toolbar-1.1.11.tgz", + "integrity": "sha512-4ol06/1bLoFu1nwUqzdD4Y5RZ9oDdKeiHIsntug54Hcr1pgaHiPqHFEaXI1IFP/EsOfROQZ8Mig9VTIRza6Tjg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-toggle-group": "1.1.11" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", @@ -3901,19 +4209,55 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, - "node_modules/@rollup/pluginutils": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", - "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", - "dev": true, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.8", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.8.tgz", + "integrity": "sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" @@ -4372,7 +4716,12 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", "license": "MIT" }, "node_modules/@storybook/addon-a11y": { @@ -5214,12 +5563,102 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, "node_modules/@types/d3-drag": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", @@ -5229,6 +5668,54 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -5238,12 +5725,78 @@ "@types/d3-color": "*" } }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, "node_modules/@types/d3-selection": { "version": "3.0.11", "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", "license": "MIT" }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/d3-transition": { "version": "3.0.9", "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", @@ -5301,6 +5854,12 @@ "@types/estree": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -5419,6 +5978,12 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.32.1", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz", @@ -5658,6 +6223,16 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "license": "MIT", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitejs/plugin-react": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", @@ -6822,6 +7397,15 @@ "url": "https://opencollective.com/core-js" } }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cosmiconfig": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", @@ -6893,63 +7477,456 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "node_modules/cytoscape": { + "version": "3.33.3", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.3.tgz", + "integrity": "sha512-Gej7U+OKR+LZ8kvX7rb2HhCYJ0IhvEFsnkud4SB1PR+BUY/TsSO0dmOW59WEVLu51b1Rm+gQRKoz4bLYxGSZ2g==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, "engines": { "node": ">=12" } }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, "engines": { "node": ">=12" } }, - "node_modules/d3-drag": { + "node_modules/d3-selection": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, "engines": { "node": ">=12" } }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "license": "BSD-3-Clause", + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, "engines": { "node": ">=12" } }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", "license": "ISC", "dependencies": { - "d3-color": "1 - 3" + "d3-array": "2 - 3" }, "engines": { "node": ">=12" } }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, "engines": { "node": ">=12" } @@ -6998,6 +7975,16 @@ "node": ">=12" } }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7089,6 +8076,12 @@ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==", "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -7113,6 +8106,12 @@ "dev": true, "license": "MIT" }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/decode-named-character-reference": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", @@ -7222,6 +8221,15 @@ "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", "license": "MIT" }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -7606,6 +8614,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.46.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.1.tgz", + "integrity": "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/esbuild": { "version": "0.25.4", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", @@ -8144,6 +9162,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/expect-type": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", @@ -8675,6 +9699,12 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -8882,7 +9912,6 @@ "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -8900,6 +9929,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -8915,6 +9954,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -8955,6 +10004,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -9726,6 +10784,31 @@ "node": ">=4.0" } }, + "node_modules/katex": { + "version": "0.16.46", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.46.tgz", + "integrity": "sha512-WHy4Coo+bGZyH7NwJKHkS04YFsFcarWbAEOAC3EMndzdN6VSZqklLLIgfxzyaW9jDoeGYJX9SWbJPKpecox0Uw==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9735,6 +10818,11 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, "node_modules/knip": { "version": "5.88.1", "resolved": "https://registry.npmjs.org/knip/-/knip-5.88.1.tgz", @@ -9834,6 +10922,12 @@ "node": ">=0.10" } }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -10091,6 +11185,12 @@ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -10553,6 +11653,53 @@ "node": ">= 8" } }, + "node_modules/mermaid": { + "version": "11.15.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.15.0.tgz", + "integrity": "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.1.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "es-toolkit": "^1.45.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mermaid/node_modules/stylis": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz", + "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==", + "license": "MIT" + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -11553,6 +12700,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -11620,6 +12773,12 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -11727,6 +12886,22 @@ "pathe": "^2.0.3" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -11953,6 +13128,101 @@ } ] }, + "node_modules/radix-ui": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/radix-ui/-/radix-ui-1.4.3.tgz", + "integrity": "sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-accessible-icon": "1.1.7", + "@radix-ui/react-accordion": "1.2.12", + "@radix-ui/react-alert-dialog": "1.1.15", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-aspect-ratio": "1.1.7", + "@radix-ui/react-avatar": "1.1.10", + "@radix-ui/react-checkbox": "1.3.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-context-menu": "2.2.16", + "@radix-ui/react-dialog": "1.1.15", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-dropdown-menu": "2.1.16", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-form": "0.1.8", + "@radix-ui/react-hover-card": "1.1.15", + "@radix-ui/react-label": "2.1.7", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-menubar": "1.1.16", + "@radix-ui/react-navigation-menu": "1.2.14", + "@radix-ui/react-one-time-password-field": "0.1.8", + "@radix-ui/react-password-toggle-field": "0.1.3", + "@radix-ui/react-popover": "1.1.15", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-progress": "1.1.7", + "@radix-ui/react-radio-group": "1.3.8", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-scroll-area": "1.2.10", + "@radix-ui/react-select": "2.2.6", + "@radix-ui/react-separator": "1.1.7", + "@radix-ui/react-slider": "1.3.6", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-switch": "1.2.6", + "@radix-ui/react-tabs": "1.1.13", + "@radix-ui/react-toast": "1.2.15", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-toggle-group": "1.1.11", + "@radix-ui/react-toolbar": "1.1.11", + "@radix-ui/react-tooltip": "1.2.8", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-escape-keydown": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/radix-ui/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/rc9": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", @@ -12098,6 +13368,29 @@ "react": ">=18" } }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -12273,6 +13566,36 @@ "node": ">= 4" } }, + "node_modules/recharts": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", + "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -12287,6 +13610,21 @@ "node": ">=8" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -12412,6 +13750,12 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -12450,6 +13794,12 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.59.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", @@ -12494,6 +13844,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/rrweb-cssom": { "version": "0.7.1", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz", @@ -12536,6 +13898,12 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -12594,8 +13962,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/saxes": { "version": "6.0.0", @@ -13408,7 +14775,6 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, "license": "MIT" }, "node_modules/tinybench": { @@ -13596,7 +14962,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.10" @@ -14008,6 +15373,19 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/uuid": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-14.0.0.tgz", + "integrity": "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist-node/bin/uuid" + } + }, "node_modules/vaul": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", @@ -14049,6 +15427,28 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", diff --git a/web_src/package.json b/web_src/package.json index dcbbab9cb1..a95b67df38 100644 --- a/web_src/package.json +++ b/web_src/package.json @@ -71,7 +71,9 @@ "js-yaml": "^4.1.0", "lodash.debounce": "^4.0.8", "lucide-react": "^0.545.0", + "mermaid": "^11.15.0", "posthog-js": "^1.368.2", + "radix-ui": "^1.4.3", "react": "^19.1.0", "react-day-picker": "^9.11.0", "react-diff-view": "^3.3.2", @@ -80,6 +82,7 @@ "react-resizable-panels": "^3.0.6", "react-router-dom": "^7.13.0", "react-use-websocket": "^4.0.0", + "recharts": "^3.8.1", "remark-breaks": "^4.0.0", "remark-gfm": "^4.0.1", "sonner": "^2.0.7", diff --git a/web_src/src/components/AgentSidebar/index.tsx b/web_src/src/components/AgentSidebar/index.tsx index 22a96a9241..1d93b44d32 100644 --- a/web_src/src/components/AgentSidebar/index.tsx +++ b/web_src/src/components/AgentSidebar/index.tsx @@ -1,8 +1,5 @@ -import { Loader2, Send, SquareTerminal, X } from "lucide-react"; +import { ChevronRight, Loader2, Send, SquareTerminal, X } from "lucide-react"; import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react"; -import ReactMarkdown from "react-markdown"; -import remarkBreaks from "remark-breaks"; -import remarkGfm from "remark-gfm"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { cn } from "@/lib/utils"; @@ -11,6 +8,7 @@ import { useAgentSessionWebsocket } from "@/hooks/useAgentSessionWebsocket"; import type { AgentMessage } from "./types"; import type { AgentState } from "./useAgentState"; import { useSidebarWidth } from "./useSidebarWidth"; +import { RichMessage } from "./widgets/RichMessage"; export interface AgentSidebarProps { agentState: AgentState; @@ -184,9 +182,20 @@ function ChatConversation({ Loading older messages… ) : null} - {messages.map((m) => ( - - ))} + {groupMessages(messages).map((group) => + group.type === "tool-group" ? ( + + ) : ( + + ), + )} )} {showThinking ? : null} @@ -248,11 +257,36 @@ function ChatComposer({ ); } -function MessageRow({ message }: { message: AgentMessage }) { +function MessageRow({ + message, + sendMutation, + chatId, + canvasId, + organizationId, +}: { + message: AgentMessage; + sendMutation: ReturnType; + chatId: string; + canvasId: string; + organizationId: string; +}) { + const handleAction = useCallback( + async (action: string) => { + if (sendMutation.isPending) return; + try { + await sendMutation.mutateAsync({ chatId, content: action }); + } catch (err) { + console.error("Failed to send action:", err); + } + }, + [chatId, sendMutation], + ); + if (message.role === "tool") { return ; } const isUser = message.role === "user"; + return (
- {isUser ? message.content : } + {isUser ? ( + message.content + ) : ( + + )}
); } +type MessageGroup = { type: "message"; message: AgentMessage } | { type: "tool-group"; messages: AgentMessage[] }; + +function groupMessages(messages: AgentMessage[]): MessageGroup[] { + const groups: MessageGroup[] = []; + let toolBuffer: AgentMessage[] = []; + + function flushTools() { + if (toolBuffer.length > 0) { + groups.push({ type: "tool-group", messages: [...toolBuffer] }); + toolBuffer = []; + } + } + + for (const m of messages) { + if (m.role === "tool") { + toolBuffer.push(m); + } else { + flushTools(); + groups.push({ type: "message", message: m }); + } + } + flushTools(); + return groups; +} + +function ToolGroupRow({ messages }: { messages: AgentMessage[] }) { + const [expanded, setExpanded] = useState(false); + const hasRunning = messages.some((m) => m.toolStatus === "started"); + const count = messages.length; + const label = hasRunning + ? `Running command${count > 1 ? ` (${count})` : ""}...` + : `Ran ${count} command${count !== 1 ? "s" : ""}`; + + return ( +
+ + {expanded && ( +
+ {messages.map((m) => ( + + ))} +
+ )} +
+ ); +} + function ToolMessageRow({ message }: { message: AgentMessage }) { const [expanded, setExpanded] = useState(false); const command = message.content; const canExpand = Boolean(command); const running = message.toolStatus === "started"; + const preview = command ? command.split("\n")[0].substring(0, 80) : "command"; + return ( -
- -
- - {expanded && command ? ( -
{command}
- ) : null} -
+
+ + {expanded && command ? ( +
+
+ bash +
+
+            {command}
+          
+
+ ) : null}
); } @@ -305,38 +411,6 @@ function ThinkingRow() { ); } -const AGENT_MARKDOWN_CLASSES = - "max-w-none [&_h1]:mb-1.5 [&_h1]:mt-1 [&_h1]:text-base [&_h1]:font-semibold [&_h1:first-child]:mt-0 " + - "[&_h2]:mb-1 [&_h2]:mt-1 [&_h2]:text-sm [&_h2]:font-semibold [&_h2:first-child]:mt-0 " + - "[&_h3]:mb-0.5 [&_h3]:mt-1 [&_h3]:text-sm [&_h3]:font-semibold [&_h3:first-child]:mt-0 " + - "[&_p]:mb-2 [&_p]:leading-relaxed [&_p:last-child]:mb-0 " + - "[&_ol]:mb-2 [&_ol]:ml-5 [&_ol]:list-decimal [&_ul]:mb-2 [&_ul]:ml-5 [&_ul]:list-disc [&_li]:mb-0.5 " + - "[&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-slate-300 [&_blockquote]:pl-3 " + - "[&_code]:rounded [&_code]:bg-slate-200/70 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-xs " + - "[&_pre]:my-2 [&_pre]:overflow-auto [&_pre]:rounded [&_pre]:bg-slate-200/70 [&_pre]:p-2 " + - "[&_pre_code]:bg-transparent [&_pre_code]:p-0 " + - "[&_a]:underline [&_a]:underline-offset-2 [&_a]:decoration-current"; - -function AgentMarkdown({ content }: { content: string }) { - if (!content) return null; - return ( -
- ( - - {children} - - ), - }} - > - {content} - -
- ); -} - function statusLabel(status: string): string { switch (status) { case "streaming": diff --git a/web_src/src/components/AgentSidebar/widgets/BannerWidget.tsx b/web_src/src/components/AgentSidebar/widgets/BannerWidget.tsx new file mode 100644 index 0000000000..4b68e02da1 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/BannerWidget.tsx @@ -0,0 +1,28 @@ +import { CheckCircle2, XCircle } from "lucide-react"; +import { cn } from "@/lib/utils"; + +interface BannerWidgetProps { + variant: "success" | "error"; + content: string; +} + +export function BannerWidget({ variant, content }: BannerWidgetProps) { + const isSuccess = variant === "success"; + return ( +
+ {isSuccess ? ( + + ) : ( + + )} +

{content}

+
+ ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/ButtonsWidget.tsx b/web_src/src/components/AgentSidebar/widgets/ButtonsWidget.tsx new file mode 100644 index 0000000000..522bcebc74 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/ButtonsWidget.tsx @@ -0,0 +1,35 @@ +import { Button } from "@/components/ui/button"; + +interface ButtonsWidgetProps { + prompt: string; + items: string[]; + onAction?: (text: string) => void; +} + +export function ButtonsWidget({ prompt, items, onAction }: ButtonsWidgetProps) { + return ( +
+ {prompt && ( +
+

{prompt}

+
+ )} +
+ {items.map((item, i) => ( + + ))} +
+
+ ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/ChartWidget.stories.tsx b/web_src/src/components/AgentSidebar/widgets/ChartWidget.stories.tsx new file mode 100644 index 0000000000..daa23b139e --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/ChartWidget.stories.tsx @@ -0,0 +1,115 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { ChartWidget } from "./ChartWidget"; + +const meta: Meta = { + title: "AgentSidebar/Charts", + component: ChartWidget, + parameters: { + layout: "padded", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const RunSuccessRate: Story = { + args: { + config: { + type: "line", + title: "Run Success Rate (Last 7 Days)", + x: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], + series: [ + { name: "Successful", data: [12, 15, 11, 14, 13, 8, 10], color: "#22c55e" }, + { name: "Failed", data: [2, 1, 3, 0, 2, 1, 0], color: "#ef4444" }, + ], + }, + }, +}; + +export const ExecutionsPerNode: Story = { + args: { + config: { + type: "bar", + title: "Executions Per Node (Last 24h)", + x: ["Webhook", "API Call", "Check Status", "Notify OK", "Notify Fail"], + series: [{ name: "Executions", data: [48, 48, 48, 41, 7], color: "#8b5cf6" }], + }, + }, +}; + +export const RunOutcomes: Story = { + args: { + config: { + type: "pie", + title: "Run Outcomes (Last 30 Days)", + data: [ + { name: "Success", value: 312, color: "#22c55e" }, + { name: "Failed", value: 28, color: "#ef4444" }, + { name: "Timed Out", value: 8, color: "#f59e0b" }, + { name: "Cancelled", value: 3, color: "#94a3b8" }, + ], + }, + }, +}; + +export const LatencyOverTime: Story = { + args: { + config: { + type: "area", + title: "Avg Execution Latency (ms)", + x: ["00:00", "04:00", "08:00", "12:00", "16:00", "20:00", "24:00"], + series: [ + { name: "P50", data: [120, 115, 180, 220, 195, 160, 130], color: "#8b5cf6" }, + { name: "P95", data: [450, 420, 680, 890, 750, 520, 480], color: "#f59e0b" }, + { name: "P99", data: [1200, 980, 1500, 2100, 1800, 1100, 950], color: "#ef4444" }, + ], + }, + }, +}; + +export const WeeklyRunVolume: Story = { + args: { + config: { + type: "bar", + title: "Weekly Run Volume (4 Weeks)", + x: ["Week 1", "Week 2", "Week 3", "Week 4"], + series: [ + { name: "Success", data: [72, 85, 81, 90], color: "#22c55e" }, + { name: "Failed", data: [6, 7, 7, 3], color: "#ef4444" }, + ], + }, + }, +}; + +export const NodeFailureRate: Story = { + args: { + config: { + type: "bar", + title: "Node Failure Rate (%)", + x: ["SSH Deploy", "API Health", "Slack Notify", "DB Backup", "DNS Update"], + series: [{ name: "Failure %", data: [12.5, 4.2, 1.1, 8.7, 0.3], color: "#ef4444" }], + }, + }, +}; + +export const MultiSeriesLine: Story = { + args: { + config: { + type: "line", + title: "Canvas Activity (Last 14 Days)", + x: ["D1", "D2", "D3", "D4", "D5", "D6", "D7", "D8", "D9", "D10", "D11", "D12", "D13", "D14"], + series: [ + { name: "Triggers", data: [24, 31, 28, 35, 42, 38, 22, 19, 33, 41, 45, 39, 37, 44], color: "#8b5cf6" }, + { name: "Actions", data: [72, 93, 84, 105, 126, 114, 66, 57, 99, 123, 135, 117, 111, 132], color: "#06b6d4" }, + { name: "Failures", data: [3, 2, 5, 1, 4, 2, 1, 0, 3, 2, 6, 1, 2, 3], color: "#ef4444" }, + ], + }, + }, +}; diff --git a/web_src/src/components/AgentSidebar/widgets/ChartWidget.tsx b/web_src/src/components/AgentSidebar/widgets/ChartWidget.tsx new file mode 100644 index 0000000000..f6a5f585f6 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/ChartWidget.tsx @@ -0,0 +1,146 @@ +import { + Bar, + BarChart, + Line, + LineChart, + Area, + AreaChart, + Pie, + PieChart, + Cell, + XAxis, + YAxis, + CartesianGrid, +} from "recharts"; +import { + ChartContainer, + ChartTooltip, + ChartTooltipContent, + ChartLegend, + ChartLegendContent, + type ChartConfig as ShadcnChartConfig, +} from "@/components/ui/chart"; +import type { ChartConfig } from "./parser"; + +const DEFAULT_COLORS = ["#8b5cf6", "#06b6d4", "#22c55e", "#f59e0b", "#ef4444", "#ec4899"]; + +interface ChartWidgetProps { + config: ChartConfig; +} + +export function ChartWidget({ config }: ChartWidgetProps) { + if (config.type === "pie") { + return ; + } + return ; +} + +function XYChartWidget({ config }: { config: ChartConfig }) { + const { type, title, x, series } = config; + if (!x || !series?.length) { + return
Chart: missing data
; + } + + const data = x.map((label, i) => { + const point: Record = { x: label }; + for (const s of series) { + point[s.name] = s.data[i] ?? 0; + } + return point; + }); + + const chartConfig: ShadcnChartConfig = {}; + series.forEach((s, i) => { + chartConfig[s.name] = { + label: s.name, + color: s.color || DEFAULT_COLORS[i % DEFAULT_COLORS.length], + }; + }); + + const ChartComponent = type === "bar" ? BarChart : type === "area" ? AreaChart : LineChart; + + return ( +
+ {title &&

{title}

} + + + + + + } /> + } /> + {series.map((s, i) => { + const color = s.color || DEFAULT_COLORS[i % DEFAULT_COLORS.length]; + if (type === "bar") { + return ; + } + if (type === "area") { + return ( + + ); + } + return ( + + ); + })} + + +
+ ); +} + +function PieChartWidget({ config }: { config: ChartConfig }) { + const { title, data } = config; + if (!data?.length) { + return
Pie chart: missing data
; + } + + const chartConfig: ShadcnChartConfig = {}; + data.forEach((d, i) => { + chartConfig[d.name] = { + label: d.name, + color: d.color || DEFAULT_COLORS[i % DEFAULT_COLORS.length], + }; + }); + + return ( +
+ {title &&

{title}

} + + + } /> + `${name} ${((percent ?? 0) * 100).toFixed(0)}%`} + labelLine={{ strokeWidth: 1 }} + fontSize={11} + > + {data.map((entry, i) => ( + + ))} + + + +
+ ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/CodeBlockWidget.tsx b/web_src/src/components/AgentSidebar/widgets/CodeBlockWidget.tsx new file mode 100644 index 0000000000..dc5c54340f --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/CodeBlockWidget.tsx @@ -0,0 +1,135 @@ +import { Check, Copy, Maximize2 } from "lucide-react"; +import { useCallback, useState } from "react"; +import Editor from "@monaco-editor/react"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; + +interface CodeBlockWidgetProps { + code: string; + language?: string; +} + +const MONACO_OPTIONS = { + readOnly: true, + minimap: { enabled: false }, + fontSize: 12, + lineNumbers: "off" as const, + wordWrap: "on" as const, + folding: true, + scrollBeyondLastLine: false, + renderWhitespace: "none" as const, + contextmenu: false, + cursorStyle: "line" as const, + scrollbar: { + vertical: "auto" as const, + horizontal: "auto" as const, + }, + padding: { top: 8, bottom: 8 }, + overviewRulerLanes: 0, + hideCursorInOverviewRuler: true, + overviewRulerBorder: false, + guides: { indentation: false }, + renderLineHighlight: "none" as const, +}; + +function mapLanguage(lang?: string): string { + const map: Record = { + yml: "yaml", + sh: "shell", + bash: "shell", + zsh: "shell", + js: "javascript", + ts: "typescript", + py: "python", + rb: "ruby", + dockerfile: "dockerfile", + tf: "hcl", + }; + if (!lang) return "plaintext"; + return map[lang.toLowerCase()] || lang.toLowerCase(); +} + +function calcHeight(code: string, maxPx = 250): number { + const lineCount = code.split("\n").length; + const lineHeight = 19; + return Math.min(Math.max(lineCount * lineHeight + 16, 60), maxPx); +} + +export function CodeBlockWidget({ code, language }: CodeBlockWidgetProps) { + const [copied, setCopied] = useState(false); + const [expanded, setExpanded] = useState(false); + const monacoLang = mapLanguage(language); + + const handleCopy = useCallback(async () => { + await navigator.clipboard.writeText(code); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }, [code]); + + const height = calcHeight(code); + + return ( + <> +
+
+ {language || "code"} +
+ + +
+
+
+ +
+
+ + + + + + {language || "Code"} + + + +
+ +
+
+
+ + ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/CollapseWidget.tsx b/web_src/src/components/AgentSidebar/widgets/CollapseWidget.tsx new file mode 100644 index 0000000000..aac1c6015c --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/CollapseWidget.tsx @@ -0,0 +1,30 @@ +import { ChevronRight } from "lucide-react"; +import { useState } from "react"; +import { cn } from "@/lib/utils"; + +interface CollapseWidgetProps { + title: string; + content: string; +} + +export function CollapseWidget({ title, content }: CollapseWidgetProps) { + const [open, setOpen] = useState(false); + + return ( +
+ + {open && ( +
+
{content}
+
+ )} +
+ ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/ConfirmWidget.tsx b/web_src/src/components/AgentSidebar/widgets/ConfirmWidget.tsx new file mode 100644 index 0000000000..0f2318ab2f --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/ConfirmWidget.tsx @@ -0,0 +1,28 @@ +import { AlertTriangle } from "lucide-react"; +import { Button } from "@/components/ui/button"; + +interface ConfirmWidgetProps { + message: string; + yes: string; + no: string; + onAction?: (text: string) => void; +} + +export function ConfirmWidget({ message, yes, no, onAction }: ConfirmWidgetProps) { + return ( +
+
+ +

{message}

+
+
+ + +
+
+ ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/MermaidWidget.stories.tsx b/web_src/src/components/AgentSidebar/widgets/MermaidWidget.stories.tsx new file mode 100644 index 0000000000..70e480ec6c --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/MermaidWidget.stories.tsx @@ -0,0 +1,185 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { MermaidWidget } from "./MermaidWidget"; + +const meta: Meta = { + title: "AgentSidebar/Mermaid", + component: MermaidWidget, + parameters: { + layout: "padded", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const CanvasFlowchart: Story = { + args: { + content: `flowchart LR + A[Webhook Trigger] --> B[Call API] + B --> C{Check Status} + C -->|200 OK| D[Notify Success] + C -->|Error| E[Notify Failure]`, + }, +}; + +export const DeployPipeline: Story = { + args: { + content: `flowchart TD + A[GitHub Push] --> B[Build Image] + B --> C[Run Tests] + C -->|Pass| D[Deploy to Staging] + C -->|Fail| E[Notify Team] + D --> F[Run Smoke Tests] + F -->|Pass| G[Deploy to Production] + F -->|Fail| H[Rollback]`, + }, +}; + +export const SequenceDiagram: Story = { + args: { + content: `sequenceDiagram + participant User + participant SuperPlane + participant API + participant Slack + + User->>SuperPlane: Trigger Webhook + SuperPlane->>API: GET /health + API-->>SuperPlane: 200 OK + SuperPlane->>Slack: Post Success Message + Slack-->>User: Notification`, + }, +}; + +export const NodeStateDiagram: Story = { + args: { + content: `stateDiagram-v2 + [*] --> Pending + Pending --> Running: Trigger Received + Running --> Success: Execution Complete + Running --> Failed: Error Occurred + Failed --> Running: Retry + Success --> [*] + Failed --> [*]: Max Retries`, + }, +}; + +export const CanvasTopology: Story = { + args: { + content: `flowchart LR + subgraph Triggers + T1[Schedule: Every 5min] + T2[Webhook: /deploy] + end + subgraph Actions + A1[HTTP: Health Check] + A2[SSH: Deploy Script] + A3[If: Status == 200] + end + subgraph Notifications + N1[Slack: #ops] + N2[Email: on-call@team.com] + end + + T1 --> A1 + T2 --> A2 + A1 --> A3 + A3 -->|true| N1 + A3 -->|false| N2 + A2 --> N1`, + }, +}; + +export const GitGraph: Story = { + args: { + content: `gitGraph + commit id: "init" + commit id: "add trigger" + branch feature/api-node + checkout feature/api-node + commit id: "add HTTP node" + commit id: "add error handling" + checkout main + merge feature/api-node + commit id: "add notifications" + branch fix/timeout + checkout fix/timeout + commit id: "bump timeout to 30s" + checkout main + merge fix/timeout + commit id: "v1.0 release" tag: "v1.0"`, + }, +}; + +export const GanttChart: Story = { + args: { + content: `gantt + title Canvas Build Timeline + dateFormat YYYY-MM-DD + axisFormat %b %d + + section Setup + Install CLI :done, setup1, 2026-05-01, 1d + Connect to API :done, setup2, after setup1, 1d + + section Build + Create trigger node :done, build1, after setup2, 2d + Add API health check :done, build2, after build1, 2d + Add branching logic :active, build3, after build2, 2d + Add notifications :build4, after build3, 3d + + section Deploy + Staging deploy :deploy1, after build4, 1d + Production deploy :deploy2, after deploy1, 1d`, + }, +}; + +export const XYChart: Story = { + args: { + content: `xychart-beta + title "Run Duration by Node (ms)" + x-axis ["Webhook", "API Call", "If Check", "SSH Deploy", "Notify"] + y-axis "Duration (ms)" 0 --> 5000 + bar [120, 2400, 50, 4200, 350] + line [120, 2400, 50, 4200, 350]`, + }, +}; + +export const Timeline: Story = { + args: { + content: `timeline + title Canvas Evolution + section v0.1 - MVP + Webhook trigger : Basic HTTP listener + Single API call : GET health endpoint + section v0.2 - Branching + If node added : Status code routing + Success path : Slack notification + Failure path : Email alert + section v0.3 - Reliability + Retry logic : 3 attempts with backoff + Timeout config : 30s per node + SSH deploy : Remote script execution + section v1.0 - Production + Monitoring : Run analytics dashboard + Approvals : Manual gate before deploy + Scheduling : Cron-based triggers`, + }, +}; + +export const PieChart: Story = { + args: { + content: `pie title Run Outcomes (Last 7 Days) + "Success" : 312 + "Failed" : 28 + "Timed Out" : 8 + "Cancelled" : 3`, + }, +}; diff --git a/web_src/src/components/AgentSidebar/widgets/MermaidWidget.tsx b/web_src/src/components/AgentSidebar/widgets/MermaidWidget.tsx new file mode 100644 index 0000000000..06e00b40ad --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/MermaidWidget.tsx @@ -0,0 +1,194 @@ +import { useEffect, useId, useRef, useState, useCallback } from "react"; +import mermaid from "mermaid"; +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; + +mermaid.initialize({ + startOnLoad: false, + theme: "base", + securityLevel: "strict", + fontFamily: "ui-sans-serif, system-ui, sans-serif", + themeVariables: { + primaryColor: "#ede9fe", + primaryTextColor: "#4c1d95", + primaryBorderColor: "#8b5cf6", + secondaryColor: "#ecfeff", + secondaryTextColor: "#164e63", + secondaryBorderColor: "#06b6d4", + tertiaryColor: "#fffbeb", + tertiaryTextColor: "#78350f", + tertiaryBorderColor: "#f59e0b", + lineColor: "#94a3b8", + textColor: "#334155", + nodeBorder: "#8b5cf6", + nodeTextColor: "#1e293b", + clusterBkg: "#f8fafc", + clusterBorder: "#e2e8f0", + defaultLinkColor: "#8b5cf6", + fontSize: "13px", + }, +}); + +interface MermaidWidgetProps { + content: string; +} + +export function MermaidWidget({ content }: MermaidWidgetProps) { + const id = useId().replace(/:/g, "m"); + const containerRef = useRef(null); + const [svg, setSvg] = useState(null); + const [error, setError] = useState(null); + const [expanded, setExpanded] = useState(false); + + useEffect(() => { + let cancelled = false; + + async function render() { + try { + const { svg: rendered } = await mermaid.render(`mermaid-${id}`, content.trim()); + if (!cancelled) { + setSvg(rendered); + setError(null); + } + } catch (err) { + if (!cancelled) { + setError(err instanceof Error ? err.message : "Failed to render diagram"); + setSvg(null); + } + document.getElementById(`dmermaid-${id}`)?.remove(); + } + } + + render(); + return () => { + cancelled = true; + }; + }, [content, id]); + + if (error) { + return ( +
+

Diagram error

+
{content.trim()}
+
+ ); + } + + if (!svg) { + return ( +
Rendering diagram...
+ ); + } + + return ( + <> +
setExpanded(true)} + className="my-4 rounded-lg border border-violet-200 bg-white p-3 shadow-sm overflow-x-auto cursor-pointer hover:border-violet-300 transition-colors [&_svg]:max-w-full [&_svg]:h-auto [&_svg]:mx-auto" + > +
+
+ + + + + Diagram + + + + + + ); +} + +function MermaidPanZoom({ svg }: { svg: string }) { + const containerRef = useRef(null); + const [scale, setScale] = useState(2.5); + const [translate, setTranslate] = useState({ x: 0, y: 0 }); + const dragRef = useRef<{ startX: number; startY: number; startTx: number; startTy: number } | null>(null); + + const handleWheel = useCallback((e: React.WheelEvent) => { + e.preventDefault(); + const delta = e.deltaY > 0 ? 0.9 : 1.1; + setScale((prev) => Math.min(Math.max(prev * delta, 0.2), 5)); + }, []); + + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + e.preventDefault(); + dragRef.current = { + startX: e.clientX, + startY: e.clientY, + startTx: translate.x, + startTy: translate.y, + }; + }, + [translate], + ); + + const handleMouseMove = useCallback((e: React.MouseEvent) => { + if (!dragRef.current) return; + setTranslate({ + x: dragRef.current.startTx + (e.clientX - dragRef.current.startX), + y: dragRef.current.startTy + (e.clientY - dragRef.current.startY), + }); + }, []); + + const handleMouseUp = useCallback(() => { + dragRef.current = null; + }, []); + + const resetView = useCallback(() => { + setScale(2.5); + setTranslate({ x: 0, y: 0 }); + }, []); + + return ( +
+
+ + + + {Math.round(scale * 100)}% +
+
+
+
+
+
+
+ ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/NodeChip.stories.tsx b/web_src/src/components/AgentSidebar/widgets/NodeChip.stories.tsx new file mode 100644 index 0000000000..63ec22a79a --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/NodeChip.stories.tsx @@ -0,0 +1,138 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { MemoryRouter } from "react-router-dom"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { RichMessage } from "./RichMessage"; +import { canvasKeys } from "@/hooks/useCanvasData"; +import type { CanvasesCanvas } from "@/api-client"; + +const ORG_ID = "1e880270-cb0b-4310-9479-3e01c14938aa"; +const CANVAS_ID = "05bb8e74-6f11-4d1c-bbfd-75d4a28303d6"; + +const mockCanvas: CanvasesCanvas = { + spec: { + nodes: [ + { + id: "webhook-trigger", + name: "Webhook Trigger", + type: "TYPE_TRIGGER", + component: "webhook", + configuration: { authentication: "none" }, + }, + { + id: "call-api", + name: "Call Target API", + type: "TYPE_ACTION", + component: "http", + configuration: { method: "GET", url: "https://httpbin.org/status/200" }, + }, + { + id: "check-result", + name: "Check API Result", + type: "TYPE_ACTION", + component: "if", + configuration: { expression: "{{ previous().data.statusCode >= 200 && previous().data.statusCode < 300 }}" }, + }, + { + id: "random-wait", + name: "Random Wait", + type: "TYPE_ACTION", + component: "wait", + configuration: { duration: "30" }, + }, + { + id: "notify-success", + name: "Notify Success", + type: "TYPE_ACTION", + component: "http", + configuration: { method: "POST", url: "https://httpbin.org/post" }, + }, + { + id: "ssh-deploy", + name: "SSH Deploy", + type: "TYPE_ACTION", + component: "ssh", + configuration: { host: "192.168.1.1", username: "ubuntu" }, + }, + ], + edges: [ + { sourceId: "webhook-trigger", targetId: "call-api", channel: "default" }, + { sourceId: "call-api", targetId: "check-result", channel: "success" }, + { sourceId: "check-result", targetId: "notify-success", channel: "true" }, + ], + }, +}; + +function createSeededClient() { + const qc = new QueryClient({ defaultOptions: { queries: { staleTime: Infinity } } }); + qc.setQueryData(canvasKeys.detail(ORG_ID, CANVAS_ID), mockCanvas); + return qc; +} + +const meta: Meta = { + title: "AgentSidebar/NodeChips", + component: RichMessage, + parameters: { layout: "padded" }, + decorators: [ + (Story) => { + const qc = createSeededClient(); + return ( + + +
+
+ +
+
+
+
+ ); + }, + ], +}; + +export default meta; +type Story = StoryObj; + +export const NodeReferences: Story = { + args: { + content: `Check the [Webhook Trigger](node:webhook-trigger) and the [Call Target API](node:call-api) node.`, + canvasId: CANVAS_ID, + organizationId: ORG_ID, + }, +}; + +export const AllComponentTypes: Story = { + args: { + content: `Node types: + +- Trigger: [Webhook Trigger](node:webhook-trigger) +- HTTP: [Call Target API](node:call-api) +- If: [Check API Result](node:check-result) +- Wait: [Random Wait](node:random-wait) +- SSH: [SSH Deploy](node:ssh-deploy) +- Notify: [Notify Success](node:notify-success)`, + canvasId: CANVAS_ID, + organizationId: ORG_ID, + }, +}; + +export const NodesInTable: Story = { + args: { + content: `| Node | Component | Notes | +|------|-----------|-------| +| [Webhook Trigger](node:webhook-trigger) | webhook | Entry point | +| [Call Target API](node:call-api) | http | GET request | +| [Check API Result](node:check-result) | if | Status check | +| [Random Wait](node:random-wait) | wait | 30s delay |`, + canvasId: CANVAS_ID, + organizationId: ORG_ID, + }, +}; + +export const UnknownNode: Story = { + args: { + content: `This references a [Missing Node](node:does-not-exist) that doesn't exist on the canvas.`, + canvasId: CANVAS_ID, + organizationId: ORG_ID, + }, +}; diff --git a/web_src/src/components/AgentSidebar/widgets/NodeChip.tsx b/web_src/src/components/AgentSidebar/widgets/NodeChip.tsx new file mode 100644 index 0000000000..13d421a6d8 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/NodeChip.tsx @@ -0,0 +1,183 @@ +import { useCallback } from "react"; +import { useNavigate } from "react-router-dom"; +import { useQueryClient } from "@tanstack/react-query"; +import { cn } from "@/lib/utils"; +import { canvasKeys } from "@/hooks/useCanvasData"; +import { getHeaderIconSrc } from "@/ui/componentSidebar/integrationIcons"; +import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/components/ui/hover-card"; +import { Clock, Filter, Globe, Hand, Merge, Play, Split, Terminal, Webhook, type LucideIcon } from "lucide-react"; +import type { CanvasesCanvas, SuperplaneComponentsNode } from "@/api-client"; + +const COMPONENT_ICONS: Record = { + http: Globe, + wait: Clock, + webhook: Webhook, + start: Play, + if: Split, + filter: Filter, + ssh: Terminal, + approval: Hand, + merge: Merge, + schedule: Clock, +}; + +interface NodeChipProps { + nodeId: string; + label: string; + canvasId: string; + organizationId: string; +} + +export function NodeChipFromLink({ + nodeId, + rawLabel, + canvasId, + organizationId, +}: { + nodeId: string; + rawLabel?: string; + canvasId: string; + organizationId: string; +}) { + const label = rawLabel && rawLabel !== "node" ? rawLabel : nodeId; + return ; +} + +function getChipStyle(node?: SuperplaneComponentsNode) { + if (!node) return "bg-slate-100 text-slate-600 ring-slate-300"; + return node.type === "TYPE_TRIGGER" + ? "bg-purple-100 text-purple-700 ring-purple-300 hover:bg-purple-200" + : "bg-blue-100 text-blue-700 ring-blue-300 hover:bg-blue-200"; +} + +function NodeIconInline({ component, isTrigger }: { component?: string; isTrigger: boolean }) { + const iconSrc = component ? getHeaderIconSrc(component) : undefined; + const Icon = component ? COMPONENT_ICONS[component] : undefined; + if (iconSrc) return ; + if (Icon) return ; + return ; +} + +export function NodeChip({ nodeId, label, canvasId, organizationId }: NodeChipProps) { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + + const canvas = queryClient.getQueryData(canvasKeys.detail(organizationId, canvasId)); + const node = canvas?.spec?.nodes?.find((n) => n.id === nodeId); + const edges = canvas?.spec?.edges ?? []; + const isTrigger = node?.type === "TYPE_TRIGGER"; + + const handleClick = useCallback(() => { + navigate(`/${organizationId}/canvases/${canvasId}?sidebar=1&node=${nodeId}`); + window.dispatchEvent(new CustomEvent("agent:focus-node", { detail: { nodeId } })); + }, [navigate, organizationId, canvasId, nodeId]); + + return ( + + + + + {node && ( + + + + )} + + ); +} + +function NodeHoverContent({ + node, + edges, +}: { + node: SuperplaneComponentsNode; + edges: NonNullable["edges"]; +}) { + const isTrigger = node.type === "TYPE_TRIGGER"; + const iconSrc = node.component ? getHeaderIconSrc(node.component) : undefined; + const NodeIcon = node.component ? COMPONENT_ICONS[node.component] : undefined; + const config = node.configuration ?? {}; + + // Count connections + const incoming = (edges ?? []).filter((e) => e.targetId === node.id).length; + const outgoing = (edges ?? []).filter((e) => e.sourceId === node.id).length; + + // Extract key config summary + const summary = getConfigSummary(node.component, config); + + return ( +
+ {/* Header */} +
+ {iconSrc ? ( + + ) : NodeIcon ? ( + + ) : ( + + )} +
+

{node.name || node.id}

+

+ {node.component} · {isTrigger ? "Trigger" : "Action"} +

+
+
+ + {/* Config summary */} + {summary && ( +
+

{summary}

+
+ )} + + {/* Connections */} +
+ {incoming} incoming + · + {outgoing} outgoing +
+ + {/* Error/warning */} + {node.errorMessage && ( +
+ ⚠ {node.errorMessage} +
+ )} +
+ ); +} + +const CONFIG_SUMMARIZERS: Record) => string> = { + http: (c) => `${c.method || "GET"} ${c.url || ""}`, + ssh: (c) => `${c.username || "root"}@${c.host || ""}`, + if: (c) => String(c.expression || ""), + filter: (c) => String(c.expression || ""), + wait: (c) => `Wait: ${c.duration || c.waitFor || ""}`, + webhook: (c) => `Auth: ${c.authentication || "none"}`, + schedule: (c) => `Cron: ${c.cron || ""}`, + approval: (c) => String(c.message || "Approval required"), +}; + +function getConfigSummary(component?: string, config?: Record): string | null { + if (!component || !config) return null; + const summarizer = CONFIG_SUMMARIZERS[component]; + return summarizer ? summarizer(config) : null; +} +// already at end of file diff --git a/web_src/src/components/AgentSidebar/widgets/RichMessage.stories.tsx b/web_src/src/components/AgentSidebar/widgets/RichMessage.stories.tsx new file mode 100644 index 0000000000..1a77846500 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/RichMessage.stories.tsx @@ -0,0 +1,379 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { RichMessage } from "./RichMessage"; + +const meta: Meta = { + title: "AgentSidebar/RichMessage", + component: RichMessage, + parameters: { + layout: "padded", + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const PureMarkdown: Story = { + args: { + content: `## Canvas Deployed! + +Here's what was built: + +- **5 nodes** configured +- **4 edges** connecting them +- Webhook trigger → API call → branch → notifications + +\`\`\`yaml +apiVersion: v1 +kind: Canvas +metadata: + name: api-health-check +\`\`\` + +> Note: The canvas is now live and accepting webhook events.`, + }, +}; + +export const Buttons: Story = { + args: { + content: `I can set up a few different workflow patterns for you. + +:::buttons +Which pattern would you like? +- Webhook → API → Notify +- Schedule → Health Check → Alert +- GitHub Push → Deploy → Verify +- Custom (describe your workflow) +:::`, + }, +}; + +export const YAMLCodeBlock: Story = { + args: { + content: `Here's the canvas configuration: + +\`\`\`yaml +apiVersion: v1 +kind: Canvas +metadata: + name: api-health-check + id: 05bb8e74-6f11-4d1c-bbfd-75d4a28303d6 +spec: + nodes: + - id: webhook-trigger + name: Receive Request + type: TYPE_TRIGGER + component: webhook + configuration: + authentication: "none" + - id: call-api + name: Call Target API + type: TYPE_ACTION + component: http + configuration: + method: GET + url: "https://api.example.com/health" + json: "" + successCodes: "200" + timeoutSeconds: 30 +\`\`\``, + }, +}; + +export const BashCodeBlock: Story = { + args: { + content: `Run these commands to set up: + +\`\`\`bash +curl -fsSL https://install.superplane.com/install.sh | sh +export PATH="$HOME/.local/bin:$PATH" +superplane connect https://app.superplane.com your-token-here +superplane canvases list +\`\`\` + +The CLI should now be ready to use.`, + }, +}; + +export const JSONCodeBlock: Story = { + args: { + content: `The webhook payload looks like this: + +\`\`\`json +{ + "event": "push", + "repository": { + "full_name": "superplanehq/superplane", + "default_branch": "main" + }, + "ref": "refs/heads/main", + "commits": [ + { + "id": "abc123", + "message": "fix: resolve timeout issue", + "author": { "name": "Alex" } + } + ] +} +\`\`\``, + }, +}; + +export const DeployButtons: Story = { + args: { + content: `I've prepared the canvas. Ready to deploy. + +:::buttons +Where should this be deployed? +- DigitalOcean +- Google Cloud +- Hetzner +- AWS +:::`, + }, +}; + +export const Confirmation: Story = { + args: { + content: `I'm about to overwrite the existing canvas configuration. + +:::confirm +message: This will replace all 5 existing nodes with the new pipeline. This action cannot be undone. +yes: Overwrite Canvas +no: Cancel +:::`, + }, +}; + +export const LineChart: Story = { + args: { + content: `Here's your run success rate for the past week: + +:::chart +type: line +title: Run Success Rate (Last 7 Days) +x: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] +series: + - name: Successful + data: [12, 15, 11, 14, 13, 8, 10] + color: "#22c55e" + - name: Failed + data: [2, 1, 3, 0, 2, 1, 0] + color: "#ef4444" +::: + +Overall success rate: **90.4%** — 3 failures on Wednesday were due to a timeout in the API call node.`, + }, +}; + +export const BarChart: Story = { + args: { + content: `Execution count by node: + +:::chart +type: bar +title: Executions Per Node (Last 24h) +x: ["Webhook", "API Call", "Check Status", "Notify OK", "Notify Fail"] +series: + - name: Executions + data: [48, 48, 48, 41, 7] + color: "#8b5cf6" +:::`, + }, +}; + +export const PieChart: Story = { + args: { + content: `Run outcome breakdown: + +:::chart +type: pie +title: Run Outcomes (Last 30 Days) +data: + - name: Success + value: 312 + color: "#22c55e" + - name: Failed + value: 28 + color: "#ef4444" + - name: Timed Out + value: 8 + color: "#f59e0b" + - name: Cancelled + value: 3 + color: "#94a3b8" +:::`, + }, +}; + +export const Steps: Story = { + args: { + content: `Setting up your canvas: + +:::steps +- [x] Install CLI +- [x] Connect to SuperPlane API +- [x] Write canvas YAML +- [ ] Deploy canvas +- [ ] Verify node configuration +:::`, + }, +}; + +export const SuccessBanner: Story = { + args: { + content: `:::success +Canvas "api-health-check-with-alerts" deployed successfully! 5 nodes, 4 edges, all validations passed. +::: + +The webhook URL is: \`https://app.superplane.com/hooks/05bb8e74-6f11-4d1c-bbfd-75d4a28303d6\``, + }, +}; + +export const ErrorBanner: Story = { + args: { + content: `:::error +Deployment failed: Node "call-api" has validation error — json field is required for HTTP actions. +::: + +I'll fix this and retry. The issue is that GET requests still need an empty \`json: ""\` field.`, + }, +}; + +export const CollapsibleSection: Story = { + args: { + content: `Canvas deployed. Here's the full YAML if you want to review: + +:::collapse title="Full Canvas YAML" +apiVersion: v1 +kind: Canvas +metadata: + name: api-health-check-with-alerts + id: 05bb8e74-6f11-4d1c-bbfd-75d4a28303d6 +spec: + nodes: + - id: webhook-trigger + name: Receive Request + type: TYPE_TRIGGER + component: webhook + configuration: + authentication: "none" + - id: call-api + name: Call Target API + type: TYPE_ACTION + component: http + configuration: + method: GET + url: "https://api.example.com/health" + json: "" + successCodes: "200" + timeoutSeconds: 30 +:::`, + }, +}; + +export const SimpleTable: Story = { + args: { + content: `Here are the nodes in your canvas: + +| Node | Type | Component | Status | +|------|------|-----------|--------| +| Webhook Trigger | Trigger | webhook | Active | +| Call API | Action | http | Active | +| Check Status | Action | if | Active | +| Notify Success | Action | http | Active | +| Notify Failure | Action | http | Active |`, + }, +}; + +export const RunHistoryTable: Story = { + args: { + content: `## Recent Runs + +Last 5 runs for this canvas: + +| Run ID | Started | Duration | Status | Trigger | +|--------|---------|----------|--------|---------| +| #1247 | 2 min ago | 3.2s | ✅ Success | Webhook | +| #1246 | 17 min ago | 2.8s | ✅ Success | Webhook | +| #1245 | 32 min ago | 12.1s | ❌ Failed | Webhook | +| #1244 | 1h ago | 3.0s | ✅ Success | Schedule | +| #1243 | 1h 15m ago | 2.9s | ✅ Success | Schedule | + +Run #1245 failed at the **Call API** node with a timeout error.`, + }, +}; + +export const NodeComparisonTable: Story = { + args: { + content: `### Node Performance Comparison + +| Node | Avg Duration | Success Rate | Executions | +|------|-------------|-------------|------------| +| Webhook Trigger | 12ms | 100% | 1,247 | +| Call API | 2.4s | 95.8% | 1,247 | +| Check Status | 8ms | 100% | 1,195 | +| Notify Success | 340ms | 99.2% | 1,142 | +| Notify Failure | 380ms | 98.1% | 53 | + +The **Call API** node has the lowest success rate — consider adding retry logic.`, + }, +}; + +export const MermaidDiagram: Story = { + args: { + content: `Here's the flow for your canvas: + +\`\`\`mermaid +flowchart LR + A[Webhook Trigger] --> B[Call API] + B --> C{Check Status} + C -->|200 OK| D[Notify Success] + C -->|Error| E[Notify Failure] +\`\`\` + +The webhook will accept incoming requests and route them through the API health check before notifying via the appropriate channel.`, + }, +}; + +export const MixedContent: Story = { + args: { + content: `## Analysis Complete + +I analyzed 351 runs from the past 30 days. Here's what I found: + +:::chart +type: line +title: Daily Run Volume +x: ["W1", "W2", "W3", "W4"] +series: + - name: Runs + data: [78, 92, 88, 93] + color: "#8b5cf6" +::: + +:::success +Overall health is excellent — 96% success rate. +::: + +### Recommendations + +1. The "Check Status" node has the highest failure rate (4.2%) +2. Consider adding retry logic to the API call +3. Timeout could be increased from 10s to 15s + +:::buttons +What would you like to do? +- Add retry to API call +- Increase timeout +- Show me the failing runs +- Do nothing +:::`, + }, +}; diff --git a/web_src/src/components/AgentSidebar/widgets/RichMessage.tsx b/web_src/src/components/AgentSidebar/widgets/RichMessage.tsx new file mode 100644 index 0000000000..634c9e01d0 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/RichMessage.tsx @@ -0,0 +1,155 @@ +import ReactMarkdown, { defaultUrlTransform } from "react-markdown"; +import remarkBreaks from "remark-breaks"; +import remarkGfm from "remark-gfm"; +import { parseAgentContent, type Segment } from "./parser"; +import { ButtonsWidget } from "./ButtonsWidget"; +import { ConfirmWidget } from "./ConfirmWidget"; +import { ChartWidget } from "./ChartWidget"; +import { CollapseWidget } from "./CollapseWidget"; +import { StepsWidget } from "./StepsWidget"; +import { BannerWidget } from "./BannerWidget"; +import { MermaidWidget } from "./MermaidWidget"; +import { CodeBlockWidget } from "./CodeBlockWidget"; +import { RunChipFromLink } from "./RunChip"; +import { NodeChipFromLink } from "./NodeChip"; + +const MARKDOWN_CLASSES = + "max-w-none [&_h1]:mb-1.5 [&_h1]:mt-1 [&_h1]:text-base [&_h1]:font-semibold [&_h1:first-child]:mt-0 " + + "[&_h2]:mb-1 [&_h2]:mt-1 [&_h2]:text-sm [&_h2]:font-semibold [&_h2:first-child]:mt-0 " + + "[&_h3]:mb-0.5 [&_h3]:mt-1 [&_h3]:text-sm [&_h3]:font-semibold [&_h3:first-child]:mt-0 " + + "[&_p]:mb-2 [&_p]:leading-relaxed [&_p:last-child]:mb-0 " + + "[&_ol]:mb-2 [&_ol]:ml-5 [&_ol]:list-decimal [&_ul]:mb-2 [&_ul]:ml-5 [&_ul]:list-disc [&_li]:mb-0.5 " + + "[&_blockquote]:my-2 [&_blockquote]:border-l-2 [&_blockquote]:border-slate-300 [&_blockquote]:pl-3 " + + "[&_code]:rounded [&_code]:bg-slate-200/70 [&_code]:px-1 [&_code]:py-0.5 [&_code]:text-xs " + + "[&_pre]:my-2 [&_pre]:overflow-auto [&_pre]:rounded [&_pre]:bg-slate-200/70 [&_pre]:p-2 " + + "[&_pre_code]:bg-transparent [&_pre_code]:p-0 " + + "[&_a]:underline [&_a]:underline-offset-2 [&_a]:decoration-current " + + "[&_table]:w-full [&_table]:text-xs [&_table]:border-collapse " + + "[&_thead]:bg-slate-50 [&_th]:px-3 [&_th]:py-1.5 [&_th]:text-left [&_th]:font-semibold [&_th]:text-slate-700 " + + "[&_th]:border-b [&_th]:border-slate-200 " + + "[&_td]:px-3 [&_td]:py-1.5 [&_td]:text-slate-600 [&_td]:border-b [&_td]:border-slate-100 " + + "[&_tbody_tr:nth-child(even)]:bg-slate-50/60 " + + "[&_tr:last-child_td]:border-b-0 [&_tr:hover]:bg-violet-50/50"; + +interface RichMessageProps { + content: string; + onAction?: (text: string) => void; + canvasId?: string; + organizationId?: string; +} + +export function RichMessage({ content, onAction, canvasId, organizationId }: RichMessageProps) { + const segments = parseAgentContent(content); + + return ( +
+ {segments.map((segment, i) => ( + + ))} +
+ ); +} + +function SegmentRenderer({ + segment, + onAction, + canvasId, + organizationId, +}: { + segment: Segment; + onAction?: (text: string) => void; + canvasId?: string; + organizationId?: string; +}) { + switch (segment.type) { + case "markdown": + return ( +
+ (url.startsWith("run:") || url.startsWith("node:") ? url : defaultUrlTransform(url))} + components={{ + a: ({ children, href }) => { + const runMatch = href?.match(/^run:([0-9a-f-]{36})(?:~(.+))?/); + if (runMatch && canvasId && organizationId) { + const label = typeof children === "string" ? children : undefined; + return ( + + ); + } + + const nodeMatch = href?.match(/^node:(.+)$/); + if (nodeMatch && canvasId && organizationId) { + const label = typeof children === "string" ? children : undefined; + return ( + + ); + } + + return ( + + {children} + + ); + }, + code: ({ className, children, ...props }) => { + const match = /language-(\w+)/.exec(className || ""); + const codeStr = String(children).replace(/\n$/, ""); + // Only use CodeBlockWidget for fenced code blocks (with language class) + if (match) { + return ; + } + // Inline code + return ( + + {children} + + ); + }, + pre: ({ children }) => <>{children}, + table: ({ children, ...props }) => ( +
+ {children}
+
+ ), + }} + > + {segment.content} +
+
+ ); + case "buttons": + return ; + case "confirm": + return ; + case "chart": + return ; + case "collapse": + return ; + case "mermaid": + return ; + case "steps": + return ; + case "success": + return ; + case "error": + return ; + } +} diff --git a/web_src/src/components/AgentSidebar/widgets/RunChip.stories.tsx b/web_src/src/components/AgentSidebar/widgets/RunChip.stories.tsx new file mode 100644 index 0000000000..0a330e789d --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/RunChip.stories.tsx @@ -0,0 +1,59 @@ +import type { Meta, StoryObj } from "@storybook/react-vite"; +import { MemoryRouter } from "react-router-dom"; +import { RichMessage } from "./RichMessage"; + +const meta: Meta = { + title: "AgentSidebar/RunChips", + component: RichMessage, + parameters: { + layout: "padded", + }, + decorators: [ + (Story) => ( + +
+
+ +
+
+
+ ), + ], +}; + +export default meta; +type Story = StoryObj; + +export const AllStatuses: Story = { + args: { + content: `Run status examples: + +- [Health check OK](run:78848cb6-0c52-4c69-8e47-b6631bd703ec~passed) — 45s +- [API timeout](run:2999a5f1-1234-5678-9abc-def012345678~failed) — node 3 timed out +- [Rolling deploy](run:366b0a12-1111-2222-3333-444455556666~running) — in progress +- [User aborted](run:e63e35a0-5555-6666-7777-888899990000~cancelled) — stopped manually`, + canvasId: "05bb8e74-6f11-4d1c-bbfd-75d4a28303d6", + organizationId: "1e880270-cb0b-4310-9479-3e01c14938aa", + }, +}; + +export const RunsInTable: Story = { + args: { + content: `| Run | Duration | Result | +|-----|----------|--------| +| [Health check OK](run:78848cb6-0c52-4c69-8e47-b6631bd703ec~passed) | 45s | All nodes passed | +| [API timeout](run:2999a5f1-1234-5678-9abc-def012345678~failed) | 0s | Node 3 timed out | +| [Deploy staging](run:1e8cf8a2-abcd-ef01-2345-678901234567~passed) | 36s | Clean deploy | +| [In progress](run:366b0a12-1111-2222-3333-444455556666~running) | — | Waiting |`, + canvasId: "05bb8e74-6f11-4d1c-bbfd-75d4a28303d6", + organizationId: "1e880270-cb0b-4310-9479-3e01c14938aa", + }, +}; + +export const InlineText: Story = { + args: { + content: `The latest run [Health check OK](run:78848cb6-0c52-4c69-8e47-b6631bd703ec~passed) completed in 45s. The previous run [API timeout](run:2999a5f1-1234-5678-9abc-def012345678~failed) failed due to a timeout at the SSH node.`, + canvasId: "05bb8e74-6f11-4d1c-bbfd-75d4a28303d6", + organizationId: "1e880270-cb0b-4310-9479-3e01c14938aa", + }, +}; diff --git a/web_src/src/components/AgentSidebar/widgets/RunChip.tsx b/web_src/src/components/AgentSidebar/widgets/RunChip.tsx new file mode 100644 index 0000000000..93cd94e97a --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/RunChip.tsx @@ -0,0 +1,60 @@ +import { Rabbit } from "lucide-react"; +import { useNavigate } from "react-router-dom"; +import { cn } from "@/lib/utils"; +import { RUN_STATUS_META, type RunStatusKey } from "@/ui/Runs/runPresentation"; + +interface RunChipProps { + runId: string; + label: string; + status: RunStatusKey; + canvasId: string; + organizationId: string; +} + +function parseStatus(raw?: string): RunStatusKey { + if (!raw) return "unknown"; + const s = raw.toLowerCase(); + if (s === "passed" || s === "success") return "passed"; + if (s === "failed" || s === "error" || s === "failure") return "failed"; + if (s === "running" || s === "started") return "running"; + if (s === "cancelled") return "cancelled"; + return "unknown"; +} + +export function RunChipFromLink({ + runId, + rawLabel, + rawStatus, + canvasId, + organizationId, +}: { + runId: string; + rawLabel?: string; + rawStatus?: string; + canvasId: string; + organizationId: string; +}) { + const status = parseStatus(rawStatus); + const label = rawLabel && rawLabel !== "run" ? rawLabel : `#${runId.substring(0, 8)}`; + return ; +} + +export function RunChip({ runId, label, status, canvasId, organizationId }: RunChipProps) { + const navigate = useNavigate(); + const meta = RUN_STATUS_META[status]; + + return ( + + ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/StepsWidget.tsx b/web_src/src/components/AgentSidebar/widgets/StepsWidget.tsx new file mode 100644 index 0000000000..50bff2c3f1 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/StepsWidget.tsx @@ -0,0 +1,33 @@ +import { Check, Loader2 } from "lucide-react"; +import { cn } from "@/lib/utils"; +import type { StepItem } from "./parser"; + +interface StepsWidgetProps { + items: StepItem[]; +} + +export function StepsWidget({ items }: StepsWidgetProps) { + const firstPending = items.findIndex((i) => !i.done); + + return ( +
+ {items.map((item, i) => { + const isActive = i === firstPending; + return ( +
+ {item.done ? ( + + ) : isActive ? ( + + ) : ( +
+ )} + + {item.text} + +
+ ); + })} +
+ ); +} diff --git a/web_src/src/components/AgentSidebar/widgets/parser.test.ts b/web_src/src/components/AgentSidebar/widgets/parser.test.ts new file mode 100644 index 0000000000..3ed8b27547 --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/parser.test.ts @@ -0,0 +1,141 @@ +import { describe, it, expect } from "vitest"; +import { parseAgentContent } from "./parser"; + +describe("parseAgentContent", () => { + it("parses pure markdown", () => { + const content = "Hello **world**"; + const segments = parseAgentContent(content); + expect(segments).toEqual([{ type: "markdown", content: "Hello **world**" }]); + }); + + it("parses buttons block", () => { + const content = `:::buttons +- Option A +- Option B +:::`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(1); + expect(segments[0]).toEqual({ + type: "buttons", + prompt: "", + items: ["Option A", "Option B"], + }); + }); + + it("parses confirm block", () => { + const content = `:::confirm +message: Are you sure? +yes: Yes +no: No +:::`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(1); + expect(segments[0]).toEqual({ + type: "confirm", + message: "Are you sure?", + yes: "Yes", + no: "No", + }); + }); + + it("parses steps block", () => { + const content = `:::steps +- [x] Done step +- [ ] Pending step +:::`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(1); + expect(segments[0]).toEqual({ + type: "steps", + items: [ + { done: true, text: "Done step" }, + { done: false, text: "Pending step" }, + ], + }); + }); + + it("parses success banner", () => { + const content = `:::success +All good! +:::`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(1); + expect(segments[0]).toEqual({ + type: "success", + content: "All good!", + }); + }); + + it("parses mixed content", () => { + const content = `Here is some text. + +:::buttons +- Button 1 +- Button 2 +::: + +More text here.`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(3); + expect(segments[0].type).toBe("markdown"); + expect(segments[1].type).toBe("buttons"); + expect(segments[2].type).toBe("markdown"); + }); + + it("handles chart JSON", () => { + const content = `:::chart +{"type":"line","data":[{"x":1,"y":2}],"xKey":"x","yKeys":["y"]} +:::`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(1); + expect(segments[0]).toEqual({ + type: "chart", + config: { + type: "line", + data: [{ x: 1, y: 2 }], + xKey: "x", + yKeys: ["y"], + }, + }); + }); + + it("handles collapse block", () => { + const content = `:::collapse title="Click to expand" +Hidden content +:::`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(1); + expect(segments[0]).toEqual({ + type: "collapse", + title: "Click to expand", + content: "Hidden content", + }); + }); + + it("handles empty content", () => { + const segments = parseAgentContent(""); + expect(segments).toEqual([]); + }); + + it("handles block at start", () => { + const content = `:::success +Great! +::: +Some text.`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(2); + expect(segments[0].type).toBe("success"); + expect(segments[1].type).toBe("markdown"); + }); + + it("handles block at end", () => { + const content = `Some text. +:::error +Oops! +:::`; + const segments = parseAgentContent(content); + expect(segments).toHaveLength(2); + expect(segments[0].type).toBe("markdown"); + expect(segments[1].type).toBe("error"); + }); +}); diff --git a/web_src/src/components/AgentSidebar/widgets/parser.ts b/web_src/src/components/AgentSidebar/widgets/parser.ts new file mode 100644 index 0000000000..9ccd8f568a --- /dev/null +++ b/web_src/src/components/AgentSidebar/widgets/parser.ts @@ -0,0 +1,201 @@ +import YAML from "js-yaml"; + +// --- Types --- + +export type MarkdownSegment = { type: "markdown"; content: string }; +export type ButtonsSegment = { type: "buttons"; prompt: string; items: string[] }; +export type ConfirmSegment = { type: "confirm"; message: string; yes: string; no: string }; +export type ChartSegment = { type: "chart"; config: ChartConfig }; +export type CollapseSegment = { type: "collapse"; title: string; content: string }; +export type MermaidSegment = { type: "mermaid"; content: string }; +export type StepsSegment = { type: "steps"; items: StepItem[] }; +export type SuccessSegment = { type: "success"; content: string }; +export type ErrorSegment = { type: "error"; content: string }; + +export type Segment = + | MarkdownSegment + | ButtonsSegment + | ConfirmSegment + | ChartSegment + | CollapseSegment + | MermaidSegment + | StepsSegment + | SuccessSegment + | ErrorSegment; + +export type StepItem = { done: boolean; text: string }; + +export type ChartConfig = { + type: "line" | "bar" | "area" | "pie"; + title?: string; + x?: string[]; + series?: { name: string; data: number[]; color?: string }[]; + data?: { name: string; value: number; color?: string }[]; +}; + +// --- Parser --- + +const BLOCK_RE = /^\s*:::(\w+)(?:\s+(.*))?$/; +const BLOCK_END_RE = /^\s*:::$/; +const MERMAID_FENCE_START = /^\s*```mermaid\s*$/; +const FENCE_END = /^\s*```\s*$/; + +export function parseAgentContent(content: string): Segment[] { + if (!content) return []; + + const lines = content.split("\n"); + const segments: Segment[] = []; + let markdownBuffer: string[] = []; + let blockType: string | null = null; + let blockMeta = ""; + let blockLines: string[] = []; + + function flushMarkdown() { + const text = markdownBuffer.join("\n").trim(); + if (text) { + segments.push({ type: "markdown", content: text }); + } + markdownBuffer = []; + } + + function flushBlock() { + if (!blockType) return; + const raw = blockLines.join("\n"); + const segment = parseBlock(blockType, blockMeta, raw); + if (segment) { + segments.push(segment); + } + blockType = null; + blockMeta = ""; + blockLines = []; + } + + let inMermaidFence = false; + let mermaidLines: string[] = []; + + for (const line of lines) { + // Handle ```mermaid fenced code blocks + if (inMermaidFence) { + if (FENCE_END.test(line.trim())) { + flushMarkdown(); + segments.push({ type: "mermaid", content: mermaidLines.join("\n") }); + mermaidLines = []; + inMermaidFence = false; + } else { + mermaidLines.push(line); + } + continue; + } + + if (blockType) { + if (BLOCK_END_RE.test(line.trim())) { + flushBlock(); + } else { + blockLines.push(line); + } + } else if (MERMAID_FENCE_START.test(line.trim())) { + flushMarkdown(); + inMermaidFence = true; + mermaidLines = []; + } else { + const match = line.match(BLOCK_RE); + if (match) { + flushMarkdown(); + blockType = match[1]; + blockMeta = match[2] || ""; + } else { + markdownBuffer.push(line); + } + } + } + + // Handle unclosed blocks gracefully + if (blockType) { + flushBlock(); + } + flushMarkdown(); + + return segments; +} + +function parseBlock(type: string, meta: string, raw: string): Segment | null { + switch (type) { + case "buttons": + return parseButtons(raw); + case "confirm": + return parseConfirm(raw); + case "chart": + return parseChart(raw); + case "collapse": + return parseCollapse(meta, raw); + case "steps": + return parseSteps(raw); + case "success": + return { type: "success", content: raw.trim() }; + case "error": + return { type: "error", content: raw.trim() }; + default: + return { type: "markdown", content: `:::${type} ${meta}\n${raw}\n:::` }; + } +} + +function parseButtons(raw: string): ButtonsSegment { + const lines = raw.split("\n").filter((l) => l.trim()); + const items: string[] = []; + const promptLines: string[] = []; + + for (const line of lines) { + const trimmed = line.trim(); + if (/^[-*]\s/.test(trimmed)) { + items.push(trimmed.replace(/^[-*]\s*/, "").trim()); + } else { + promptLines.push(trimmed); + } + } + + return { type: "buttons", prompt: promptLines.join("\n"), items }; +} + +function parseConfirm(raw: string): ConfirmSegment { + try { + const parsed = YAML.load(raw) as Record; + return { + type: "confirm", + message: parsed.message || raw.trim(), + yes: parsed.yes || "Yes", + no: parsed.no || "No", + }; + } catch { + return { type: "confirm", message: raw.trim(), yes: "Yes", no: "No" }; + } +} + +function parseChart(raw: string): ChartSegment { + try { + const config = YAML.load(raw) as ChartConfig; + return { type: "chart", config }; + } catch { + return { type: "chart", config: { type: "bar", title: "Parse Error" } }; + } +} + +function parseCollapse(meta: string, raw: string): CollapseSegment { + const titleMatch = meta.match(/title="([^"]+)"/); + return { + type: "collapse", + title: titleMatch ? titleMatch[1] : "Details", + content: raw, + }; +} + +function parseSteps(raw: string): StepsSegment { + const items = raw + .split("\n") + .filter((l) => l.trim().startsWith("- [")) + .map((l) => { + const done = l.includes("[x]") || l.includes("[X]"); + const text = l.replace(/^[-*]\s*\[[ xX]\]\s*/, "").trim(); + return { done, text }; + }); + return { type: "steps", items }; +} diff --git a/web_src/src/components/ui/chart.tsx b/web_src/src/components/ui/chart.tsx new file mode 100644 index 0000000000..178dd05eab --- /dev/null +++ b/web_src/src/components/ui/chart.tsx @@ -0,0 +1,306 @@ +import * as React from "react"; +import * as RechartsPrimitive from "recharts"; +import type { TooltipValueType } from "recharts"; + +import { cn } from "@/lib/utils"; + +// Format: { THEME_NAME: CSS_SELECTOR } +const THEMES = { light: "", dark: ".dark" } as const; + +const INITIAL_DIMENSION = { width: 320, height: 200 } as const; +type TooltipNameType = number | string; + +export type ChartConfig = Record< + string, + { + label?: React.ReactNode; + icon?: React.ComponentType; + } & ({ color?: string; theme?: never } | { color?: never; theme: Record }) +>; + +type ChartContextProps = { + config: ChartConfig; +}; + +const ChartContext = React.createContext(null); + +function useChart() { + const context = React.useContext(ChartContext); + + if (!context) { + throw new Error("useChart must be used within a "); + } + + return context; +} + +function ChartContainer({ + id, + className, + children, + config, + initialDimension = INITIAL_DIMENSION, + ...props +}: React.ComponentProps<"div"> & { + config: ChartConfig; + children: React.ComponentProps["children"]; + initialDimension?: { + width: number; + height: number; + }; +}) { + const uniqueId = React.useId(); + const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`; + + return ( + +
+ + + {children} + +
+
+ ); +} + +const ChartStyle = ({ id, config }: { id: string; config: ChartConfig }) => { + const colorConfig = Object.entries(config).filter(([, config]) => config.theme ?? config.color); + + if (!colorConfig.length) { + return null; + } + + return ( +