diff --git a/bun.lock b/bun.lock index cabd7313..d2e4bd11 100644 --- a/bun.lock +++ b/bun.lock @@ -115,6 +115,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@shipsec/backend-client": "workspace:*", "@shipsec/shared": "workspace:*", + "@types/react-syntax-highlighter": "^15.5.13", "@uiw/react-markdown-preview": "^5.1.5", "ai": "^5.0.76", "ansi_up": "^6.0.6", @@ -126,12 +127,14 @@ "markdown-it-html5-embed": "^1.0.0", "markdown-it-link-attributes": "^4.0.1", "markdown-it-task-lists": "^2.1.1", + "mermaid": "^11.12.2", "monaco-editor": "^0.55.1", "postcss": "^8.5.6", "posthog-js": "^1.288.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.3", + "react-syntax-highlighter": "^16.1.0", "reactflow": "^11.11.4", "tailwind-merge": "^3.3.1", "xterm": "^5.3.0", @@ -317,6 +320,8 @@ "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="], + "@asamuzakjp/css-color": ["@asamuzakjp/css-color@4.1.1", "", { "dependencies": { "@csstools/css-calc": "^2.1.4", "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", "lru-cache": "^11.2.4" } }, "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ=="], "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@6.7.7", "", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.5" } }, "sha512-8CO/UQ4tzDd7ula+/CVimJIVWez99UJlbMyIgk8xOnhAVPKLnBZmUFYVgugS441v2ZqUq5EnSh6B0Ua0liSFAA=="], @@ -447,6 +452,18 @@ "@borewit/text-codec": ["@borewit/text-codec@0.2.1", "", {}, "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw=="], + "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="], + + "@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.0.3", "", { "dependencies": { "@chevrotain/gast": "11.0.3", "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ=="], + + "@chevrotain/gast": ["@chevrotain/gast@11.0.3", "", { "dependencies": { "@chevrotain/types": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q=="], + + "@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.0.3", "", {}, "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA=="], + + "@chevrotain/types": ["@chevrotain/types@11.0.3", "", {}, "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ=="], + + "@chevrotain/utils": ["@chevrotain/utils@11.0.3", "", {}, "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ=="], + "@clerk/backend": ["@clerk/backend@2.29.7", "", { "dependencies": { "@clerk/shared": "^3.44.0", "@clerk/types": "^4.101.14", "standardwebhooks": "^1.0.0", "tslib": "2.8.1" } }, "sha512-OSfFQ85L0FV2wSzqlr0hRvluIu3Z5ClgLiBE6Qx7XjSGyJoqEvP5OP4fl5Nt5icgGvH0EwA1dljPGyQpaqbQEw=="], "@clerk/clerk-react": ["@clerk/clerk-react@5.60.0", "", { "dependencies": { "@clerk/shared": "^3.44.0", "tslib": "2.8.1" }, "peerDependencies": { "react": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0", "react-dom": "^18.0.0 || ~19.0.3 || ~19.1.4 || ~19.2.3 || ~19.3.0-0" } }, "sha512-P88FncsJpq/3WZJhhlj+md8mYb35BIXpr462C/figwsBGHsinr8VuBQUMcMZZ/6M34C8ABfLTPa6PHVp6+3D5Q=="], @@ -577,6 +594,10 @@ "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + "@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="], + + "@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="], + "@ioredis/commands": ["@ioredis/commands@1.5.0", "", {}, "sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow=="], "@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="], @@ -625,6 +646,8 @@ "@lukeed/csprng": ["@lukeed/csprng@1.1.0", "", {}, "sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA=="], + "@mermaid-js/parser": ["@mermaid-js/parser@0.6.3", "", { "dependencies": { "langium": "3.3.1" } }, "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA=="], + "@microsoft/tsdoc": ["@microsoft/tsdoc@0.16.0", "", {}, "sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA=="], "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.25.3", "", { "dependencies": { "@hono/node-server": "^1.19.9", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.5", "eventsource": "^3.0.2", "eventsource-parser": "^3.0.0", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "jose": "^6.1.1", "json-schema-typed": "^8.0.2", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.25 || ^4.0", "zod-to-json-schema": "^3.25.0" }, "peerDependencies": { "@cfworker/json-schema": "^4.1.1" }, "optionalPeers": ["@cfworker/json-schema"] }, "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ=="], @@ -1203,6 +1226,8 @@ "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + "@types/react-syntax-highlighter": ["@types/react-syntax-highlighter@15.5.13", "", { "dependencies": { "@types/react": "*" } }, "sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA=="], + "@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="], "@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="], @@ -1457,6 +1482,10 @@ "charm": ["charm@0.1.2", "", {}, "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ=="], + "chevrotain": ["chevrotain@11.0.3", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.0.3", "@chevrotain/gast": "11.0.3", "@chevrotain/regexp-to-ast": "11.0.3", "@chevrotain/types": "11.0.3", "@chevrotain/utils": "11.0.3", "lodash-es": "4.17.21" } }, "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw=="], + + "chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="], + "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="], @@ -1499,6 +1528,8 @@ "concat-stream": ["concat-stream@2.0.0", "", { "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.0.2", "typedarray": "^0.0.6" } }, "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A=="], + "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], + "consola": ["consola@2.15.3", "", {}, "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw=="], "content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="], @@ -1517,6 +1548,8 @@ "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], + "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], + "croner": ["croner@4.1.97", "", {}, "sha512-/f6gpQuxDaqXu+1kwQYSckUglPaOrHdbIlBAu0YuW8/Cdb45XwXYNUBXg3r/9Mo6n540Kn/smKcZWko5x99KrQ=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -1535,24 +1568,78 @@ "culvert": ["culvert@0.1.2", "", {}, "sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg=="], + "cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="], + + "cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="], + + "cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="], + + "d3": ["d3@7.9.0", "", { "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" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="], + + "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], + + "d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="], + + "d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="], + + "d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="], + "d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="], + "d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="], + + "d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="], + "d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="], "d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="], + "d3-dsv": ["d3-dsv@3.0.1", "", { "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" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="], + "d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="], + "d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="], + + "d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="], + + "d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="], + + "d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="], + + "d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="], + "d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="], + "d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="], + + "d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="], + + "d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="], + + "d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="], + + "d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="], + + "d3-scale": ["d3-scale@4.0.2", "", { "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" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="], + + "d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="], + "d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="], + "d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="], + + "d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="], + + "d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="], + "d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="], "d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="], "d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="], + "dagre-d3-es": ["dagre-d3-es@7.0.13", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q=="], + "data-uri-to-buffer": ["data-uri-to-buffer@4.0.1", "", {}, "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A=="], "data-urls": ["data-urls@6.0.1", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^15.1.0" } }, "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ=="], @@ -1591,6 +1678,8 @@ "degenerator": ["degenerator@5.0.1", "", { "dependencies": { "ast-types": "^0.13.4", "escodegen": "^2.1.0", "esprima": "^4.0.1" } }, "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ=="], + "delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="], + "delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="], "denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="], @@ -1617,7 +1706,7 @@ "dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="], - "dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], + "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], @@ -1759,6 +1848,8 @@ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + "fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="], + "fclone": ["fclone@1.0.11", "", {}, "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw=="], "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], @@ -1791,6 +1882,8 @@ "form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="], + "format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="], + "formdata-polyfill": ["formdata-polyfill@4.0.10", "", { "dependencies": { "fetch-blob": "^3.1.2" } }, "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g=="], "formidable": ["formidable@3.5.4", "", { "dependencies": { "@paralleldrive/cuid2": "^2.2.2", "dezalgo": "^1.0.4", "once": "^1.4.0" } }, "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug=="], @@ -1869,6 +1962,8 @@ "gtoken": ["gtoken@8.0.0", "", { "dependencies": { "gaxios": "^7.0.0", "jws": "^4.0.0" } }, "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw=="], + "hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="], + "hard-rejection": ["hard-rejection@2.1.0", "", {}, "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA=="], "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], @@ -1895,7 +1990,7 @@ "hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="], - "hast-util-parse-selector": ["hast-util-parse-selector@3.1.1", "", { "dependencies": { "@types/hast": "^2.0.0" } }, "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA=="], + "hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], "hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="], @@ -1909,10 +2004,14 @@ "hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="], - "hastscript": ["hastscript@7.2.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^3.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw=="], + "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], "heap-js": ["heap-js@2.7.1", "", {}, "sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA=="], + "highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="], + + "highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="], + "hono": ["hono@4.11.7", "", {}, "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw=="], "hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="], @@ -1955,6 +2054,8 @@ "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + "internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="], + "ioredis": ["ioredis@5.9.2", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-tAAg/72/VxOUW7RQSX1pIxJVucYKcjFjfvj60L57jrZpYCHC3XN0WCQ3sNYL4Gmvv+7GPvTAjc+KSdeNuE8oWQ=="], "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], @@ -2089,10 +2190,18 @@ "kafkajs": ["kafkajs@2.2.4", "", {}, "sha512-j/YeapB1vfPT2iOIUn/vxdyKEuhuY2PxMBvf5JWux6iSaukAccrMtXEY/Lb7OvavDhOWME589bpLrEdnVHjfjA=="], + "katex": ["katex@0.16.28", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg=="], + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + "khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="], + "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], + "langium": ["langium@3.3.1", "", { "dependencies": { "chevrotain": "~11.0.3", "chevrotain-allstar": "~0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.0.8" } }, "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w=="], + + "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], "libphonenumber-js": ["libphonenumber-js@1.12.36", "", {}, "sha512-woWhKMAVx1fzzUnMCyOzglgSgf6/AFHLASdOBcchYCyvWSGWt12imw3iu2hdI5d4dGZRsNWAmWiz37sDKUPaRQ=="], @@ -2113,6 +2222,8 @@ "lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="], + "lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="], + "lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="], "lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="], @@ -2129,6 +2240,8 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="], + "lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="], "lucide-react": ["lucide-react@0.544.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-t5tS44bqd825zAW45UQxpG2CvcC4urOwn2TrwSH8u+MjeE+1NnWl6QqeQ/6NdjMqdOygyiT9p3Ev0p1NJykxjw=="], @@ -2147,7 +2260,7 @@ "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - "marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], + "marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -2197,6 +2310,8 @@ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + "mermaid": ["mermaid@11.12.2", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^0.6.3", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "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.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.21", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w=="], + "methods": ["methods@1.1.2", "", {}, "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w=="], "micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="], @@ -2283,6 +2398,8 @@ "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "mlly": ["mlly@1.8.0", "", { "dependencies": { "acorn": "^8.15.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.1" } }, "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g=="], + "module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="], "monaco-editor": ["monaco-editor@0.55.1", "", { "dependencies": { "dompurify": "3.2.7", "marked": "14.0.0" } }, "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A=="], @@ -2377,6 +2494,8 @@ "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="], + "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], + "pako": ["pako@2.1.0", "", {}, "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="], "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], @@ -2393,6 +2512,8 @@ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="], + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], @@ -2403,6 +2524,8 @@ "path-to-regexp": ["path-to-regexp@3.3.0", "", {}, "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="], + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "pg": ["pg@8.18.0", "", { "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", "pgpass": "1.0.5" }, "optionalDependencies": { "pg-cloudflare": "^1.3.0" }, "peerDependencies": { "pg-native": ">=3.0.1" }, "optionalPeers": ["pg-native"] }, "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ=="], "pg-cloudflare": ["pg-cloudflare@1.3.0", "", {}, "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ=="], @@ -2433,6 +2556,8 @@ "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + "pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="], + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], "pm2": ["pm2@6.0.14", "", { "dependencies": { "@pm2/agent": "~2.1.1", "@pm2/blessed": "0.1.81", "@pm2/io": "~6.1.0", "@pm2/js-api": "~0.8.0", "@pm2/pm2-version-check": "^1.0.4", "ansis": "4.0.0-node10", "async": "3.2.6", "chokidar": "3.6.0", "cli-tableau": "2.0.1", "commander": "2.15.1", "croner": "4.1.97", "dayjs": "1.11.15", "debug": "4.4.3", "enquirer": "2.3.6", "eventemitter2": "5.0.1", "fclone": "1.0.11", "js-yaml": "4.1.1", "mkdirp": "1.0.4", "needle": "2.4.0", "pidusage": "3.0.2", "pm2-axon": "~4.0.1", "pm2-axon-rpc": "~0.7.1", "pm2-deploy": "~1.0.2", "pm2-multimeter": "^0.1.2", "promptly": "2.2.0", "semver": "7.7.2", "source-map-support": "0.5.21", "sprintf-js": "1.1.2", "vizion": "~2.2.1" }, "optionalDependencies": { "pm2-sysmonit": "^1.2.8" }, "bin": { "pm2": "bin/pm2", "pm2-dev": "bin/pm2-dev", "pm2-docker": "bin/pm2-docker", "pm2-runtime": "bin/pm2-runtime" } }, "sha512-wX1FiFkzuT2H/UUEA8QNXDAA9MMHDsK/3UHj6Dkd5U7kxyigKDA5gyDw78ycTQZAuGCLWyUX5FiXEuVQWafukA=="], @@ -2447,6 +2572,10 @@ "pm2-sysmonit": ["pm2-sysmonit@1.2.8", "", { "dependencies": { "async": "^3.2.0", "debug": "^4.3.1", "pidusage": "^2.0.21", "systeminformation": "^5.7", "tx2": "~1.0.4" } }, "sha512-ACOhlONEXdCTVwKieBIQLSi2tQZ8eKinhcr9JpZSUAL8Qy0ajIgRtsLxG/lwPOW3JEKqPyw/UaHmTWhUzpP4kA=="], + "points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="], + + "points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="], + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], @@ -2485,6 +2614,8 @@ "pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="], + "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], + "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], "promptly": ["promptly@2.2.0", "", { "dependencies": { "read": "^1.0.4" } }, "sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA=="], @@ -2547,6 +2678,8 @@ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], + "react-syntax-highlighter": ["react-syntax-highlighter@16.1.0", "", { "dependencies": { "@babel/runtime": "^7.28.4", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^5.0.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg=="], + "reactflow": ["reactflow@11.11.4", "", { "dependencies": { "@reactflow/background": "11.3.14", "@reactflow/controls": "11.2.14", "@reactflow/core": "11.11.4", "@reactflow/minimap": "11.7.14", "@reactflow/node-resizer": "2.2.14", "@reactflow/node-toolbar": "1.3.14" }, "peerDependencies": { "react": ">=17", "react-dom": ">=17" } }, "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og=="], "read": ["read@1.0.7", "", { "dependencies": { "mute-stream": "~0.0.4" } }, "sha512-rSOKNYUmaxy0om1BNjMN4ezNT6VKK+2xF4GBhc81mkH7L60i6dp8qPYrkndNLT3QPphoII3maL9PVC9XmhHwVQ=="], @@ -2571,7 +2704,7 @@ "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], - "refractor": ["refractor@4.9.0", "", { "dependencies": { "@types/hast": "^2.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^7.0.0", "parse-entities": "^4.0.0" } }, "sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og=="], + "refractor": ["refractor@5.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^9.0.0", "parse-entities": "^4.0.0" } }, "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw=="], "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], @@ -2623,14 +2756,20 @@ "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": { "rimraf": "dist/esm/bin.mjs" } }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="], + "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], + "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + "roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "run-series": ["run-series@1.1.9", "", {}, "sha512-Arc4hUN896vjkqCYrUXquBFtRZdv1PfLbTYP71efP6butxyQ0kWpiNJyAgsxscmQg1cqvHY32/UCBzXedTpU2g=="], + "rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="], + "rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="], "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], @@ -2775,6 +2914,8 @@ "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], + "stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="], + "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="], "superagent": ["superagent@10.3.0", "", { "dependencies": { "component-emitter": "^1.3.1", "cookiejar": "^2.1.4", "debug": "^4.3.7", "fast-safe-stringify": "^2.1.1", "form-data": "^4.0.5", "formidable": "^3.5.4", "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.14.1" } }, "sha512-B+4Ik7ROgVKrQsXTV0Jwp2u+PXYLSlqtDAhYnkkD+zn3yg8s/zjA2MeGayPoY/KICrbitwneDHrjSotxKL+0XQ=="], @@ -2821,6 +2962,8 @@ "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "tinygradient": ["tinygradient@1.1.5", "", { "dependencies": { "@types/tinycolor2": "^1.4.0", "tinycolor2": "^1.0.0" } }, "sha512-8nIfc2vgQ4TeLnk2lFj4tRLvvJwEfQuabdsmvDdQPT0xlk9TaNtpGd6nNRxXoK6vQhN6RSzj+Cnp5tTQmpxmbw=="], @@ -2849,6 +2992,8 @@ "ts-api-utils": ["ts-api-utils@2.4.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA=="], + "ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="], + "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="], "ts-loader": ["ts-loader@9.5.4", "", { "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", "semver": "^7.3.4", "source-map": "^0.7.4" }, "peerDependencies": { "typescript": "*", "webpack": "^5.0.0" } }, "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ=="], @@ -2883,6 +3028,8 @@ "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + "uid": ["uid@2.0.2", "", { "dependencies": { "@lukeed/csprng": "^1.0.0" } }, "sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g=="], "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], @@ -2949,6 +3096,18 @@ "vizion": ["vizion@2.2.1", "", { "dependencies": { "async": "^2.6.3", "git-node-fs": "^1.0.0", "ini": "^1.3.5", "js-git": "^0.7.8" } }, "sha512-sfAcO2yeSU0CSPFI/DmZp3FsFE9T+8913nv1xWBOyzODv13fwkn6Vl7HqxGpkr9F608M+8SuFId3s+BlZqfXww=="], + "vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="], + + "vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="], + + "vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="], + + "vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="], + + "vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="], + + "vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], + "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], "watchpack": ["watchpack@2.5.1", "", { "dependencies": { "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.1.2" } }, "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg=="], @@ -3049,6 +3208,10 @@ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + + "@chevrotain/gast/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "@clerk/shared/csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "@clerk/shared/swr": ["swr@2.3.4", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-bYd2lrhc+VarcpkgWclcUi92wYCpOgMws9Sd1hG1ntAu0NEy+14CbotuFjshBU2kt9rYj9TSmDcybpxpeTU1fg=="], @@ -3219,6 +3382,8 @@ "chalk-animation/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], + "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "cli-tableau/chalk": ["chalk@3.0.0", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg=="], @@ -3229,6 +3394,16 @@ "cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], + + "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], + + "d3-dsv/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="], + + "d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="], + "data-urls/whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="], "debug/ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -3267,16 +3442,8 @@ "hast-util-from-html/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "hast-util-from-parse5/hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], - - "hast-util-parse-selector/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], - "hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], - "hastscript/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], - - "hastscript/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], - "hosted-git-info/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], "jest-worker/@types/node": ["@types/node@25.2.0", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w=="], @@ -3285,6 +3452,8 @@ "js-git/pako": ["pako@0.2.9", "", {}, "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], + "markdown-it-html5-embed/markdown-it": ["markdown-it@8.4.2", "", { "dependencies": { "argparse": "^1.0.7", "entities": "~1.1.1", "linkify-it": "^2.0.0", "mdurl": "^1.0.1", "uc.micro": "^1.0.5" }, "bin": { "markdown-it": "bin/markdown-it.js" } }, "sha512-GcRz3AWTqSUphY3vsUqQSFMbgR38a4Lh3GWlHRh/7MRwz8mcu9n2IO7HOh+bXHrR9kOPDl5RNCaEsrneb+xhHQ=="], "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], @@ -3295,12 +3464,18 @@ "meow/yargs-parser": ["yargs-parser@20.2.9", "", {}, "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w=="], + "mermaid/dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="], + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "minio/lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], "minio/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="], + "monaco-editor/dompurify": ["dompurify@3.2.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw=="], + + "monaco-editor/marked": ["marked@14.0.0", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ=="], + "multer/mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="], "multer/type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], @@ -3335,8 +3510,6 @@ "postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="], - "posthog-js/dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], - "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], "pretty-format/react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="], @@ -3357,10 +3530,10 @@ "readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], - "refractor/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], - "rehype-attr/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + "rehype-prism-plus/refractor": ["refractor@4.9.0", "", { "dependencies": { "@types/hast": "^2.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^7.0.0", "parse-entities": "^4.0.0" } }, "sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og=="], + "require-in-the-middle/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], "restore-cursor/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], @@ -3559,6 +3732,10 @@ "cliui/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + "cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="], + + "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], + "drizzle-kit/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], "drizzle-kit/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], @@ -3623,14 +3800,8 @@ "hast-util-from-html/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "hast-util-from-parse5/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="], - - "hast-util-parse-selector/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - "hast-util-raw/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], - "hastscript/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], - "markdown-it-html5-embed/markdown-it/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "markdown-it-html5-embed/markdown-it/entities": ["entities@1.1.2", "", {}, "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="], @@ -3657,7 +3828,9 @@ "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], - "refractor/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "rehype-prism-plus/refractor/@types/hast": ["@types/hast@2.3.10", "", { "dependencies": { "@types/unist": "^2" } }, "sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw=="], + + "rehype-prism-plus/refractor/hastscript": ["hastscript@7.2.0", "", { "dependencies": { "@types/hast": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^3.0.0", "property-information": "^6.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw=="], "ts-loader/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -3703,6 +3876,12 @@ "multer/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], + "rehype-prism-plus/refractor/@types/hast/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + + "rehype-prism-plus/refractor/hastscript/hast-util-parse-selector": ["hast-util-parse-selector@3.1.1", "", { "dependencies": { "@types/hast": "^2.0.0" } }, "sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA=="], + + "rehype-prism-plus/refractor/hastscript/property-information": ["property-information@6.5.0", "", {}, "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig=="], + "@nestjs/platform-express/express/accepts/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], "@nestjs/platform-express/express/type-is/mime-types/mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="], diff --git a/frontend/package.json b/frontend/package.json index 8e524811..4542fdb0 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -67,6 +67,7 @@ "@radix-ui/react-tooltip": "^1.2.8", "@shipsec/backend-client": "workspace:*", "@shipsec/shared": "workspace:*", + "@types/react-syntax-highlighter": "^15.5.13", "@uiw/react-markdown-preview": "^5.1.5", "ai": "^5.0.76", "ansi_up": "^6.0.6", @@ -78,12 +79,14 @@ "markdown-it-html5-embed": "^1.0.0", "markdown-it-link-attributes": "^4.0.1", "markdown-it-task-lists": "^2.1.1", + "mermaid": "^11.12.2", "monaco-editor": "^0.55.1", "postcss": "^8.5.6", "posthog-js": "^1.288.0", "react": "^19.2.0", "react-dom": "^19.2.0", "react-router-dom": "^7.9.3", + "react-syntax-highlighter": "^16.1.0", "reactflow": "^11.11.4", "tailwind-merge": "^3.3.1", "xterm": "^5.3.0", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 26139b94..909b736b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,6 +12,7 @@ import { WebhookEditorPage } from '@/pages/WebhookEditorPage'; import { SchedulesPage } from '@/pages/SchedulesPage'; import { ActionCenterPage } from '@/pages/ActionCenterPage'; import { RunRedirect } from '@/pages/RunRedirect'; +import { AgentPage } from '@/pages/AgentPage'; import { ToastProvider } from '@/components/ui/toast-provider'; import { AppLayout } from '@/components/layout/AppLayout'; import { AuthProvider } from '@/auth/auth-context'; @@ -46,54 +47,42 @@ function App() { {/* Analytics wiring */} - - - - } /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } - /> - } /> - - - + + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } + /> + } /> + + + + } + /> + diff --git a/frontend/src/components/agent/StatusBar.tsx b/frontend/src/components/agent/StatusBar.tsx new file mode 100644 index 00000000..7a67094b --- /dev/null +++ b/frontend/src/components/agent/StatusBar.tsx @@ -0,0 +1,600 @@ +import React, { useState, useRef, useCallback } from 'react'; +import { + Cloud, + Shield, + Terminal, + AlertTriangle, + Activity, + CheckCircle2, + Clock, + Server, + Globe, + Blocks, + Eye, + Radio, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'; + +// ─── Mock Data ────────────────────────────────────────────────────────────── + +const awsAccounts = [ + { + name: 'Production', + id: '●●●●-4821', + region: 'us-east-1', + resources: 142, + status: 'healthy' as const, + }, + { + name: 'Staging', + id: '●●●●-7293', + region: 'us-west-2', + resources: 87, + status: 'healthy' as const, + }, + { + name: 'Development', + id: '●●●●-1056', + region: 'eu-west-1', + resources: 56, + status: 'warning' as const, + }, +]; + +const githubOrgs = [ + { + name: 'shipsec-platform', + repos: 18, + lastSync: '2m ago', + avatar: 'SP', + topRepos: [ + { name: 'studio-github', lang: 'TypeScript', lastScan: '2m ago' }, + { name: 'api-gateway', lang: 'Go', lastScan: '5m ago' }, + { name: 'mcp-servers', lang: 'Python', lastScan: '8m ago' }, + { name: 'scanner-engine', lang: 'Rust', lastScan: '12m ago' }, + ], + }, + { + name: 'shipsec-internal', + repos: 14, + lastSync: '5m ago', + avatar: 'SI', + topRepos: [ + { name: 'web-app', lang: 'TypeScript', lastScan: '5m ago' }, + { name: 'backend-api', lang: 'Python', lastScan: '10m ago' }, + { name: 'infrastructure', lang: 'HCL', lastScan: '15m ago' }, + ], + }, +]; + +const scanners = [ + { name: 'Semgrep', type: 'SAST', status: 'active' as const, lastRun: '12m ago' }, + { name: 'Trivy', type: 'Container', status: 'active' as const, lastRun: '15m ago' }, + { name: 'TruffleHog', type: 'Secrets', status: 'active' as const, lastRun: '3m ago' }, + { name: 'Checkov', type: 'IaC', status: 'active' as const, lastRun: '22m ago' }, + { name: 'SonarQube', type: 'Quality', status: 'idle' as const, lastRun: '1h ago' }, +]; + +const mcpTools = [ + { category: 'Security Analysis', count: 12, icon: Shield }, + { category: 'Cloud Operations', count: 8, icon: Cloud }, + { category: 'Code Intelligence', count: 10, icon: Terminal }, + { category: 'Compliance', count: 6, icon: CheckCircle2 }, + { category: 'Threat Intel', count: 6, icon: Globe }, +]; + +const findingsSummary = { + critical: 12, + high: 45, + medium: 89, + low: 101, +}; + +const recentActivity = [ + { event: 'AWS Security Hub synced', time: '2m ago', type: 'sync' as const }, + { event: 'GitHub webhook: push to main', time: '4m ago', type: 'webhook' as const }, + { event: 'Semgrep scan completed', time: '12m ago', type: 'scan' as const }, + { event: 'New critical finding detected', time: '18m ago', type: 'alert' as const }, + { event: 'Snyk dependency check done', time: '24m ago', type: 'scan' as const }, +]; + +// ─── Hover Popover Wrapper ────────────────────────────────────────────────── + +function HoverPopover({ + children, + content, + align = 'center', + className, +}: { + children: React.ReactNode; + content: React.ReactNode; + align?: 'start' | 'center' | 'end'; + className?: string; +}) { + const [open, setOpen] = useState(false); + const timeoutRef = useRef>(undefined); + + const handleMouseEnter = useCallback(() => { + clearTimeout(timeoutRef.current); + setOpen(true); + }, []); + + const handleMouseLeave = useCallback(() => { + timeoutRef.current = setTimeout(() => setOpen(false), 150); + }, []); + + return ( + + +
+ {children} +
+
+ + {content} + +
+ ); +} + +// ─── Individual Status Pill ───────────────────────────────────────────────── + +function StatusPill({ + icon: Icon, + label, + value, + dotColor = 'green', + popoverContent, + popoverAlign, + className, +}: { + icon: React.ElementType; + label: string; + value: string | number; + dotColor?: 'green' | 'amber' | 'red' | 'blue'; + popoverContent: React.ReactNode; + popoverAlign?: 'start' | 'center' | 'end'; + className?: string; +}) { + const dotColors = { + green: 'bg-emerald-500', + amber: 'bg-amber-500', + red: 'bg-red-500', + blue: 'bg-blue-500', + }; + + const dotGlowColors = { + green: 'shadow-[0_0_6px_rgba(16,185,129,0.4)]', + amber: 'shadow-[0_0_6px_rgba(245,158,11,0.4)]', + red: 'shadow-[0_0_6px_rgba(239,68,68,0.4)]', + blue: 'shadow-[0_0_6px_rgba(59,130,246,0.4)]', + }; + + return ( + + + + ); +} + +// ─── GitHub Icon ──────────────────────────────────────────────────────────── + +function GitHubIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +// ─── Popover Content Panels ───────────────────────────────────────────────── + +function AwsPopoverContent() { + return ( +
+
+

AWS Accounts

+ + All Healthy + +
+
+ {awsAccounts.map((account) => ( +
+
+
+
+

{account.name}

+

+ {account.id} · {account.region} +

+
+
+ {account.resources} resources +
+ ))} +
+
+

+ + Security Hub last synced 2m ago +

+
+
+ ); +} + +function GitHubPopoverContent() { + const totalRepos = githubOrgs.reduce((sum, org) => sum + org.repos, 0); + return ( +
+
+

GitHub

+ + {totalRepos} Repos + +
+
+ {githubOrgs.map((org) => { + const remaining = org.repos - org.topRepos.length; + return ( +
+ {/* Org header */} +
+
+
+ {org.avatar} +
+ {org.name} + · {org.repos} +
+ {org.lastSync} +
+ {/* Repo list */} +
+ {org.topRepos.map((repo) => ( +
+ {repo.name} + {repo.lang} +
+ ))} + {remaining > 0 && ( +

+ +{remaining} more repositories +

+ )} +
+
+ ); + })} +
+
+

+ + Monitoring all branches · Webhooks active +

+
+
+ ); +} + +function ScannersPopoverContent() { + const activeCount = scanners.filter((s) => s.status === 'active').length; + return ( +
+
+

Security Scanners

+ + {activeCount} Active + +
+
+ {scanners.map((scanner) => ( +
+
+
+

{scanner.name}

+

{scanner.type}

+
+
+ ))} +
+
+

+ + All scanners reporting · Next scheduled in 8m +

+
+
+ ); +} + +function McpToolsPopoverContent() { + const total = mcpTools.reduce((sum, cat) => sum + cat.count, 0); + return ( +
+
+

MCP Tool Servers

+ + {total} Tools + +
+
+ {mcpTools.map((cat) => { + const CatIcon = cat.icon; + return ( +
+
+ + {cat.category} +
+ {cat.count} +
+ ); + })} +
+
+

+ 3 MCP servers connected · All healthy +

+
+
+ ); +} + +function FindingsPopoverContent() { + const total = + findingsSummary.critical + findingsSummary.high + findingsSummary.medium + findingsSummary.low; + + const severities = [ + { + label: 'Critical', + count: findingsSummary.critical, + color: 'bg-red-500', + textColor: 'text-red-600 dark:text-red-400', + barColor: 'bg-red-500', + }, + { + label: 'High', + count: findingsSummary.high, + color: 'bg-orange-500', + textColor: 'text-orange-600 dark:text-orange-400', + barColor: 'bg-orange-500', + }, + { + label: 'Medium', + count: findingsSummary.medium, + color: 'bg-amber-500', + textColor: 'text-amber-600 dark:text-amber-400', + barColor: 'bg-amber-500', + }, + { + label: 'Low', + count: findingsSummary.low, + color: 'bg-blue-400', + textColor: 'text-blue-600 dark:text-blue-400', + barColor: 'bg-blue-400', + }, + ]; + + return ( +
+
+

Open Findings

+ + {total} Total + +
+ + {/* Severity bar visualization */} +
+ {severities.map((sev) => ( +
+ ))} +
+ +
+ {severities.map((sev) => ( +
+
+
+ {sev.label} +
+ {sev.count} +
+ ))} +
+ +
+

+ + {findingsSummary.critical} critical require immediate attention +

+
+
+ ); +} + +function LivePopoverContent() { + return ( +
+
+

Live Activity

+ + Monitoring + +
+
+ {recentActivity.map((item, i) => ( +
+
+
+

{item.event}

+

{item.time}

+
+
+ ))} +
+
+

+ + Real-time webhooks · 15s polling interval +

+
+
+ ); +} + +// ─── Main StatusBar Component ─────────────────────────────────────────────── + +interface StatusBarProps { + className?: string; +} + +export function StatusBar({ className }: StatusBarProps) { + const totalFindings = + findingsSummary.critical + findingsSummary.high + findingsSummary.medium + findingsSummary.low; + const totalRepos = githubOrgs.reduce((sum, org) => sum + org.repos, 0); + const totalMcpTools = mcpTools.reduce((sum, cat) => sum + cat.count, 0); + const activeScanners = scanners.filter((s) => s.status === 'active').length; + + return ( +
+
+ {/* AWS */} + } + popoverAlign="start" + /> + + {/* Separator */} +
+ + {/* GitHub */} + } + /> + + {/* Separator */} +
+ + {/* Scanners */} + } + /> + + {/* Separator */} +
+ + {/* MCP Tools */} + } + /> + + {/* Separator */} +
+ + {/* Findings */} + } + className="text-amber-600 dark:text-amber-400" + /> + + {/* Spacer */} +
+ + {/* Live indicator (right side) */} + } align="end"> + + +
+
+ ); +} diff --git a/frontend/src/components/layout/AgentLayout.tsx b/frontend/src/components/layout/AgentLayout.tsx new file mode 100644 index 00000000..9bbdecc2 --- /dev/null +++ b/frontend/src/components/layout/AgentLayout.tsx @@ -0,0 +1,400 @@ +import React, { useState, useEffect, useCallback } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { Sidebar, SidebarHeader, SidebarContent, SidebarFooter } from '@/components/ui/sidebar'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { + Bot, + Plus, + Search, + MessageSquare, + Trash2, + Sun, + Moon, + Menu, + X, + Workflow, + CalendarClock, + Webhook, + Zap, + KeyRound, + Shield, + Archive, + ChevronRight, + Pencil, + Check, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { useThemeStore } from '@/store/themeStore'; +import { useChatStore } from '@/store/chatStore'; +import { ThemeTransition } from '@/components/ui/ThemeTransition'; + +interface AgentLayoutProps { + children: React.ReactNode; +} + +function useIsMobile(breakpoint = 768) { + const [isMobile, setIsMobile] = useState( + typeof window !== 'undefined' ? window.innerWidth < breakpoint : false, + ); + + useEffect(() => { + const handleResize = () => { + setIsMobile(window.innerWidth < breakpoint); + }; + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, [breakpoint]); + + return isMobile; +} + +const studioNavItems = [ + { name: 'Workflow Builder', href: '/workflows', icon: Workflow }, + { name: 'Schedules', href: '/schedules', icon: CalendarClock }, + { name: 'Webhooks', href: '/webhooks', icon: Webhook }, + { name: 'Action Center', href: '/action-center', icon: Zap }, + { name: 'Secrets', href: '/secrets', icon: KeyRound }, + { name: 'API Keys', href: '/api-keys', icon: Shield }, + { name: 'Artifact Library', href: '/artifacts', icon: Archive }, +]; + +export function AgentLayout({ children }: AgentLayoutProps) { + const isMobile = useIsMobile(); + const navigate = useNavigate(); + const [sidebarOpen, setSidebarOpen] = useState(!isMobile); + const [searchQuery, setSearchQuery] = useState(''); + const [studioExpanded, setStudioExpanded] = useState(false); + const { theme, startTransition } = useThemeStore(); + + const { + conversations, + activeConversationId, + setActiveConversation, + deleteConversation, + renameConversation, + } = useChatStore(); + + // Rename editing state + const [editingConversationId, setEditingConversationId] = useState(null); + const [editTitle, setEditTitle] = useState(''); + + // Update sidebar state based on mobile + useEffect(() => { + setSidebarOpen(!isMobile); + }, [isMobile]); + + const handleNewChat = useCallback(() => { + setActiveConversation(null); + navigate('/'); + if (isMobile) { + setSidebarOpen(false); + } + }, [setActiveConversation, navigate, isMobile]); + + const handleSelectConversation = useCallback( + (id: string) => { + navigate(`/c/${id}`); + if (isMobile) { + setSidebarOpen(false); + } + }, + [navigate, isMobile], + ); + + const handleDeleteConversation = useCallback( + (e: React.MouseEvent, id: string) => { + e.stopPropagation(); + const wasActive = activeConversationId === id; + deleteConversation(id); + if (wasActive) { + navigate('/'); + } + }, + [deleteConversation, activeConversationId, navigate], + ); + + const handleStartRename = useCallback((e: React.MouseEvent, id: string, currentTitle: string) => { + e.stopPropagation(); + setEditingConversationId(id); + setEditTitle(currentTitle); + }, []); + + const handleSaveRename = useCallback( + (e: React.MouseEvent | React.KeyboardEvent, id: string) => { + e.stopPropagation(); + if (editTitle.trim()) { + renameConversation(id, editTitle.trim()); + } + setEditingConversationId(null); + setEditTitle(''); + }, + [editTitle, renameConversation], + ); + + const handleCancelRename = useCallback(() => { + setEditingConversationId(null); + setEditTitle(''); + }, []); + + const filteredConversations = conversations.filter((conv) => + conv.title.toLowerCase().includes(searchQuery.toLowerCase()), + ); + + return ( + <> + +
+ {/* Mobile backdrop */} + {isMobile && sidebarOpen && ( +
setSidebarOpen(false)} + /> + )} + + {/* Sidebar */} + + {/* Header with logo and agent info */} + +
+ +
+ ShipSec { + e.currentTarget.style.display = 'none'; + }} + /> +
+
+
+ ShipSec AI + {/* + + Powered by Claude Opus + */} +
+ + {isMobile && ( + + )} +
+ + {/* New chat button */} + + + + + {/* Search bar */} +
+ + setSearchQuery(e.target.value)} + className="pl-9 h-9 text-sm" + /> +
+ + {/* Studio Navigation - Collapsible */} +
+ + {studioExpanded && ( +
+ {studioNavItems.map((item) => { + const Icon = item.icon; + return ( + isMobile && setSidebarOpen(false)} + > + + {item.name} + + ); + })} +
+ )} +
+ + {/* Conversation history */} +
+

Your Chats

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

No conversations yet

+

Start a new chat to begin

+
+ ) : ( + filteredConversations.map((conv) => ( +
+ editingConversationId !== conv.id && handleSelectConversation(conv.id) + } + className={cn( + 'w-full flex items-center gap-3 px-3 py-2.5 rounded-lg text-left', + 'transition-colors group cursor-pointer', + activeConversationId === conv.id + ? 'bg-accent text-accent-foreground' + : 'hover:bg-muted text-muted-foreground hover:text-foreground', + )} + > + +
+ {editingConversationId === conv.id ? ( + setEditTitle(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') handleSaveRename(e, conv.id); + if (e.key === 'Escape') handleCancelRename(); + }} + onClick={(e) => e.stopPropagation()} + autoFocus + className={cn( + 'w-full text-sm font-medium bg-background border border-input rounded px-2 py-0.5', + 'focus:outline-none focus:ring-1 focus:ring-ring', + )} + /> + ) : ( + <> +

{conv.title}

+

+ {conv.messages.length} messages +

+ + )} +
+
+ {editingConversationId === conv.id ? ( + + ) : ( + + )} + +
+
+ )) + )} +
+
+ + +
+
+ User avatar +
+ +
+
+ version: v0.2 (e73fd1) +
+
+ + + {/* Main content */} +
+ {/* Mobile header */} + {isMobile && ( +
+ +
+ ShipSec { + e.currentTarget.style.display = 'none'; + }} + /> + ShipSec AI +
+
+ )} +
{children}
+
+
+ + ); +} diff --git a/frontend/src/components/layout/AppLayout.tsx b/frontend/src/components/layout/AppLayout.tsx index bd0bf44f..898cd4fd 100644 --- a/frontend/src/components/layout/AppLayout.tsx +++ b/frontend/src/components/layout/AppLayout.tsx @@ -9,6 +9,8 @@ import { } from '@/components/ui/sidebar'; import { AppTopBar } from '@/components/layout/AppTopBar'; import { Button } from '@/components/ui/button'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { useAuth } from '@/auth/useAuth'; import { Workflow, KeyRound, @@ -23,12 +25,16 @@ import { Command, Zap, Webhook, + Bot, + MessageSquare, + Trash2, + Pencil, + Check, } from 'lucide-react'; import React, { useState, useEffect, useCallback } from 'react'; import { useAuthStore } from '@/store/authStore'; import { hasAdminRole } from '@/utils/auth'; -import { UserButton } from '@/components/auth/UserButton'; -import { useAuth, useAuthProvider } from '@/auth/auth-context'; +import { useChatStore } from '@/store/chatStore'; import { env } from '@/config/env'; import { useThemeStore } from '@/store/themeStore'; import { cn } from '@/lib/utils'; @@ -68,11 +74,69 @@ export function AppLayout({ children }: AppLayoutProps) { const navigate = useNavigate(); const roles = useAuthStore((state) => state.roles); const canManageWorkflows = hasAdminRole(roles); - const { isAuthenticated } = useAuth(); - const authProvider = useAuthProvider(); - const showUserButton = isAuthenticated || authProvider.name === 'clerk'; const { theme, startTransition } = useThemeStore(); const openCommandPalette = useCommandPaletteStore((state) => state.open); + const { user } = useAuth(); + + // Chat conversation management + const { + conversations, + activeConversationId, + setActiveConversation, + deleteConversation, + renameConversation, + } = useChatStore(); + const [editingConversationId, setEditingConversationId] = useState(null); + const [editTitle, setEditTitle] = useState(''); + + const handleNewChat = useCallback(() => { + setActiveConversation(null); + navigate('/'); + if (isMobile) setSidebarOpen(false); + }, [setActiveConversation, navigate, isMobile]); + + const handleSelectConversation = useCallback( + (id: string) => { + navigate(`/c/${id}`); + if (isMobile) setSidebarOpen(false); + }, + [navigate, isMobile], + ); + + const handleDeleteConversation = useCallback( + (e: React.MouseEvent, id: string) => { + e.stopPropagation(); + const wasActive = activeConversationId === id; + deleteConversation(id); + if (wasActive) { + navigate('/'); + } + }, + [deleteConversation, activeConversationId, navigate], + ); + + const handleStartRename = useCallback((e: React.MouseEvent, id: string, currentTitle: string) => { + e.stopPropagation(); + setEditingConversationId(id); + setEditTitle(currentTitle); + }, []); + + const handleSaveRename = useCallback( + (e: React.MouseEvent | React.KeyboardEvent, id: string) => { + e.stopPropagation(); + if (editTitle.trim()) { + renameConversation(id, editTitle.trim()); + } + setEditingConversationId(null); + setEditTitle(''); + }, + [editTitle, renameConversation], + ); + + const handleCancelRename = useCallback(() => { + setEditingConversationId(null); + setEditTitle(''); + }, []); // Get git SHA for version display (monorepo - same for frontend and backend) const gitSha = env.VITE_GIT_SHA; @@ -84,19 +148,23 @@ export function AppLayout({ children }: AppLayoutProps) { : gitSha.slice(0, 7) : 'dev'; - // Auto-collapse sidebar when opening workflow builder, expand for other routes + // Auto-collapse sidebar when opening workflow builder or webhook editor, expand for other routes // On mobile, always start collapsed useEffect(() => { if (isMobile) { setSidebarOpen(false); setWasExplicitlyOpened(false); } else { - const isWorkflowRoute = - (location.pathname.startsWith('/workflows') || - location.pathname.startsWith('/webhooks/')) && - location.pathname !== '/'; - setSidebarOpen(!isWorkflowRoute); - setWasExplicitlyOpened(!isWorkflowRoute); + // Only collapse for workflow builder (not the list page) and webhook editor + // /workflows = list page (keep sidebar open) + // /workflows/new or /workflows/:id = builder (collapse sidebar) + // /webhooks/:id = webhook editor (collapse sidebar) + const isWorkflowBuilder = + location.pathname.startsWith('/workflows/') && location.pathname !== '/workflows/'; + const isWebhookEditor = location.pathname.startsWith('/webhooks/'); + const shouldCollapse = isWorkflowBuilder || isWebhookEditor; + setSidebarOpen(!shouldCollapse); + setWasExplicitlyOpened(!shouldCollapse); } }, [location.pathname, isMobile]); @@ -256,8 +324,13 @@ export function AppLayout({ children }: AppLayoutProps) { const navigationItems = [ { - name: 'Workflow Builder', + name: 'AI Agent', href: '/', + icon: Bot, + }, + { + name: 'Workflow Builder', + href: '/workflows', icon: Workflow, }, { @@ -303,14 +376,17 @@ export function AppLayout({ children }: AppLayoutProps) { const isActive = (path: string) => { if (path === '/') { - return location.pathname === '/' || location.pathname.startsWith('/workflows'); + return location.pathname === '/' || location.pathname.startsWith('/c/'); + } + if (path === '/workflows') { + return location.pathname === '/workflows' || location.pathname.startsWith('/workflows/'); } return location.pathname === path || location.pathname.startsWith(`${path}/`); }; // Get page-specific actions const getPageActions = () => { - if (location.pathname === '/') { + if (location.pathname === '/workflows') { return ( +
- {/* Command Palette Button */} -
-
- {sidebarOpen && ( - - K - + {conversations.length === 0 ? ( +
+ +

No conversations yet

+

+ Start a new chat to begin +

+
+ ) : ( +
+ {conversations.map((conv) => ( +
+ editingConversationId !== conv.id && handleSelectConversation(conv.id) + } + className={cn( + 'w-full flex items-center gap-2.5 px-3 py-2 rounded-lg text-left', + 'transition-colors group cursor-pointer', + activeConversationId === conv.id + ? 'bg-accent text-accent-foreground' + : 'hover:bg-muted text-muted-foreground hover:text-foreground', + )} + > +
+ {editingConversationId === conv.id ? ( + setEditTitle(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') handleSaveRename(e, conv.id); + if (e.key === 'Escape') handleCancelRename(); + }} + onClick={(e) => e.stopPropagation()} + autoFocus + className={cn( + 'w-full text-xs font-medium bg-background border border-input rounded px-1.5 py-0.5', + 'focus:outline-none focus:ring-1 focus:ring-ring', + )} + /> + ) : ( +

{conv.title}

+ )} +
+
+ {editingConversationId === conv.id ? ( + + ) : ( + + )} + +
+
+ ))} +
)} - -
+
+ )} -
- {/* Auth components - UserButton includes organization switching */} - {showUserButton && ( -
- - {/* Dark mode toggle */} +
+
+
+ + + + {user?.firstName?.[0] || user?.email?.[0] || 'U'} + + {sidebarOpen && ( -
- )} - {/* Dark mode toggle when no user button */} - {!showUserButton && ( -
+ {sidebarOpen && ( -
- )} + )} +
@@ -554,8 +722,10 @@ export function AppLayout({ children }: AppLayoutProps) { isMobile ? 'w-full' : '', )} > - {/* Only show AppTopBar for non-workflow-builder and non-webhook-editor pages */} - {!location.pathname.startsWith('/workflows') && + {/* Only show AppTopBar for non-agent, non-workflow-builder, and non-webhook-editor pages */} + {location.pathname !== '/' && + !location.pathname.startsWith('/c/') && + !location.pathname.startsWith('/workflows') && !location.pathname.startsWith('/webhooks/') && ( = ({ + language, + value, + showLineNumbers = true, +}) => { + const [copied, setCopied] = useState(false); + + const handleCopy = useCallback(async () => { + try { + await navigator.clipboard.writeText(value); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error('Failed to copy code:', err); + } + }, [value]); + + const displayLanguage = language === 'text' ? 'plaintext' : language; + + // Determine theme based on document class + const isDark = document.documentElement.classList.contains('dark'); + const syntaxTheme = isDark ? vscDarkPlus : vs; + + return ( +
+
+ + {displayLanguage} + + +
+ +
+ + {value} + +
+
+ ); +}; + +export default CodeBlock; diff --git a/frontend/src/components/ui/MoMADiagram.tsx b/frontend/src/components/ui/MoMADiagram.tsx new file mode 100644 index 00000000..cf1b97fd --- /dev/null +++ b/frontend/src/components/ui/MoMADiagram.tsx @@ -0,0 +1,463 @@ +import React, { useState, useRef, useEffect, useCallback } from 'react'; +import mermaid from 'mermaid'; +import { Copy, Download, Maximize2, ZoomIn, ZoomOut, RotateCcw, Loader2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +// Initialize Mermaid once +let mermaidInitialized = false; + +const initializeMermaid = () => { + if (mermaidInitialized) return; + + mermaid.initialize({ + startOnLoad: false, + theme: 'base', + themeVariables: { + primaryColor: '#3C82F6', + primaryTextColor: '#FFFFFF', + primaryBorderColor: '#2563EB', + lineColor: '#7A7C80', + secondaryColor: '#F3F4F6', + tertiaryColor: '#E5E7EB', + background: '#1C1C1C', + mainBkg: '#232427', + nodeBorder: '#2A2B2D', + clusterBkg: '#232427', + clusterBorder: '#2A2B2D', + titleColor: '#FFFFFF', + edgeLabelBackground: '#1F2023', + actorBkg: '#232427', + actorBorder: '#2A2B2D', + actorLineColor: '#7A7C80', + signalColor: '#7A7C80', + signalTextColor: '#C6C7C8', + labelBoxBkgColor: '#1F2023', + labelBoxBorderColor: '#2A2B2D', + labelTextColor: '#C6C7C8', + loopTextColor: '#C6C7C8', + noteBorderColor: '#2A2B2D', + noteTextColor: '#C6C7C8', + message0: '#3C82F6', + message1: '#10B981', + message2: '#F59E0B', + message3: '#EF4444', + message4: '#8B5CF6', + message5: '#EC4899', + message6: '#14B8A6', + message7: '#F97316', + }, + securityLevel: 'loose', + fontFamily: 'DM Sans, -apple-system, BlinkMacSystemFont, Segoe UI, sans-serif', + fontSize: 14, + flowchart: { + curve: 'basis', + padding: 20, + nodeSpacing: 50, + rankSpacing: 50, + useMaxWidth: true, + }, + sequence: { + diagramMarginX: 50, + diagramMarginY: 10, + actorMargin: 50, + width: 150, + height: 65, + boxMargin: 10, + boxTextMargin: 5, + noteMargin: 10, + messageMargin: 35, + useMaxWidth: true, + }, + }); + mermaidInitialized = true; +}; + +export interface MoMADiagramProps { + code: string; + className?: string; + onEdit?: (code: string) => void; +} + +type ViewState = 'idle' | 'loading' | 'rendered' | 'error'; + +interface Transform { + scale: number; + translateX: number; + translateY: number; +} + +export const MoMADiagram = React.forwardRef( + ({ code, className, onEdit }, ref) => { + const [viewState, setViewState] = useState('idle'); + const [errorMessage, setErrorMessage] = useState(''); + const [svgContent, setSvgContent] = useState(''); + const [transform, setTransform] = useState({ + scale: 1, + translateX: 0, + translateY: 0, + }); + const [isDragging, setIsDragging] = useState(false); + const [dragStart, setDragStart] = useState({ x: 0, y: 0 }); + const [showCopyFeedback, setShowCopyFeedback] = useState(false); + + const containerRef = useRef(null); + const svgRef = useRef(null); + const diagramIdRef = useRef(`mermaid-${Math.random().toString(36).substr(2, 9)}`); + + // Initialize Mermaid on mount + useEffect(() => { + initializeMermaid(); + }, []); + + // Parse and render diagram when code changes + useEffect(() => { + if (!code.trim()) { + setViewState('idle'); + return; + } + + const renderDiagram = async () => { + setViewState('loading'); + setErrorMessage(''); + + try { + // Ensure Mermaid is initialized + initializeMermaid(); + + const uniqueId = `mermaid-${Math.random().toString(36).substr(2, 9)}`; + diagramIdRef.current = uniqueId; + + // Parse and render the diagram + const { svg } = await mermaid.render(uniqueId, code); + setSvgContent(svg); + setViewState('rendered'); + + // Reset transform when new diagram renders + setTransform({ scale: 1, translateX: 0, translateY: 0 }); + } catch (error) { + console.error('Mermaid rendering error:', error); + setErrorMessage(error instanceof Error ? error.message : 'Failed to render diagram'); + setViewState('error'); + } + }; + + const debounceTimer = setTimeout(renderDiagram, 150); + return () => clearTimeout(debounceTimer); + }, [code]); + + // Handle wheel zoom + const handleWheel = useCallback( + (e: React.WheelEvent) => { + e.preventDefault(); + const delta = e.deltaY > 0 ? -0.1 : 0.1; + const newScale = Math.min(Math.max(transform.scale + delta, 0.1), 5); + setTransform((prev) => ({ ...prev, scale: newScale })); + }, + [transform.scale], + ); + + // Handle pan drag start + const handleMouseDown = useCallback( + (e: React.MouseEvent) => { + if (e.button !== 0) return; // Only left mouse button + setIsDragging(true); + setDragStart({ x: e.clientX - transform.translateX, y: e.clientY - transform.translateY }); + }, + [transform], + ); + + // Handle pan drag move + const handleMouseMove = useCallback( + (e: React.MouseEvent) => { + if (!isDragging) return; + e.preventDefault(); + setTransform({ + ...transform, + translateX: e.clientX - dragStart.x, + translateY: e.clientY - dragStart.y, + }); + }, + [isDragging, dragStart, transform], + ); + + // Handle pan drag end + const handleMouseUp = useCallback(() => { + setIsDragging(false); + }, []); + + // Handle global mouse up to catch drag releases outside container + useEffect(() => { + const handleGlobalMouseUp = () => setIsDragging(false); + window.addEventListener('mouseup', handleGlobalMouseUp); + return () => window.removeEventListener('mouseup', handleGlobalMouseUp); + }, []); + + // Zoom controls + const handleZoomIn = useCallback(() => { + setTransform((prev) => ({ ...prev, scale: Math.min(prev.scale + 0.2, 5) })); + }, []); + + const handleZoomOut = useCallback(() => { + setTransform((prev) => ({ ...prev, scale: Math.max(prev.scale - 0.2, 0.1) })); + }, []); + + const handleResetView = useCallback(() => { + setTransform({ scale: 1, translateX: 0, translateY: 0 }); + }, []); + + const handleFitToScreen = useCallback(() => { + if (!containerRef.current || !svgRef.current) return; + + const container = containerRef.current; + const svg = svgRef.current.firstElementChild; + + if (!svg) return; + + const containerRect = container.getBoundingClientRect(); + const svgRect = svg.getBoundingClientRect(); + + const padding = 40; + const scaleX = (containerRect.width - padding) / svgRect.width; + const scaleY = (containerRect.height - padding) / svgRect.height; + const newScale = Math.min(scaleX, scaleY, 1); + + setTransform({ scale: newScale, translateX: 0, translateY: 0 }); + }, []); + + // Copy code to clipboard + const handleCopyCode = useCallback(async () => { + try { + await navigator.clipboard.writeText(code); + setShowCopyFeedback(true); + setTimeout(() => setShowCopyFeedback(false), 2000); + } catch (error) { + console.error('Failed to copy code:', error); + } + }, [code]); + + // Download SVG + const handleDownloadSVG = useCallback(() => { + if (!svgContent) return; + + const blob = new Blob([svgContent], { type: 'image/svg+xml' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + link.href = url; + link.download = `diagram-${Date.now()}.svg`; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); + }, [svgContent]); + + return ( +
{ + // Handle both refs + if (typeof ref === 'function') ref(node); + else if (ref) ref.current = node; + containerRef.current = node; + }} + className={cn( + 'relative overflow-hidden rounded-lg border bg-card', + 'shadow-sm transition-shadow duration-200', + 'hover:shadow-md', + className, + )} + style={{ height: '100%', minHeight: '300px' }} + > + {/* Toolbar */} +
+
+ Diagram +
+
+ {/* Zoom controls */} + + + + + +
+ + {/* Action buttons */} + + +
+
+ + {/* Diagram container */} +
+
+ {viewState === 'loading' && ( +
+ + Rendering diagram... +
+ )} + + {viewState === 'error' && ( +
+
+ + + +
+
+

Diagram Error

+

+ {errorMessage || 'Unable to render the diagram. Please check the syntax.'} +

+
+ {onEdit && ( + + )} +
+ )} + + {viewState === 'rendered' && svgContent && ( +
+ )} + + {viewState === 'idle' && ( +
+ + + + No diagram to display +
+ )} +
+
+ + {/* Zoom indicator */} + {transform.scale !== 1 && ( +
+ {Math.round(transform.scale * 100)}% +
+ )} +
+ ); + }, +); + +MoMADiagram.displayName = 'MoMADiagram'; + +export default MoMADiagram; + +/** + * Utility function to extract Mermaid/MoMA diagram code from markdown text. + * Supports both ```mermaid and ```moma code blocks. + */ +export function extractDiagramCode(text: string): string | null { + // Try to match ```moma or ```mermaid code blocks + const mermaidRegex = /```(?:moma|mermaid)\n([\s\S]*?)```/i; + const match = text.match(mermaidRegex); + + if (match && match[1]) { + return match[1].trim(); + } + + return null; +} + +/** + * Utility function to extract all Mermaid/MoMA diagrams from markdown text. + * Returns an array of diagram code strings. + */ +export function extractAllDiagrams(text: string): string[] { + const mermaidRegex = /```(?:moma|mermaid)\n([\s\S]*?)```/gi; + const matches: string[] = []; + let match; + + while ((match = mermaidRegex.exec(text)) !== null) { + if (match[1]) { + matches.push(match[1].trim()); + } + } + + return matches; +} diff --git a/frontend/src/components/ui/__tests__/markdown.test.tsx b/frontend/src/components/ui/__tests__/markdown.test.tsx index 8f2c726d..7c8e6c9f 100644 --- a/frontend/src/components/ui/__tests__/markdown.test.tsx +++ b/frontend/src/components/ui/__tests__/markdown.test.tsx @@ -31,7 +31,13 @@ describe('MarkdownView', () => { it('renders code blocks', () => { const markdown = '```js\nconst x = 1\n```'; - render(); - expect(screen.getByText(/const x = 1/)).toBeInTheDocument(); + const { container } = render(); + // Syntax highlighting splits code into tokens, so check for individual parts + expect(screen.getByText('const')).toBeInTheDocument(); + expect(screen.getByText('x')).toBeInTheDocument(); + // Check that the CodeBlock component wrapper is rendered + expect(container.querySelector('.rounded-lg.border')).toBeInTheDocument(); + // Verify the language label is shown + expect(screen.getByText('js')).toBeInTheDocument(); }); }); diff --git a/frontend/src/components/ui/markdown.tsx b/frontend/src/components/ui/markdown.tsx index a1bd0091..63a70835 100644 --- a/frontend/src/components/ui/markdown.tsx +++ b/frontend/src/components/ui/markdown.tsx @@ -5,6 +5,7 @@ import markdownItTaskLists from 'markdown-it-task-lists'; import markdownItHTML5Embed from 'markdown-it-html5-embed'; import markdownItImsize from '@/lib/markdown-it-imsize'; import { cn } from '@/lib/utils'; +import { CodeBlock } from './CodeBlock'; interface MarkdownViewProps { content: string; @@ -13,6 +14,7 @@ interface MarkdownViewProps { // When provided, enables interactive task checkboxes and will be called // with the updated markdown string after a toggle. onEdit?: (next: string) => void; + showLineNumbers?: boolean; } // Initialize markdown-it with plugins (similar to n8n sticky notes) @@ -67,6 +69,59 @@ function toggleNthTask(md: string, index: number): string { // Key: dataTestId, Value: expected content string const pendingCheckboxUpdates = new Map(); +// Code block renderer with syntax highlighting +const renderCodeBlocks = (html: string, showLineNumbers = true): React.ReactNode => { + const codeBlockRegex = /
(.*?)<\/code><\/pre>/gs;
+  const blocks: React.ReactNode[] = [];
+  let lastIndex = 0;
+  let match;
+
+  while ((match = codeBlockRegex.exec(html)) !== null) {
+    // Add text before this code block
+    if (match.index > lastIndex) {
+      blocks.push(
+        
, + ); + } + + const language = match[1]; + const code = match[2] + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'"); + + blocks.push( + , + ); + + lastIndex = match.index + match[0].length; + } + + // Add remaining text after last code block + if (lastIndex < html.length) { + blocks.push( +
, + ); + } + + // If no code blocks found, return original HTML + if (blocks.length === 0) { + return
; + } + + return <>{blocks}; +}; + // Custom comparison for memo - only re-render when content/className/dataTestId change // Ignore onEdit since it's stored in a ref and changes every parent render function arePropsEqual(prevProps: MarkdownViewProps, nextProps: MarkdownViewProps): boolean { @@ -88,7 +143,8 @@ function arePropsEqual(prevProps: MarkdownViewProps, nextProps: MarkdownViewProp const equal = prevProps.content === nextProps.content && prevProps.className === nextProps.className && - prevProps.dataTestId === nextProps.dataTestId; + prevProps.dataTestId === nextProps.dataTestId && + prevProps.showLineNumbers === nextProps.showLineNumbers; if (!equal) { console.log('[MarkdownView] Props changed, will re-render'); } @@ -102,6 +158,7 @@ export const MarkdownView = memo(function MarkdownView({ className, dataTestId, onEdit, + showLineNumbers = true, }: MarkdownViewProps) { console.log('[MarkdownView] Rendering with content length:', content.length); // Store onEdit in a ref so we can use a stable callback without re-renders @@ -122,6 +179,12 @@ export const MarkdownView = memo(function MarkdownView({ return rendered.replace(/(]*type="checkbox"[^>]*)disabled([^>]*>)/g, '$1$2'); }, [normalized]); + // Render with code block highlighting + const renderedContent = useMemo( + () => renderCodeBlocks(html, showLineNumbers), + [html, showLineNumbers], + ); + // Handle clicks on interactive elements - use useCallback for stable reference const handleClick = useCallback((e: React.MouseEvent) => { const target = e.target as HTMLElement; @@ -216,8 +279,9 @@ export const MarkdownView = memo(function MarkdownView({ onMouseDownCapture={handleMouseDown} onClick={handleClick} onWheel={handleWheel} - dangerouslySetInnerHTML={{ __html: html }} - /> + > + {renderedContent} +
); }, arePropsEqual); diff --git a/frontend/src/pages/AgentPage.tsx b/frontend/src/pages/AgentPage.tsx new file mode 100644 index 00000000..b06859d6 --- /dev/null +++ b/frontend/src/pages/AgentPage.tsx @@ -0,0 +1,1980 @@ +import React, { useState, useRef, useEffect, useCallback, useMemo } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { + Send, + ArrowUp, + Sparkles, + Workflow, + Shield, + FileSearch, + Zap, + User, + GitBranch, + AlertTriangle, + ChevronRight, + Clock, + Loader2, + Play, + Bug, + Lock, + CloudCog, + Database, + Terminal, + PanelLeftOpen, + PanelLeftClose, +} from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { cn } from '@/lib/utils'; +import { useChatStore, type ChatMessage } from '@/store/chatStore'; +import { useAuthProvider } from '@/auth/auth-context'; +import { MarkdownView } from '@/components/ui/markdown'; +import { StatusBar } from '@/components/agent/StatusBar'; +import { MoMADiagram } from '@/components/ui/MoMADiagram'; +import { useSidebar } from '@/components/layout/sidebar-context'; + +// GitHub icon component +function GitHubIcon({ className }: { className?: string }) { + return ( + + + + ); +} + +const suggestedActions = [ + { + icon: Workflow, + label: 'Run workflow', + description: 'Execute a security workflow', + }, + { + icon: Shield, + label: 'Scan repository', + description: 'Analyze code for vulnerabilities', + }, + { + icon: FileSearch, + label: 'Review findings', + description: 'Check recent security findings', + }, + { + icon: AlertTriangle, + label: 'Investigate alert', + description: 'Investigate security alerts', + }, +]; + +// Types for rich message content +type MessageContentType = + | { type: 'text'; content: string } + | { type: 'thinking'; content: string } + | { type: 'workflow-buttons'; workflows: WorkflowOption[] } + | { type: 'repo-buttons'; repos: RepoOption[]; intro: string } + | { type: 'finding-cards'; findings: FindingOption[] } + | { type: 'quick-action-buttons'; actions: QuickActionOption[] } + | { type: 'guardduty-alerts'; alerts: GuardDutyAlert[] } + | { type: 'action-buttons'; buttons: ActionButton[] } + | { type: 'loading'; content: string } + | { type: 'moma-diagram'; code: string }; + +interface WorkflowOption { + id: string; + name: string; + description: string; + icon: React.ElementType; + status: 'active' | 'scheduled' | 'draft'; +} + +interface RepoOption { + id: string; + name: string; + org: string; + lastScanned?: string; + isRecent?: boolean; +} + +interface FindingOption { + id: string; + source: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + count: number; + lastRun: string; + icon: React.ElementType; +} + +interface QuickActionOption { + id: string; + name: string; + description: string; + icon: React.ElementType; +} + +interface GuardDutyAlert { + id: string; + title: string; + severity: 'critical' | 'high' | 'medium' | 'low'; + timestamp: string; + instance: string; + sourceIp: string; + description: string; +} + +interface ActionButton { + id: string; + label: string; + emoji: string; + variant: 'primary' | 'destructive'; +} + +// Extended message type for rich content +interface RichChatMessage extends Omit { + content: string | MessageContentType[]; + isStreaming?: boolean; +} + +// Module-level cache for rich messages across component remounts +// (React Router remounts AgentPage when switching between "/" and "/c/:id" routes) +const richMessagesByConversation = new Map(); + +// Collapsible thinking section component (like ChatGPT) +function ThinkingSection({ content, isActive }: { content: string; isActive: boolean }) { + const [isOpen, setIsOpen] = useState(false); + const [elapsed, setElapsed] = useState(0); + const startTimeRef = useRef(Date.now()); + const frozenElapsedRef = useRef(null); + const wasEverActiveRef = useRef(isActive); + + useEffect(() => { + if (isActive) { + wasEverActiveRef.current = true; + startTimeRef.current = Date.now(); + frozenElapsedRef.current = null; + const interval = setInterval(() => { + setElapsed(Math.floor((Date.now() - startTimeRef.current) / 1000)); + }, 100); + return () => clearInterval(interval); + } else if (wasEverActiveRef.current && frozenElapsedRef.current === null) { + frozenElapsedRef.current = Math.max( + 1, + Math.floor((Date.now() - startTimeRef.current) / 1000), + ); + setElapsed(frozenElapsedRef.current); + } + }, [isActive]); + + if (isActive) { + return ( +
+ + Thinking + {elapsed > 0 && {elapsed}s} +
+ ); + } + + const displaySeconds = frozenElapsedRef.current || Math.max(1, elapsed); + const durationLabel = wasEverActiveRef.current + ? `Thought for ${displaySeconds} second${displaySeconds !== 1 ? 's' : ''}` + : 'Thought for a moment'; + + return ( +
+ + {isOpen && ( +
+

{content}

+
+ )} +
+ ); +} + +// Workflow button component +function WorkflowButton({ + workflow, + onClick, +}: { + workflow: WorkflowOption; + onClick: (workflow: WorkflowOption) => void; +}) { + const Icon = workflow.icon; + const statusColors = { + active: 'bg-green-500/10 text-green-600 border-green-200', + scheduled: 'bg-blue-500/10 text-blue-600 border-blue-200', + draft: 'bg-gray-500/10 text-gray-600 border-gray-200', + }; + + return ( + + ); +} + +// Repository button component +function RepoButton({ repo, onClick }: { repo: RepoOption; onClick: (repo: RepoOption) => void }) { + return ( + + ); +} + +// Finding card component +function FindingCard({ + finding, + onClick, +}: { + finding: FindingOption; + onClick: (finding: FindingOption) => void; +}) { + const Icon = finding.icon; + const severityColors = { + critical: 'bg-red-500/10 text-red-600 border-red-200', + high: 'bg-orange-500/10 text-orange-600 border-orange-200', + medium: 'bg-yellow-500/10 text-yellow-600 border-yellow-200', + low: 'bg-blue-500/10 text-blue-600 border-blue-200', + }; + + return ( + + ); +} + +// Quick action button component +function QuickActionButton({ + action, + onClick, +}: { + action: QuickActionOption; + onClick: (action: QuickActionOption) => void; +}) { + const Icon = action.icon; + + return ( + + ); +} + +// GuardDuty alert card component +function GuardDutyAlertCard({ + alert, + onClick, +}: { + alert: GuardDutyAlert; + onClick: (alert: GuardDutyAlert) => void; +}) { + const severityColors = { + critical: 'bg-red-500/10 text-red-600 border-red-200', + high: 'bg-orange-500/10 text-orange-600 border-orange-200', + medium: 'bg-yellow-500/10 text-yellow-600 border-yellow-200', + low: 'bg-blue-500/10 text-blue-600 border-blue-200', + }; + + return ( + + ); +} + +// Action button group component +function ActionButtonGroup({ + buttons, + onClick, +}: { + buttons: ActionButton[]; + onClick: (button: ActionButton) => void; +}) { + return ( +
+ {buttons.map((button) => ( + + ))} +
+ ); +} + +// Rich message content renderer +function RichMessageContent({ + content, + onWorkflowClick, + onRepoClick, + onFindingClick, + onQuickActionClick, + onAlertClick, + onActionButtonClick, +}: { + content: MessageContentType[]; + onWorkflowClick: (workflow: WorkflowOption) => void; + onRepoClick: (repo: RepoOption) => void; + onFindingClick: (finding: FindingOption) => void; + onQuickActionClick: (action: QuickActionOption) => void; + onAlertClick: (alert: GuardDutyAlert) => void; + onActionButtonClick: (button: ActionButton) => void; +}) { + return ( +
+ {content.map((item, index) => { + switch (item.type) { + case 'text': + return ( + + ); + case 'thinking': + return ( + + ); + case 'loading': + return ( +
+ + {item.content} +
+ ); + case 'workflow-buttons': + return ( +
+ {item.workflows.map((workflow) => ( + + ))} +
+ ); + case 'repo-buttons': + return ( +
+

{item.intro}

+
+ {item.repos.map((repo) => ( + + ))} +
+
+ ); + case 'finding-cards': + return ( +
+ {item.findings.map((finding) => ( + + ))} +
+ ); + case 'quick-action-buttons': + return ( +
+ {item.actions.map((action) => ( + + ))} +
+ ); + case 'guardduty-alerts': + return ( +
+ {item.alerts.map((alert) => ( + + ))} +
+ ); + case 'action-buttons': + return ( + + ); + default: + return null; + } + })} +
+ ); +} + +interface MessageBubbleProps { + message: RichChatMessage; + userImageUrl?: string; + userInitials?: string; + onWorkflowClick: (workflow: WorkflowOption) => void; + onRepoClick: (repo: RepoOption) => void; + onFindingClick: (finding: FindingOption) => void; + onQuickActionClick: (action: QuickActionOption) => void; + onAlertClick: (alert: GuardDutyAlert) => void; + onActionButtonClick: (button: ActionButton) => void; +} + +function MessageBubble({ + message, + userImageUrl, + userInitials, + onWorkflowClick, + onRepoClick, + onFindingClick, + onQuickActionClick, + onAlertClick, + onActionButtonClick, +}: MessageBubbleProps) { + const isUser = message.role === 'user'; + const isRichContent = Array.isArray(message.content); + + // Extract diagrams from rich content to render outside the bubble + const contentWithDiagramsExtracted = useMemo(() => { + if (!isRichContent) + return { + bubbleContent: message.content as string | MessageContentType[], + diagrams: [] as MessageContentType[], + }; + + const content = message.content as MessageContentType[]; + const diagrams: MessageContentType[] = []; + const bubbleContent: MessageContentType[] = []; + + content.forEach((item) => { + if (item.type === 'moma-diagram') { + diagrams.push(item); + } else { + bubbleContent.push(item); + } + }); + + return { bubbleContent, diagrams }; + }, [isRichContent, message.content]); + + return ( +
+ {/* Chat bubble */} +
+ {/* Assistant avatar (left side) */} + {!isUser && ( + + + + AI + + + )} + +
+ {isRichContent ? ( + + ) : ( + + )} + + {message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} + +
+ + {/* User avatar (right side) */} + {isUser && ( + + + + {userInitials || } + + + )} +
+ + {/* Render diagrams outside the bubble, full width */} + {contentWithDiagramsExtracted.diagrams.map((item: MessageContentType, index: number) => ( +
+ {item.type === 'moma-diagram' && } +
+ ))} +
+ ); +} + +function WelcomeScreen({ onSuggestedAction }: { onSuggestedAction: (action: string) => void }) { + return ( +
+ {/* Logo and branding */} +
+
+ ShipSec { + e.currentTarget.style.display = 'none'; + }} + /> +
+

ShipSec AI Agent

+

+ Your intelligent security assistant +

+
+
+ {/*
+ + Powered by Claude Opus +
*/} +
+ + {/* Suggested actions */} +
+ {suggestedActions.map((action) => { + const Icon = action.icon; + return ( + + ); + })} +
+ + {/* Metadata footer */} +
+

+ ShipSec AI can help you with security workflows, code scanning, and vulnerability + management. +

+
+
+ ); +} + +// Mock data for workflows +const mockWorkflows: WorkflowOption[] = [ + { + id: 'w1', + name: 'SAST Code Analysis', + description: 'Static application security testing with Semgrep', + icon: Bug, + status: 'active', + }, + { + id: 'w2', + name: 'Dependency Audit', + description: 'Check for vulnerable dependencies in your codebase', + icon: GitBranch, + status: 'active', + }, + { + id: 'w3', + name: 'Secret Scanner', + description: 'Detect hardcoded secrets and API keys', + icon: Lock, + status: 'active', + }, + { + id: 'w4', + name: 'Infrastructure Review', + description: 'Analyze IaC templates for misconfigurations', + icon: CloudCog, + status: 'scheduled', + }, + { + id: 'w5', + name: 'Container Security', + description: 'Scan Docker images for vulnerabilities', + icon: Database, + status: 'draft', + }, +]; + +// Mock data for repositories +const mockRepos: RepoOption[] = [ + { + id: 'r1', + name: 'studio', + org: 'ShipSecAI', + lastScanned: '2 hours ago', + isRecent: true, + }, + { + id: 'r2', + name: 'api-gateway', + org: 'ShipSecAI', + lastScanned: '1 day ago', + }, + { + id: 'r3', + name: 'auth-service', + org: 'ShipSecAI', + lastScanned: '3 days ago', + }, + { + id: 'r4', + name: 'frontend-app', + org: 'ShipSecAI', + lastScanned: '1 week ago', + }, +]; + +// Mock data for findings +const mockFindings: FindingOption[] = [ + { + id: 'f1', + source: 'AWS Security Hub', + severity: 'critical', + count: 3, + lastRun: 'Today, 2:30 PM', + icon: CloudCog, + }, + { + id: 'f2', + source: 'GitHub Advanced Security', + severity: 'high', + count: 12, + lastRun: 'Today, 11:00 AM', + icon: GitBranch, + }, + { + id: 'f3', + source: 'Semgrep SAST', + severity: 'medium', + count: 28, + lastRun: 'Yesterday, 4:15 PM', + icon: Bug, + }, + { + id: 'f4', + source: 'Trivy Container Scan', + severity: 'high', + count: 7, + lastRun: 'Yesterday, 9:00 AM', + icon: Database, + }, + { + id: 'f5', + source: 'Checkov IaC Analysis', + severity: 'low', + count: 45, + lastRun: '2 days ago', + icon: Terminal, + }, +]; + +// Dynamic date helpers for realistic mock data +function formatAlertTimestamp(date: Date, hours: number, minutes: number): string { + const y = date.getUTCFullYear(); + const m = String(date.getUTCMonth() + 1).padStart(2, '0'); + const d = String(date.getUTCDate()).padStart(2, '0'); + return `${y}-${m}-${d} ${String(hours).padStart(2, '0')}:${String(minutes).padStart(2, '0')} UTC`; +} + +function formatDateOnly(date: Date): string { + const y = date.getUTCFullYear(); + const m = String(date.getUTCMonth() + 1).padStart(2, '0'); + const d = String(date.getUTCDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; +} + +function formatDateCompact(date: Date): string { + const y = date.getUTCFullYear(); + const m = String(date.getUTCMonth() + 1).padStart(2, '0'); + const d = String(date.getUTCDate()).padStart(2, '0'); + return `${y}${m}${d}`; +} + +const _now = new Date(); +const _today = new Date(_now); +const _yesterday = new Date(_now); +_yesterday.setDate(_yesterday.getDate() - 1); + +const ALERT_DATE_PRIMARY = formatAlertTimestamp(_today, 6, 22); +const ALERT_DATE_SECONDARY = formatAlertTimestamp(_yesterday, 18, 47); +const ALERT_DATE_TERTIARY = formatAlertTimestamp(_yesterday, 14, 30); + +const _ptoStart = new Date(_today); +_ptoStart.setDate(_ptoStart.getDate() - 5); +const _ptoEnd = new Date(_today); +_ptoEnd.setDate(_ptoEnd.getDate() + 1); +const PTO_START = formatDateOnly(_ptoStart); +const PTO_END = formatDateOnly(_ptoEnd); +const TODAY_COMPACT = formatDateCompact(_today); + +// Mock data for GuardDuty alerts +const mockGuardDutyAlerts: GuardDutyAlert[] = [ + { + id: 'gd1', + title: 'Unusual SSH Login Detected', + severity: 'high', + timestamp: ALERT_DATE_PRIMARY, + instance: 'i-07abc123d456ef789', + sourceIp: '189.45.23.18', + description: 'Suspicious SSH login from unauthorized IP (GeoIP: São Paulo, Brazil)', + }, + { + id: 'gd3', + title: 'IAM Credentials Exfiltration Attempt', + severity: 'high', + timestamp: ALERT_DATE_SECONDARY, + instance: 'i-09f8e7d6c5b4a3210', + sourceIp: '103.21.244.0', + description: 'Unusual API call pattern detected from temporary credentials', + }, + { + id: 'gd4', + title: 'S3 Bucket Brute Force Access', + severity: 'medium', + timestamp: ALERT_DATE_TERTIARY, + instance: 'N/A', + sourceIp: '198.51.100.42', + description: 'Multiple failed access attempts on private S3 buckets', + }, +]; + +export function AgentPage() { + const { conversationId: urlConversationId } = useParams<{ conversationId: string }>(); + const navigate = useNavigate(); + const [inputValue, setInputValue] = useState(''); + const [richMessages, setRichMessages] = useState([]); + const messagesEndRef = useRef(null); + const textareaRef = useRef(null); + + // Sidebar toggle from AppLayout context + const { isOpen: sidebarOpen, toggle: toggleSidebar, isMobile } = useSidebar(); + + const authProvider = useAuthProvider(); + const { user } = authProvider.context; + + // Get user avatar info from Clerk authentication + const userImageUrl = user?.imageUrl; + const userInitials = + user?.firstName && user?.lastName + ? `${user.firstName[0]}${user.lastName[0]}` + : user?.username + ? user.username.substring(0, 2).toUpperCase() + : user?.email + ? user.email.substring(0, 2).toUpperCase() + : undefined; + + const { + activeConversationId, + createConversation, + addMessage, + setMessages, + getActiveConversation, + setActiveConversation, + conversations, + } = useChatStore(); + + // Refs for tracking conversation ownership across effect cycles + const richMessagesRef = useRef([]); + const activeConvIdRef = useRef(null); + const syncTimerRef = useRef | undefined>(undefined); + + // Sync URL param with active conversation (only when navigating to /c/:id directly) + // Don't clear activeConversation when urlConversationId is absent — the "New Chat" + // handlers in AgentLayout/AppLayout handle that explicitly via setActiveConversation(null). + useEffect(() => { + if (urlConversationId) { + const exists = conversations.some((c) => c.id === urlConversationId); + if (exists) { + setActiveConversation(urlConversationId); + } else { + navigate('/', { replace: true }); + } + } + }, [urlConversationId, conversations, setActiveConversation, navigate]); + + // Keep richMessagesRef in sync and save to module cache on every change. + // Also debounce-sync text content to the chatStore for cross-refresh persistence. + useEffect(() => { + richMessagesRef.current = richMessages; + if (activeConvIdRef.current && richMessages.length > 0) { + // Immediately save to module-level cache (preserves rich components) + richMessagesByConversation.set(activeConvIdRef.current, [...richMessages]); + + // Debounced sync of text content to chatStore (for page refresh scenarios) + clearTimeout(syncTimerRef.current); + const convId = activeConvIdRef.current; + syncTimerRef.current = setTimeout(() => { + const textMessages = richMessages + .map((msg) => { + const contentStr = + typeof msg.content === 'string' + ? msg.content + : msg.content + .filter((c) => c.type === 'text') + .map((c) => (c as { type: 'text'; content: string }).content) + .join('\n'); + return { role: msg.role as 'user' | 'assistant', content: contentStr }; + }) + .filter((msg) => msg.content); + if (textMessages.length > 0) { + setMessages(convId, textMessages); + } + }, 500); + } + return () => clearTimeout(syncTimerRef.current); + }, [richMessages, setMessages]); + + // Handle conversation switching: save outgoing, load incoming + useEffect(() => { + const prevId = activeConvIdRef.current; + + // Save outgoing conversation's rich messages before switching + if (prevId && prevId !== activeConversationId && richMessagesRef.current.length > 0) { + richMessagesByConversation.set(prevId, [...richMessagesRef.current]); + // Immediate sync to store for the outgoing conversation + const textMessages = richMessagesRef.current + .map((msg) => { + const contentStr = + typeof msg.content === 'string' + ? msg.content + : msg.content + .filter((c) => c.type === 'text') + .map((c) => (c as { type: 'text'; content: string }).content) + .join('\n'); + return { role: msg.role as 'user' | 'assistant', content: contentStr }; + }) + .filter((msg) => msg.content); + if (textMessages.length > 0) { + setMessages(prevId, textMessages); + } + } + + activeConvIdRef.current = activeConversationId; + + // Load incoming conversation + if (!activeConversationId) { + setRichMessages([]); + return; + } + + // If we just created this conversation from addRichMessage (transitioning from no conversation), + // richMessages already contain the correct data — save to cache and don't overwrite + if (!prevId && richMessagesRef.current.length > 0) { + richMessagesByConversation.set(activeConversationId, [...richMessagesRef.current]); + return; + } + + // Check module cache first (has full rich content with components) + const cached = richMessagesByConversation.get(activeConversationId); + if (cached && cached.length > 0) { + setRichMessages(cached); + return; + } + + // Fall back to chatStore (plain text only, for page refresh scenarios) + const conversation = getActiveConversation(); + if (conversation && conversation.messages.length > 0) { + const loaded: RichChatMessage[] = conversation.messages.map((msg) => ({ + id: msg.id, + role: msg.role, + content: msg.content, + timestamp: msg.timestamp, + })); + setRichMessages(loaded); + } else { + setRichMessages([]); + } + }, [activeConversationId, getActiveConversation, setMessages]); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [richMessages]); + + // Auto-resize textarea + useEffect(() => { + if (textareaRef.current) { + textareaRef.current.style.height = 'auto'; + textareaRef.current.style.height = `${Math.min(textareaRef.current.scrollHeight, 200)}px`; + } + }, [inputValue]); + + const addRichMessage = useCallback( + (message: Omit) => { + const newMessage: RichChatMessage = { + ...message, + id: `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + timestamp: new Date(), + }; + setRichMessages((prev) => [...prev, newMessage]); + + // Also add to store for persistence (simplified content) + // Read directly from the store to get the latest value synchronously, + // avoiding stale closure when multiple addRichMessage calls happen in the same render cycle. + let conversationId = useChatStore.getState().activeConversationId; + if (!conversationId) { + conversationId = createConversation(); + // Update URL without React Router navigation to avoid component remount + // (React Router treats "/" and "/c/:id" as different routes, causing unmount/remount) + window.history.replaceState(null, '', `/c/${conversationId}`); + } + const contentStr = + typeof message.content === 'string' + ? message.content + : message.content + .filter((c) => c.type === 'text') + .map((c) => (c as { type: 'text'; content: string }).content) + .join('\n'); + + if (contentStr) { + addMessage(conversationId, { + role: message.role, + content: contentStr, + }); + } + + return newMessage.id; + }, + [createConversation, addMessage], + ); + + const simulateStreamingResponse = useCallback( + async (content: MessageContentType[]) => { + // Add initial message with thinking state — thinking stays active until content appears + const thinkingItem: MessageContentType = { + type: 'thinking', + content: 'Analyzing your request and retrieving data...', + }; + + addRichMessage({ + role: 'assistant', + content: [thinkingItem], + }); + + // Thinking phase — keep active for realistic duration + await new Promise((resolve) => setTimeout(resolve, 2000)); + + // Thinking collapses, content appears below (ChatGPT-style) + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + updated[lastIdx] = { + ...updated[lastIdx], + content: [thinkingItem, ...content], + }; + } + return updated; + }); + }, + [addRichMessage], + ); + + const handleRunWorkflow = useCallback(() => { + addRichMessage({ + role: 'user', + content: 'Run workflow', + }); + + simulateStreamingResponse([ + { + type: 'text', + content: + 'I found 5 active workflows in your ShipSec Studio. Here are the available security workflows you can run:', + }, + { type: 'workflow-buttons', workflows: mockWorkflows }, + { + type: 'text', + content: 'Click on any workflow to start it, or tell me which one you want to configure.', + }, + ]); + }, [addRichMessage, simulateStreamingResponse]); + + const handleScanRepository = useCallback(() => { + addRichMessage({ + role: 'user', + content: 'Scan repository', + }); + + simulateStreamingResponse([ + { + type: 'text', + content: + "You have 200 repositories connected to ShipSec Studio. I've identified your most recently scanned repositories:", + }, + { + type: 'repo-buttons', + repos: mockRepos, + intro: + 'Select a repository to scan, or I can recommend one based on the time since last scan:', + }, + { + type: 'text', + content: + 'Pro tip: The ShipSecAI/studio repository was last scanned 2 hours ago. Would you like to run another scan with the same configuration?', + }, + ]); + }, [addRichMessage, simulateStreamingResponse]); + + const handleReviewFindings = useCallback(() => { + addRichMessage({ + role: 'user', + content: 'Review findings', + }); + + const thinkingItem: MessageContentType = { + type: 'thinking', + content: 'Fetching security findings across all integrated sources...', + }; + + addRichMessage({ + role: 'assistant', + content: [thinkingItem], + }); + + // Thinking stays active for the full duration, then collapses when content appears + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + thinkingItem, + { + type: 'text', + content: + "I've retrieved your latest security findings from across all integrated sources. Here's a summary of the last 5 security reports:", + }, + { type: 'finding-cards', findings: mockFindings }, + { + type: 'text', + content: + 'You have 3 critical findings from AWS Security Hub that require immediate attention. Would you like me to provide detailed remediation steps for those?', + }, + ], + }; + } + return updated; + }); + }, 2500); + }, [addRichMessage]); + + const handleWorkflowClick = useCallback( + (workflow: WorkflowOption) => { + addRichMessage({ + role: 'user', + content: `Run ${workflow.name}`, + }); + + simulateStreamingResponse([ + { + type: 'text', + content: `Starting **${workflow.name}** workflow...\n\n${workflow.description}\n\nThis workflow will scan your connected repositories and report any findings. I'll notify you when the scan is complete.`, + }, + { + type: 'loading', + content: 'Initializing workflow...', + }, + ]); + + // Simulate completion — preserve thinking from simulateStreamingResponse + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + const prevContent = updated[lastIdx].content as MessageContentType[]; + const thinkingItems = prevContent.filter((c) => c.type === 'thinking'); + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + ...thinkingItems, + { + type: 'text', + content: `**${workflow.name}** workflow has been queued successfully!\n\nEstimated completion: 5-10 minutes\nRepositories to scan: 4\nNotification: Enabled\n\nI'll update you when the results are ready. Would you like to run another workflow or review existing findings?`, + }, + ], + }; + } + return updated; + }); + }, 4500); + }, + [addRichMessage, simulateStreamingResponse], + ); + + const handleRepoClick = useCallback( + (repo: RepoOption) => { + addRichMessage({ + role: 'user', + content: `Scan ${repo.org}/${repo.name}`, + }); + + simulateStreamingResponse([ + { + type: 'text', + content: `Initiating security scan for **${repo.org}/${repo.name}**...\n\nLast scanned: ${repo.lastScanned}\nBranch: main\nScan type: Full security suite`, + }, + { + type: 'loading', + content: 'Running SAST, SCA, and secret detection...', + }, + ]); + + // Preserve thinking from simulateStreamingResponse + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + const prevContent = updated[lastIdx].content as MessageContentType[]; + const thinkingItems = prevContent.filter((c) => c.type === 'thinking'); + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + ...thinkingItems, + { + type: 'text', + content: `Scan completed for **${repo.org}/${repo.name}**!\n\n**Results Summary:**\n- Critical: 0\n- High: 2\n- Medium: 8\n- Low: 15\n\nThe 2 high severity findings are related to outdated dependencies. Would you like me to show detailed remediation steps?`, + }, + ], + }; + } + return updated; + }); + }, 5000); + }, + [addRichMessage, simulateStreamingResponse], + ); + + const handleFindingClick = useCallback( + (finding: FindingOption) => { + addRichMessage({ + role: 'user', + content: `Show ${finding.source} findings`, + }); + + const thinkingItem: MessageContentType = { + type: 'thinking', + content: `Retrieving ${finding.source} findings...`, + }; + + addRichMessage({ + role: 'assistant', + content: [thinkingItem], + }); + + // Thinking stays active for the full duration, then collapses when content appears + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + const findingDetails: Record = { + 'AWS Security Hub': `**AWS Security Hub — CRITICAL Findings**\n\nTotal: ${finding.count} findings\nLast run: ${finding.lastRun}\nRegion: us-east-1\n\n**Finding Details:**\n\n1. **S3 Bucket Public Access Enabled** — \`CRITICAL\`\n - Resource: \`arn:aws:s3:::prod-user-uploads\`\n - Control: S3.2 — S3 buckets should prohibit public read access\n - Account: 491203847561 (production)\n - Remediation: Enable S3 Block Public Access at the bucket level and review bucket policy for \`Principal: "*"\` statements\n\n2. **IAM Root Account Access Key Active** — \`CRITICAL\`\n - Resource: \`arn:aws:iam::491203847561:root\`\n - Control: IAM.4 — IAM root user access key should not exist\n - Account: 491203847561 (production)\n - Remediation: Delete root access keys immediately, create an IAM admin user with MFA, and rotate any services using root credentials\n\n3. **RDS Instance Publicly Accessible** — \`CRITICAL\`\n - Resource: \`arn:aws:rds:us-east-1:491203847561:db/prod-postgres\`\n - Control: RDS.2 — RDS DB instances should prohibit public access\n - Account: 491203847561 (production)\n - Remediation: Modify the RDS instance to disable public accessibility, ensure it resides in a private subnet, and use VPC security groups to restrict access\n\nWould you like me to generate remediation steps or create Jira tickets for these findings?`, + + 'GitHub Advanced Security': `**GitHub Advanced Security — HIGH Findings**\n\nTotal: ${finding.count} findings across 4 repositories\nLast run: ${finding.lastRun}\n\n**Finding Details:**\n\n1. **SQL Injection via unsanitized query parameter** — \`HIGH\`\n - Rule: \`javascript/sql-injection\`\n - File: \`api-gateway/src/routes/users.ts:87\`\n - Branch: main\n - Snippet: \`db.query(\`SELECT * FROM users WHERE id = \${req.params.id}\`)\`\n - Remediation: Use parameterized queries with \`$1\` placeholders instead of string interpolation\n\n2. **Leaked GitHub Personal Access Token** — \`HIGH\`\n - Rule: \`secret-scanning/github-token\`\n - File: \`studio/.env.example:12\`\n - Branch: main\n - Remediation: Revoke the token in GitHub Settings > Developer settings > Tokens, rotate and store in a secrets manager\n\n3. **Prototype Pollution in lodash <4.17.21** — \`HIGH\`\n - Rule: \`dependabot/npm/lodash\`\n - File: \`frontend-app/package-lock.json\`\n - Remediation: Run \`npm audit fix\` or update lodash to >=4.17.21\n\n_Showing 3 of ${finding.count} findings. Would you like me to show all findings or generate remediation PRs?_`, + + 'Semgrep SAST': `**Semgrep SAST — MEDIUM Findings**\n\nTotal: ${finding.count} findings across 6 repositories\nLast run: ${finding.lastRun}\nRuleset: p/owasp-top-ten, p/typescript\n\n**Top Findings by Category:**\n\n| Category | Count | Severity |\n|---|---|---|\n| Missing input validation | 9 | Medium |\n| Insecure crypto usage | 6 | Medium |\n| Hardcoded configuration | 5 | Medium |\n| Missing error handling | 4 | Medium |\n| Insecure deserialization | 4 | Medium |\n\n**Sample Findings:**\n\n1. **Use of \`Math.random()\` for token generation** — \`MEDIUM\`\n - Rule: \`javascript.lang.security.insecure-randomness\`\n - File: \`auth-service/src/utils/token.ts:23\`\n - Remediation: Replace with \`crypto.randomBytes()\` or \`crypto.randomUUID()\`\n\n2. **Unvalidated redirect URL** — \`MEDIUM\`\n - Rule: \`javascript.express.security.open-redirect\`\n - File: \`api-gateway/src/middleware/auth.ts:56\`\n - Remediation: Validate redirect URLs against an allowlist of trusted domains\n\nWould you like a full breakdown by repository, or should I prioritize remediation for a specific category?`, + + 'Trivy Container Scan': `**Trivy Container Scan — HIGH Findings**\n\nTotal: ${finding.count} findings across 3 images\nLast run: ${finding.lastRun}\n\n**Image: \`ghcr.io/shipsecai/api-gateway:latest\`** (4 findings)\n\n1. **CVE-2024-38816 — Spring Framework path traversal** — \`HIGH\` (CVSS 8.1)\n - Package: \`org.springframework:spring-webmvc 6.1.6\`\n - Fixed in: 6.1.13\n - Remediation: Update Spring Boot parent to >=3.3.4\n\n2. **CVE-2024-47554 — Apache Commons IO ReDoS** — \`HIGH\` (CVSS 7.5)\n - Package: \`commons-io:commons-io 2.11.0\`\n - Fixed in: 2.14.0\n\n**Image: \`ghcr.io/shipsecai/frontend-app:latest\`** (2 findings)\n\n3. **CVE-2024-21538 — cross-spawn ReDoS** — \`HIGH\` (CVSS 7.5)\n - Package: \`cross-spawn 7.0.3\`\n - Fixed in: 7.0.5\n\n**Image: \`ghcr.io/shipsecai/auth-service:latest\`** (1 finding)\n\n4. **GHSA-72xf-g2v4-qvf3 — undici request smuggling** — \`HIGH\` (CVSS 7.5)\n - Package: \`undici 5.28.3\`\n - Fixed in: 5.28.4\n\nWould you like me to update the Dockerfiles with patched base images?`, + + 'Checkov IaC Analysis': `**Checkov IaC Analysis — LOW Findings**\n\nTotal: ${finding.count} findings across 12 Terraform files\nLast run: ${finding.lastRun}\nFramework: Terraform v1.7.x\n\n**Findings by Category:**\n\n| Category | Count | Severity |\n|---|---|---|\n| Missing resource tagging | 18 | Low |\n| Logging not enabled | 12 | Low |\n| Encryption at rest defaults | 8 | Low |\n| Backup not configured | 7 | Low |\n\n**Sample Findings:**\n\n1. **S3 bucket missing versioning** — \`LOW\`\n - Check: CKV_AWS_21\n - File: \`infra/modules/storage/main.tf:34\`\n - Remediation: Add \`versioning { enabled = true }\` block\n\n2. **CloudWatch log group missing retention policy** — \`LOW\`\n - Check: CKV_AWS_158\n - File: \`infra/modules/monitoring/main.tf:12\`\n - Remediation: Set \`retention_in_days = 90\`\n\n3. **EC2 instance missing detailed monitoring** — \`LOW\`\n - Check: CKV_AWS_126\n - File: \`infra/environments/staging/main.tf:67\`\n - Remediation: Add \`monitoring = true\`\n\nThese are low-severity hygiene items. Would you like me to generate a Terraform PR to fix all tagging issues at once?`, + }; + + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + thinkingItem, + { + type: 'text', + content: + findingDetails[finding.source] || + `**${finding.source} — ${finding.severity.toUpperCase()} Findings**\n\nTotal: ${finding.count} findings\nLast run: ${finding.lastRun}\n\nNo detailed breakdown available for this source. Would you like me to fetch the raw findings data?`, + }, + ], + }; + } + return updated; + }); + }, 2000); + }, + [addRichMessage], + ); + + const handleQuickActionClick = useCallback( + (action: QuickActionOption) => { + addRichMessage({ + role: 'user', + content: action.name, + }); + + simulateStreamingResponse([ + { + type: 'text', + content: `Executing **${action.name}**...\n\n${action.description}`, + }, + { + type: 'loading', + content: 'Processing...', + }, + ]); + + // Preserve thinking from simulateStreamingResponse + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + const prevContent = updated[lastIdx].content as MessageContentType[]; + const thinkingItems = prevContent.filter((c) => c.type === 'thinking'); + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + ...thinkingItems, + { + type: 'text', + content: `**${action.name}** completed!\n\n${ + action.id === 'qa1' + ? 'Your security report has been generated and is available in the Reports section. Key highlights:\n- Overall security score: 78/100\n- 3 critical issues resolved this week\n- 12 new findings require attention' + : action.id === 'qa2' + ? 'Compliance check completed:\n\n- SOC2: 94% compliant (2 controls pending)\n- HIPAA: 89% compliant (review data retention)\n- PCI-DSS: 97% compliant' + : action.id === 'qa3' + ? 'Alert settings updated. You will receive notifications for:\n- Critical findings: Immediately\n- High findings: Within 1 hour\n- Medium/Low: Daily digest' + : 'Full security suite initiated. Running:\n- SAST analysis\n- Dependency audit\n- Secret scanning\n- Container analysis\n\nEstimated completion: 15-20 minutes' + }`, + }, + ], + }; + } + return updated; + }); + }, 4000); + }, + [addRichMessage, simulateStreamingResponse], + ); + + // Handler: "Investigate alert" welcome screen button + const handleInvestigateAlert = useCallback(() => { + addRichMessage({ + role: 'user', + content: 'Investigate alert', + }); + + const thinkingItem: MessageContentType = { + type: 'thinking', + content: 'Connecting to AWS GuardDuty and fetching recent alerts...', + }; + + addRichMessage({ + role: 'assistant', + content: [thinkingItem], + }); + + // Thinking stays active for the full duration, then collapses when content appears + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + thinkingItem, + { + type: 'text', + content: + 'I found **3 recent security alerts** in AWS GuardDuty across your monitored accounts:', + }, + { type: 'guardduty-alerts', alerts: mockGuardDutyAlerts }, + { + type: 'text', + content: + 'Would you like me to investigate any of these alerts? Just describe which one interests you.', + }, + ], + }; + } + return updated; + }); + }, 2000); + }, [addRichMessage]); + + // Multi-step investigation flow + const runInvestigationFlow = useCallback(() => { + addRichMessage({ + role: 'assistant', + content: [ + { type: 'thinking', content: 'Analyzing your request and pulling alert details...' }, + ], + }); + + const updateLastAssistant = (content: MessageContentType[]) => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + updated[lastIdx] = { ...updated[lastIdx], content }; + } + return updated; + }); + }; + + // Investigation content blocks (accumulated step by step) + const alertHeader: MessageContentType = { + type: 'text', + content: + '🚨 **Incident Investigation Summary - ShipSec**\n\n🔗 **GuardDuty Alert: Unusual SSH Login Detected**\n\n**Summary:** Suspicious SSH login detected from an unauthorized IP **189.45.23.18** on EC2 instance **i-07abc123d456ef789**.\n\n---\n\n🕵️ **Investigation Timeline** *(Auto-correlated across AWS, GSuite, Rippling)*', + }; + + const guardDutyStep: MessageContentType = { + type: 'text', + content: `**1. AWS GuardDuty Alert Triggered**\n\n **Time:** **${ALERT_DATE_PRIMARY}**\n **Instance:** **i-07abc123d456ef789**\n **Source IP:** **189.45.23.18** *(GeoIP: São Paulo, Brazil)*\n **SSH User:** **ec2-user**`, + }; + + const cloudTrailStep: MessageContentType = { + type: 'text', + content: + '**2. AWS CloudTrail Log Review**\n\n ✅ Verified SSH access from the IP at the reported time\n 🔘 Session initiated by IAM role **DevOpsAccessRole**', + }; + + const ripplingStep: MessageContentType = { + type: 'text', + content: `**3. Rippling (HRMS) Context**\n\n 🎯 IAM role **DevOpsAccessRole** maps to user: **Pranjal Paliwal**\n 🏖️ Pranjal is marked **Out of Office (PTO)** from **${PTO_START}** to **${PTO_END}**\n ❗ Login occurred during PTO`, + }; + + const gsuiteStep: MessageContentType = { + type: 'text', + content: + '**4. GSuite Activity Check**\n\n 📁 No recent file access from Pranjal in last 72 hrs\n ✉️ No login to Gmail or Drive from Brazil IP', + }; + + const conclusion: MessageContentType = { + type: 'text', + content: + '---\n\n💡 **Conclusion:**\n\nThe SSH login from **189.45.23.18** appears **unauthorized**. IAM role belongs to a user on PTO with no matching activity in GSuite.', + }; + + const recommendedAction: MessageContentType = { + type: 'text', + content: + '🚨 **Recommended Action:**\n\n 🔐 Temporarily revoke **DevOpsAccessRole** credentials\n 👤 Notify user: *"Hi Pranjal, was this you?"*\n 📋 Initiate IR protocol with SecOps team', + }; + + const actionButtons: MessageContentType = { + type: 'action-buttons', + buttons: [ + { id: 'confirm-pranjal', label: 'Confirm with Pranjal', emoji: '✅', variant: 'primary' }, + { + id: 'revoke-role', + label: 'Temporarily revoke DevOpsAcc...', + emoji: '🚫', + variant: 'destructive', + }, + ], + }; + + // Step 0: Show alert header + thinking for GuardDuty (1.2s) + setTimeout(() => { + updateLastAssistant([ + alertHeader, + { type: 'thinking', content: 'Fetching full alert details from AWS GuardDuty via MCP...' }, + ]); + }, 1200); + + // Step 1: GuardDuty details + thinking for CloudTrail (2.7s) + setTimeout(() => { + updateLastAssistant([ + alertHeader, + guardDutyStep, + { type: 'thinking', content: 'Cross-referencing with AWS CloudTrail logs via MCP...' }, + ]); + }, 2700); + + // Step 2: CloudTrail + thinking for Rippling (4.2s) + setTimeout(() => { + updateLastAssistant([ + alertHeader, + guardDutyStep, + cloudTrailStep, + { + type: 'thinking', + content: 'Querying Rippling HRMS via MCP to identify user behind IAM role...', + }, + ]); + }, 4200); + + // Step 3: Rippling + thinking for GSuite (5.7s) + setTimeout(() => { + updateLastAssistant([ + alertHeader, + guardDutyStep, + cloudTrailStep, + ripplingStep, + { + type: 'thinking', + content: 'Checking GSuite activity via MCP for corroborating evidence...', + }, + ]); + }, 5700); + + // Step 4: GSuite + thinking for conclusion (7.2s) + setTimeout(() => { + updateLastAssistant([ + alertHeader, + guardDutyStep, + cloudTrailStep, + ripplingStep, + gsuiteStep, + { type: 'thinking', content: 'Correlating findings and generating conclusion...' }, + ]); + }, 7200); + + // Step 5: Full result with conclusion, recommended action, and buttons (8.5s) + setTimeout(() => { + updateLastAssistant([ + { + type: 'thinking', + content: 'Investigated alert across GuardDuty, CloudTrail, Rippling, and GSuite', + }, + alertHeader, + guardDutyStep, + cloudTrailStep, + ripplingStep, + gsuiteStep, + conclusion, + recommendedAction, + actionButtons, + ]); + }, 8500); + }, [addRichMessage]); + + // Handler: GuardDuty alert card click + const handleAlertClick = useCallback( + (_alert: GuardDutyAlert) => { + runInvestigationFlow(); + }, + [runInvestigationFlow], + ); + + // Handler: action button clicks (Confirm with Pranjal / Revoke role) + const handleActionButtonClick = useCallback( + (button: ActionButton) => { + if (button.id === 'confirm-pranjal') { + addRichMessage({ + role: 'user', + content: 'Confirm with Pranjal', + }); + + const thinkingItem: MessageContentType = { + type: 'thinking', + content: 'Sending Slack notification to Pranjal Paliwal via MCP...', + }; + + addRichMessage({ + role: 'assistant', + content: [thinkingItem], + }); + + // Thinking stays active, then collapses when content appears + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + thinkingItem, + { + type: 'text', + content: `✅ **Slack notification sent to Pranjal Paliwal**\n\nUsing **Slack MCP** to send a direct message.\n\n---\n\n**Direct message sent to:** Pranjal Paliwal\n\n**Message preview:**\n> 🔔 **Security Alert — Action Required**\n>\n> Hi Pranjal, we detected an SSH login to EC2 instance **i-07abc123d456ef789** from IP **189.45.23.18** (São Paulo, Brazil) at **${ALERT_DATE_PRIMARY}** using your IAM role **DevOpsAccessRole**.\n>\n> Our records show you are currently on PTO. **Was this you?**\n>\n> Please reply with ✅ if this was authorized or 🚫 if this was not you.\n\n---\n\nI'll monitor for Pranjal's response and update you. If no response within 30 minutes, I'll automatically escalate to the SecOps team.`, + }, + ], + }; + } + return updated; + }); + }, 2000); + } else if (button.id === 'revoke-role') { + addRichMessage({ + role: 'user', + content: 'Temporarily revoke DevOpsAccessRole', + }); + + const thinkingItem: MessageContentType = { + type: 'thinking', + content: 'Revoking DevOpsAccessRole credentials via AWS IAM MCP...', + }; + + addRichMessage({ + role: 'assistant', + content: [thinkingItem], + }); + + // Thinking stays active, then collapses when content appears + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + thinkingItem, + { + type: 'text', + content: `🚫 **DevOpsAccessRole temporarily revoked**\n\nUsing **AWS IAM MCP** to attach a deny-all policy and invalidate active sessions.\n\n---\n\n| Action | Status |\n|---|---|\n| **Inline policy attached** | ✅ DenyAll policy added to DevOpsAccessRole |\n| **Active sessions invalidated** | ✅ All sessions older than now revoked |\n| **CloudTrail logging** | ✅ Enhanced logging enabled for this role |\n| **Rollback window** | 24 hours (auto-restore if confirmed safe) |\n\n**Role ARN:** arn:aws:iam::491203847561:role/DevOpsAccessRole\n**Policy:** ShipSec-EmergencyDenyAll-${TODAY_COMPACT}\n\n---\n\nThe role is now locked down. Any services using this role will lose access. I've also notified the SecOps team in **#incident-response** about this containment action.\n\nWould you like me to initiate the full IR protocol or wait for Pranjal's confirmation first?`, + }, + ], + }; + } + return updated; + }); + }, 2500); + } + }, + [addRichMessage], + ); + + const handleSend = () => { + if (!inputValue.trim()) return; + + const userMessage = inputValue.trim(); + const lowerMessage = userMessage.toLowerCase(); + + addRichMessage({ + role: 'user', + content: userMessage, + }); + + // Handle different inputs + if (lowerMessage.includes('run workflow') || lowerMessage.includes('workflow')) { + setInputValue(''); + simulateStreamingResponse([ + { + type: 'text', + content: + 'I found 5 active workflows in your ShipSec Studio. Here are the available security workflows you can run:', + }, + { type: 'workflow-buttons', workflows: mockWorkflows }, + { + type: 'text', + content: 'Click on any workflow to start it, or tell me which one you want to configure.', + }, + ]); + return; + } + + if (lowerMessage.includes('scan') || lowerMessage.includes('repository')) { + setInputValue(''); + simulateStreamingResponse([ + { + type: 'text', + content: + 'You have 200 repositories connected to ShipSec Studio. Here are your most recently scanned ones:', + }, + { + type: 'repo-buttons', + repos: mockRepos, + intro: 'Select a repository to scan:', + }, + ]); + return; + } + + if ( + lowerMessage.includes('investigate') || + lowerMessage.includes('alert') || + lowerMessage.includes('ssh') || + lowerMessage.includes('login') + ) { + setInputValue(''); + runInvestigationFlow(); + return; + } + + if ( + lowerMessage.includes('finding') || + lowerMessage.includes('review') || + lowerMessage.includes('aws') + ) { + setInputValue(''); + simulateStreamingResponse([ + { + type: 'text', + content: + "I've retrieved your latest security findings from across all integrated sources. Here's a summary of the last 5 security reports:", + }, + { type: 'finding-cards', findings: mockFindings }, + { + type: 'text', + content: + 'You have 3 critical findings from AWS Security Hub that require immediate attention. Would you like me to provide detailed remediation steps for those?', + }, + ]); + return; + } + + if ( + lowerMessage.includes('jira') || + lowerMessage.includes('ticket') || + lowerMessage.includes('create ticket') + ) { + setInputValue(''); + + const thinkingItem: MessageContentType = { + type: 'thinking', + content: 'Connecting to JIRA MCP via ShipSec secure proxy and creating ticket...', + }; + + addRichMessage({ + role: 'assistant', + content: [thinkingItem], + }); + + // Thinking stays active, then collapses when final content appears + setTimeout(() => { + setRichMessages((prev) => { + const updated = [...prev]; + const lastIdx = updated.length - 1; + if (updated[lastIdx]?.role === 'assistant') { + updated[lastIdx] = { + ...updated[lastIdx], + content: [ + thinkingItem, + { + type: 'text', + content: `I've connected to the **JIRA MCP server** via the ShipSec secure HTTP proxy and created a ticket for all 3 critical AWS Security Hub findings. The ticket has been assigned to **Pranjal** as he's the DevOps Lead.\n\n---\n\n**[SD-47](https://shipsec.atlassian.net/browse/SD-47)** — \`Critical AWS Security Hub Findings — Immediate Remediation Required\`\n\n| Field | Value |\n|---|---|\n| **Project** | ShipSec DevOps (SD) |\n| **Type** | Bug |\n| **Priority** | 🔴 Critical |\n| **Assignee** | Pranjal (DevOps Lead) |\n| **Labels** | \`aws\`, \`security-hub\`, \`critical\`, \`production\` |\n| **Due Date** | Feb 7, 2025 |\n\n**Description includes:**\n1. **S3 Bucket Public Access Enabled** — \`prod-user-uploads\` bucket has public read access\n2. **IAM Root Account Access Key Active** — Root access keys exist on production account \n3. **RDS Instance Publicly Accessible** — \`prod-postgres\` is publicly accessible\n\n**Acceptance Criteria:**\n- [ ] S3 Block Public Access enabled on \`prod-user-uploads\`\n- [ ] Root access keys deleted and IAM admin user created with MFA\n- [ ] RDS instance moved to private subnet with public access disabled\n\n🔗 **Ticket URL:** [https://shipsec.atlassian.net/browse/SD-47](https://shipsec.atlassian.net/browse/SD-47)\n\n---\n\nPranjal has been notified via Slack (\`#devops-alerts\`) and email. Would you like me to add any watchers, link this to an existing epic, or escalate the priority?`, + }, + ], + }; + } + return updated; + }); + }, 3000); + return; + } + + // Default response + setInputValue(''); + simulateStreamingResponse([ + { + type: 'text', + content: `I understand you want help with "${userMessage}". As your security assistant, I can help you with:\n\n• Running security workflows\n• Scanning repositories for vulnerabilities\n• Reviewing security findings\n• Performing quick security actions\n\nWhat would you like to do?`, + }, + ]); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSend(); + } + }; + + const handleSuggestedAction = (action: string) => { + const lowerAction = action.toLowerCase(); + + if (lowerAction === 'run workflow') { + handleRunWorkflow(); + } else if (lowerAction === 'scan repository') { + handleScanRepository(); + } else if (lowerAction === 'review findings') { + handleReviewFindings(); + } else if (lowerAction === 'investigate alert') { + handleInvestigateAlert(); + } else { + setInputValue(action); + textareaRef.current?.focus(); + } + }; + + return ( +
+ {/* Header with sidebar toggle and status bar */} +
+ {/* Sidebar toggle button - positioned at the start */} + {!isMobile && ( + + )} + {/* Status indicators */} + +
+ + {/* Messages area */} +
+ {richMessages.length === 0 ? ( + + ) : ( +
+ {richMessages.map((message) => ( + + ))} +
+
+ )} +
+ + {/* Input area */} +
+
+
+