From 4cdf01ba9e1b02ac19e9bd9cded52f63add18562 Mon Sep 17 00:00:00 2001 From: JAE Date: Wed, 25 Mar 2026 23:51:40 +0000 Subject: [PATCH] feat: add comprehensive web UI features - command palette, memory manager, cost tracker, diff viewer, mermaid diagrams, keyboard shortcuts, session export --- package-lock.json | 1144 ++++++++++++++++- packages/web-ui/example/package.json | 6 +- .../example/src/components/command-palette.ts | 120 ++ .../example/src/components/cost-tracker.ts | 97 ++ .../src/components/keyboard-shortcuts.ts | 60 + .../example/src/components/memory-manager.ts | 148 +++ .../example/src/components/session-export.ts | 45 + packages/web-ui/example/src/main.ts | 632 +++++---- packages/web-ui/src/index.ts | 7 + packages/web-ui/src/tools/diff-viewer.ts | 98 ++ packages/web-ui/src/tools/memory-tool.ts | 164 +++ packages/web-ui/src/tools/mermaid-diagram.ts | 103 ++ .../src/tools/renderers/DiffRenderer.ts | 47 + .../src/tools/renderers/MermaidRenderer.ts | 53 + 14 files changed, 2392 insertions(+), 332 deletions(-) create mode 100644 packages/web-ui/example/src/components/command-palette.ts create mode 100644 packages/web-ui/example/src/components/cost-tracker.ts create mode 100644 packages/web-ui/example/src/components/keyboard-shortcuts.ts create mode 100644 packages/web-ui/example/src/components/memory-manager.ts create mode 100644 packages/web-ui/example/src/components/session-export.ts create mode 100644 packages/web-ui/src/tools/diff-viewer.ts create mode 100644 packages/web-ui/src/tools/memory-tool.ts create mode 100644 packages/web-ui/src/tools/mermaid-diagram.ts create mode 100644 packages/web-ui/src/tools/renderers/DiffRenderer.ts create mode 100644 packages/web-ui/src/tools/renderers/MermaidRenderer.ts diff --git a/package-lock.json b/package-lock.json index af6b4cf..9f99d2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,26 @@ "node": ">=20.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/install-pkg/node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "engines": { + "node": ">=18" + } + }, "node_modules/@anthropic-ai/sandbox-runtime": { "version": "0.0.16", "resolved": "https://registry.npmjs.org/@anthropic-ai/sandbox-runtime/-/sandbox-runtime-0.0.16.tgz", @@ -960,6 +980,45 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.1.2.tgz", + "integrity": "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q==", + "dependencies": { + "@chevrotain/gast": "11.1.2", + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.1.2.tgz", + "integrity": "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g==", + "dependencies": { + "@chevrotain/types": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.1.2.tgz", + "integrity": "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw==" + }, + "node_modules/@chevrotain/types": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.1.2.tgz", + "integrity": "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw==" + }, + "node_modules/@chevrotain/utils": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.1.2.tgz", + "integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.4", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", @@ -1399,6 +1458,21 @@ } } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, "node_modules/@jaeswift/jae": { "resolved": "packages/pods", "link": true @@ -1780,6 +1854,14 @@ "node": ">= 20" } }, + "node_modules/@mermaid-js/parser": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-1.0.1.tgz", + "integrity": "sha512-opmV19kN1JsK0T6HhhokHpcVkqKpF+x2pPDKKM2ThHtZAB5F4PROopk0amuVYK5qMrIA4erzpNm8gmPNJgMDxQ==", + "dependencies": { + "langium": "^4.0.0" + } + }, "node_modules/@mistralai/mistralai": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@mistralai/mistralai/-/mistralai-1.14.1.tgz", @@ -2416,6 +2498,17 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/@profoundlogic/hogan": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@profoundlogic/hogan/-/hogan-3.0.4.tgz", + "integrity": "sha512-pmNVGuooS30Mm7YbZd5T7E5zYVO6D5Ct91sn4T39mUvMUc3sCGridcnhAufL1/Bz2QzAtzEn0agNrdk3+5yWzw==", + "dependencies": { + "nopt": "1.0.10" + }, + "bin": { + "hulk": "bin/hulk" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3849,6 +3942,228 @@ "assertion-error": "^2.0.1" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -3869,6 +4184,11 @@ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", "license": "MIT" }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==" + }, "node_modules/@types/hosted-git-info": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@types/hosted-git-info/-/hosted-git-info-3.0.5.tgz", @@ -4071,6 +4391,15 @@ "win32" ] }, + "node_modules/@upsetjs/venn.js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", + "integrity": "sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==", + "optionalDependencies": { + "d3-selection": "^3.0.0", + "d3-transition": "^3.0.1" + } + }, "node_modules/@vitest/expect": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.4.tgz", @@ -4209,6 +4538,22 @@ "dev": true, "license": "MIT" }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -4536,6 +4881,30 @@ "node": ">= 16" } }, + "node_modules/chevrotain": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", + "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.1.2", + "@chevrotain/gast": "11.1.2", + "@chevrotain/regexp-to-ast": "11.1.2", + "@chevrotain/types": "11.1.2", + "@chevrotain/utils": "11.1.2", + "lodash-es": "4.17.23" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, "node_modules/chownr": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", @@ -4806,12 +5175,25 @@ "node": ">=12" } }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, + "node_modules/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/croner": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/croner/-/croner-9.1.0.tgz", @@ -4848,6 +5230,471 @@ "semver": "bin/semver" } }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dagre-d3-es": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.14.tgz", + "integrity": "sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -4857,6 +5704,11 @@ "node": ">= 12" } }, + "node_modules/dayjs": { + "version": "1.11.20", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.20.tgz", + "integrity": "sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==" + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -4924,6 +5776,14 @@ "node": ">= 14" } }, + "node_modules/delaunator": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.1.0.tgz", + "integrity": "sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4951,6 +5811,30 @@ "node": ">=0.3.1" } }, + "node_modules/diff2html": { + "version": "3.4.56", + "resolved": "https://registry.npmjs.org/diff2html/-/diff2html-3.4.56.tgz", + "integrity": "sha512-u9gfn+BlbHcyO7vItCIC4z49LJDUt31tODzOfAuJ5R1E7IdlRL6KjugcB9zOpejD+XiR+dDZbsnHSQ3g6A/u8A==", + "dependencies": { + "@profoundlogic/hogan": "^3.0.4", + "diff": "^8.0.3" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "highlight.js": "11.11.1" + } + }, + "node_modules/diff2html/node_modules/highlight.js": { + "version": "11.11.1", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz", + "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/docx-preview": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.7.tgz", @@ -4960,6 +5844,14 @@ "jszip": ">=3.0.0" } }, + "node_modules/dompurify": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", + "integrity": "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -5695,6 +6587,11 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5812,6 +6709,17 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -5860,6 +6768,14 @@ "dev": true, "license": "ISC" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -6113,6 +7029,11 @@ "node": ">= 12" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, "node_modules/koffi": { "version": "2.15.2", "resolved": "https://registry.npmjs.org/koffi/-/koffi-2.15.2.tgz", @@ -6124,6 +7045,27 @@ "url": "https://liberapay.com/Koromix" } }, + "node_modules/langium": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-4.2.1.tgz", + "integrity": "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ==", + "dependencies": { + "chevrotain": "~11.1.1", + "chevrotain-allstar": "~0.3.1", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.1.0" + }, + "engines": { + "node": ">=20.10.0", + "npm": ">=10.2.3" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==" + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -6487,6 +7429,45 @@ "node": ">= 8" } }, + "node_modules/mermaid": { + "version": "11.13.0", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.13.0.tgz", + "integrity": "sha512-fEnci+Immw6lKMFI8sqzjlATTyjLkRa6axrEgLV2yHTfv8r+h1wjFbV6xeRtd4rUV1cS4EpR9rwp3Rci7TRWDw==", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.2", + "@mermaid-js/parser": "^1.0.1", + "@types/d3": "^7.4.3", + "@upsetjs/venn.js": "^2.0.0", + "cytoscape": "^3.33.1", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.14", + "dayjs": "^1.11.19", + "dompurify": "^3.3.1", + "katex": "^0.16.25", + "khroma": "^2.1.0", + "lodash-es": "^4.17.23", + "marked": "^16.3.0", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/mermaid/node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -6576,6 +7557,17 @@ "dev": true, "license": "MIT" }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -6702,6 +7694,20 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/nopt": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", + "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -6851,6 +7857,11 @@ "node": ">= 14" } }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -6884,6 +7895,11 @@ "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", "license": "MIT" }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==" + }, "node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -6921,7 +7937,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, "license": "MIT" }, "node_modules/pathval": { @@ -6987,6 +8002,30 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", @@ -7270,6 +8309,11 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.3.tgz", + "integrity": "sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==" + }, "node_modules/rollup": { "version": "4.60.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.0.tgz", @@ -7314,6 +8358,17 @@ "fsevents": "~2.3.2" } }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -7338,6 +8393,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -7368,6 +8428,11 @@ ], "license": "MIT" }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -7709,6 +8774,11 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==" + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -7973,6 +9043,14 @@ "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", "license": "MIT" }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "engines": { + "node": ">=6.10" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -8026,6 +9104,11 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==" + }, "node_modules/uhtml": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/uhtml/-/uhtml-5.0.9.tgz", @@ -8068,6 +9151,18 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", @@ -8280,6 +9375,49 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==" + }, + "node_modules/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==" + }, "node_modules/web-streams-polyfill": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", @@ -8812,8 +9950,10 @@ "@jaeswift/jae-web-ui": "file:../", "@mariozechner/mini-lit": "^0.2.0", "@tailwindcss/vite": "^4.1.17", + "diff2html": "^3.4.56", "lit": "^3.3.1", - "lucide": "^0.544.0" + "lucide": "^0.544.0", + "mermaid": "^11.13.0" }, "devDependencies": { "typescript": "^5.7.3", diff --git a/packages/web-ui/example/package.json b/packages/web-ui/example/package.json index 755b653..30fdaca 100644 --- a/packages/web-ui/example/package.json +++ b/packages/web-ui/example/package.json @@ -11,12 +11,14 @@ "clean": "shx rm -rf dist" }, "dependencies": { - "@mariozechner/mini-lit": "^0.2.0", "@jaeswift/jae-ai": "file:../../ai", "@jaeswift/jae-web-ui": "file:../", + "@mariozechner/mini-lit": "^0.2.0", "@tailwindcss/vite": "^4.1.17", + "diff2html": "^3.4.56", "lit": "^3.3.1", - "lucide": "^0.544.0" + "lucide": "^0.544.0", + "mermaid": "^11.13.0" }, "devDependencies": { "typescript": "^5.7.3", diff --git a/packages/web-ui/example/src/components/command-palette.ts b/packages/web-ui/example/src/components/command-palette.ts new file mode 100644 index 0000000..873266e --- /dev/null +++ b/packages/web-ui/example/src/components/command-palette.ts @@ -0,0 +1,120 @@ + +import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +export interface Command { + id: string; + label: string; + description?: string; + icon?: string; + shortcut?: string; + action: () => void; + keywords?: string[]; +} + +@customElement("command-palette") +export class CommandPalette extends LitElement { + @state() private open = false; + @state() private query = ""; + @state() private selectedIndex = 0; + + private commands: Command[] = []; + + protected override createRenderRoot() { return this; } + + setCommands(commands: Command[]) { + this.commands = commands; + } + + show() { + this.open = true; + this.query = ""; + this.selectedIndex = 0; + this.requestUpdate(); + requestAnimationFrame(() => { + const input = this.querySelector("input") as HTMLInputElement; + if (input) input.focus(); + }); + } + + hide() { + this.open = false; + this.requestUpdate(); + } + + get filteredCommands(): Command[] { + if (!this.query) return this.commands; + const q = this.query.toLowerCase(); + return this.commands.filter(c => + c.label.toLowerCase().includes(q) || + c.description?.toLowerCase().includes(q) || + c.keywords?.some(k => k.toLowerCase().includes(q)) + ); + } + + private handleKeyDown(e: KeyboardEvent) { + const cmds = this.filteredCommands; + if (e.key === "ArrowDown") { + e.preventDefault(); + this.selectedIndex = Math.min(this.selectedIndex + 1, cmds.length - 1); + } else if (e.key === "ArrowUp") { + e.preventDefault(); + this.selectedIndex = Math.max(this.selectedIndex - 1, 0); + } else if (e.key === "Enter") { + e.preventDefault(); + if (cmds[this.selectedIndex]) { + cmds[this.selectedIndex].action(); + this.hide(); + } + } else if (e.key === "Escape") { + this.hide(); + } + } + + override render() { + if (!this.open) return html``; + const cmds = this.filteredCommands; + + return html` +
{ if (e.target === e.currentTarget) this.hide(); }}> +
+
+ + { this.query = (e.target as HTMLInputElement).value; this.selectedIndex = 0; }} + @keydown=${this.handleKeyDown} + /> + ESC +
+
+ ${cmds.length === 0 ? html`
No commands found
` : ""} + ${cmds.map((cmd, i) => html` + + `)} +
+
+ ↑↓ Navigate + ↵ Select + ESC Close +
+
+
+ `; + } +} diff --git a/packages/web-ui/example/src/components/cost-tracker.ts b/packages/web-ui/example/src/components/cost-tracker.ts new file mode 100644 index 0000000..552179f --- /dev/null +++ b/packages/web-ui/example/src/components/cost-tracker.ts @@ -0,0 +1,97 @@ + +import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; +import type { Agent } from "@jaeswift/jae-agent-core"; + +export interface UsageSnapshot { + inputTokens: number; + outputTokens: number; + totalTokens: number; + estimatedCost: number; + model: string; + requestCount: number; +} + +// Very rough cost estimates per 1M tokens for common models +const MODEL_COSTS: Record = { + default: { input: 3.0, output: 15.0 }, +}; + +function estimateCost(model: string, input: number, output: number): number { + const costs = MODEL_COSTS[model] || MODEL_COSTS.default; + return (input / 1_000_000) * costs.input + (output / 1_000_000) * costs.output; +} + +@customElement("cost-tracker") +export class CostTracker extends LitElement { + @state() private inputTokens = 0; + @state() private outputTokens = 0; + @state() private requestCount = 0; + @state() private modelId = ""; + @state() private expanded = false; + + private unsubscribe?: () => void; + + protected override createRenderRoot() { return this; } + + bindAgent(agent: Agent) { + if (this.unsubscribe) this.unsubscribe(); + this.inputTokens = 0; + this.outputTokens = 0; + this.requestCount = 0; + this.modelId = agent.state.model?.id || ""; + this.unsubscribe = agent.subscribe((event) => { + if (event.type === "message" && event.message.role === "assistant") { + const msg = event.message as any; + if (msg.usage) { + this.inputTokens += msg.usage.inputTokens || 0; + this.outputTokens += msg.usage.outputTokens || 0; + this.requestCount += 1; + } + } + }); + } + + get totalTokens() { return this.inputTokens + this.outputTokens; } + get estimatedCost() { return estimateCost(this.modelId, this.inputTokens, this.outputTokens); } + + reset() { + this.inputTokens = 0; + this.outputTokens = 0; + this.requestCount = 0; + } + + override render() { + const cost = this.estimatedCost; + return html` + + ${this.expanded ? html` +
+
+ Token Usage + +
+
+
Input tokens${this.inputTokens.toLocaleString()}
+
Output tokens${this.outputTokens.toLocaleString()}
+
Total tokens${this.totalTokens.toLocaleString()}
+
Est. cost$${cost.toFixed(6)}
+
Requests${this.requestCount}
+
+
+ ` : ""} + `; + } +} + +export function createCostTracker(): CostTracker { + return document.createElement("cost-tracker") as CostTracker; +} diff --git a/packages/web-ui/example/src/components/keyboard-shortcuts.ts b/packages/web-ui/example/src/components/keyboard-shortcuts.ts new file mode 100644 index 0000000..39cc37c --- /dev/null +++ b/packages/web-ui/example/src/components/keyboard-shortcuts.ts @@ -0,0 +1,60 @@ + +import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +@customElement("keyboard-shortcuts") +export class KeyboardShortcuts extends LitElement { + @state() private open = false; + + protected override createRenderRoot() { return this; } + + show() { this.open = true; this.requestUpdate(); } + hide() { this.open = false; this.requestUpdate(); } + toggle() { this.open = !this.open; this.requestUpdate(); } + + private readonly shortcuts = [ + { group: "General", items: [ + { key: "Cmd+K", desc: "Open command palette" }, + { key: "?", desc: "Show keyboard shortcuts" }, + { key: "Ctrl+L", desc: "Open model selector" }, + { key: "Esc", desc: "Close dialogs / abort generation" }, + ]}, + { group: "Sessions", items: [ + { key: "Ctrl+N", desc: "New session" }, + { key: "Ctrl+H", desc: "Session history" }, + { key: "Ctrl+E", desc: "Export session" }, + ]}, + { group: "Tools & Features", items: [ + { key: "/memory", desc: "Open memory manager" }, + { key: "/clear", desc: "Clear conversation" }, + { key: "/model", desc: "Switch model" }, + ]}, + ]; + + override render() { + if (!this.open) return html``; + return html` +
{ if (e.target === e.currentTarget) this.hide(); }}> +
+
+

Keyboard Shortcuts

+ +
+ ${this.shortcuts.map(group => html` +
+
${group.group}
+
+ ${group.items.map(item => html` +
+ ${item.desc} + ${item.key} +
+ `)} +
+
+ `)} +
+
+ `; + } +} diff --git a/packages/web-ui/example/src/components/memory-manager.ts b/packages/web-ui/example/src/components/memory-manager.ts new file mode 100644 index 0000000..2e04b7f --- /dev/null +++ b/packages/web-ui/example/src/components/memory-manager.ts @@ -0,0 +1,148 @@ + +import { html, LitElement } from "lit"; +import { customElement, state } from "lit/decorators.js"; + +export interface MemoryEntry { + id: string; + content: string; + tags: string[]; + timestamp: string; +} + +const DB_NAME = "jae-memory"; +const DB_VERSION = 1; +const STORE_NAME = "memories"; +let _db: IDBDatabase | null = null; + +async function openDB(): Promise { + if (_db) return _db; + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, DB_VERSION); + req.onupgradeneeded = () => req.result.createObjectStore(STORE_NAME, { keyPath: "id" }); + req.onsuccess = () => { _db = req.result; resolve(_db); }; + req.onerror = () => reject(req.error); + }); +} +export async function memoryLoad(): Promise { + const db = await openDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readonly"); + const req = tx.objectStore(STORE_NAME).getAll(); + req.onsuccess = () => resolve(req.result || []); + req.onerror = () => reject(req.error); + }); +} +export async function memorySave(content: string, tags: string[] = []): Promise { + const db = await openDB(); + const entry: MemoryEntry = { id: crypto.randomUUID(), content, tags, timestamp: new Date().toISOString() }; + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + tx.objectStore(STORE_NAME).put(entry); + tx.oncomplete = () => resolve(entry.id); + tx.onerror = () => reject(tx.error); + }); +} +export async function memoryDelete(id: string): Promise { + const db = await openDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + tx.objectStore(STORE_NAME).delete(id); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); +} + +@customElement("memory-manager") +export class MemoryManager extends LitElement { + @state() private open = false; + @state() private entries: MemoryEntry[] = []; + @state() private loading = false; + @state() private newContent = ""; + @state() private newTags = ""; + @state() private filter = ""; + + protected override createRenderRoot() { return this; } + + async show() { + this.open = true; + this.loading = true; + this.requestUpdate(); + this.entries = await memoryLoad(); + this.loading = false; + this.requestUpdate(); + } + hide() { this.open = false; this.requestUpdate(); } + + get filtered() { + if (!this.filter) return this.entries; + const q = this.filter.toLowerCase(); + return this.entries.filter(e => e.content.toLowerCase().includes(q) || e.tags.some(t => t.toLowerCase().includes(q))); + } + + async deleteEntry(id: string) { + await memoryDelete(id); + this.entries = this.entries.filter(e => e.id !== id); + this.requestUpdate(); + } + + async addEntry() { + if (!this.newContent.trim()) return; + const tags = this.newTags.split(",").map(t => t.trim()).filter(Boolean); + await memorySave(this.newContent.trim(), tags); + this.newContent = ""; + this.newTags = ""; + this.entries = await memoryLoad(); + this.requestUpdate(); + } + + override render() { + if (!this.open) return html``; + const entries = this.filtered; + return html` +
{ if (e.target === e.currentTarget) this.hide(); }}> +
+
+

🧠 Memory Manager

+ +
+
+ { this.filter = (e.target as HTMLInputElement).value; }} /> +
+
+ ${this.loading ? html`
Loading...
` : ""} + ${!this.loading && entries.length === 0 ? html`
No memories stored yet
` : ""} +
+ ${entries.map(entry => html` +
+
+
${entry.content}
+
+ ${entry.timestamp.slice(0, 10)} + ${entry.tags.map(tag => html`${tag}`)} +
+
+ +
+ `)} +
+
+
+
+ +
+ { this.newTags = (e.target as HTMLInputElement).value; }} /> + +
+
+
+
+
+ `; + } +} diff --git a/packages/web-ui/example/src/components/session-export.ts b/packages/web-ui/example/src/components/session-export.ts new file mode 100644 index 0000000..0d4bff6 --- /dev/null +++ b/packages/web-ui/example/src/components/session-export.ts @@ -0,0 +1,45 @@ + +import type { AgentMessage } from "@jaeswift/jae-agent-core"; + +export function exportSessionAsMarkdown(messages: AgentMessage[], title: string): void { + const lines: string[] = [ + `# ${title || "JAE Session Export"}`, + ``, + `*Exported: ${new Date().toLocaleString()}*`, + ``, + `---`, + ``, + ]; + + for (const msg of messages) { + if (msg.role === "user") { + const content = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content); + lines.push(`## 👤 User`, ``, content, ``, `---`, ``); + } else if (msg.role === "assistant") { + const m = msg as any; + const textBlocks = Array.isArray(m.content) + ? m.content.filter((b: any) => b.type === "text").map((b: any) => b.text).join("\n") + : m.content || ""; + lines.push(`## 🤖 Assistant`, ``, textBlocks, ``, `---`, ``); + } + } + + const blob = new Blob([lines.join("\n")], { type: "text/markdown" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `jae-session-${Date.now()}.md`; + a.click(); + URL.revokeObjectURL(url); +} + +export function exportSessionAsJson(messages: AgentMessage[], title: string): void { + const data = { title, exportedAt: new Date().toISOString(), messages }; + const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const a = document.createElement("a"); + a.href = url; + a.download = `jae-session-${Date.now()}.json`; + a.click(); + URL.revokeObjectURL(url); +} diff --git a/packages/web-ui/example/src/main.ts b/packages/web-ui/example/src/main.ts index f1d6cdc..4b61f91 100644 --- a/packages/web-ui/example/src/main.ts +++ b/packages/web-ui/example/src/main.ts @@ -1,31 +1,41 @@ + import "@mariozechner/mini-lit/dist/ThemeToggle.js"; import { Agent, type AgentMessage } from "@jaeswift/jae-agent-core"; import { getModel } from "@jaeswift/jae-ai"; import { - type AgentState, - ApiKeyPromptDialog, - AppStorage, - ChatPanel, - CustomProvidersStore, - createJavaScriptReplTool, - IndexedDBStorageBackend, - // PersistentStorageDialog, // TODO: Fix - currently broken - ProviderKeysStore, - ProvidersModelsTab, - ProxyTab, - SessionListDialog, - SessionsStore, - SettingsDialog, - SettingsStore, - setAppStorage, + type AgentState, + ApiKeyPromptDialog, + AppStorage, + ChatPanel, + CustomProvidersStore, + createJavaScriptReplTool, + IndexedDBStorageBackend, + ProviderKeysStore, + ProvidersModelsTab, + ProxyTab, + SessionListDialog, + SessionsStore, + SettingsDialog, + SettingsStore, + setAppStorage, } from "@jaeswift/jae-web-ui"; import { html, render } from "lit"; -import { Bell, History, Plus, Settings } from "lucide"; +import { Bell, Brain, Download, History, Keyboard, Plus, Settings, Terminal } from "lucide"; import "./app.css"; import { icon } from "@mariozechner/mini-lit"; import { Button } from "@mariozechner/mini-lit/dist/Button.js"; import { Input } from "@mariozechner/mini-lit/dist/Input.js"; import { createSystemNotification, customConvertToLlm, registerCustomMessageRenderers } from "./custom-messages.js"; +import { createWebSearchTool, createImageGenTool, createTTSTool } from "@jaeswift/jae-web-ui"; +import { CommandPalette } from "./components/command-palette.js"; +import { KeyboardShortcuts } from "./components/keyboard-shortcuts.js"; +import { MemoryManager } from "./components/memory-manager.js"; +import { CostTracker } from "./components/cost-tracker.js"; +import { exportSessionAsMarkdown, exportSessionAsJson } from "./components/session-export.js"; +import "./components/command-palette.js"; +import "./components/keyboard-shortcuts.js"; +import "./components/memory-manager.js"; +import "./components/cost-tracker.js"; // Register custom message renderers registerCustomMessageRenderers(); @@ -36,29 +46,25 @@ const providerKeys = new ProviderKeysStore(); const sessions = new SessionsStore(); const customProviders = new CustomProvidersStore(); -// Gather configs const configs = [ - settings.getConfig(), - SessionsStore.getMetadataConfig(), - providerKeys.getConfig(), - customProviders.getConfig(), - sessions.getConfig(), + settings.getConfig(), + SessionsStore.getMetadataConfig(), + providerKeys.getConfig(), + customProviders.getConfig(), + sessions.getConfig(), ]; -// Create backend const backend = new IndexedDBStorageBackend({ - dbName: "jae-web-ui-example", - version: 2, // Incremented for custom-providers store - stores: configs, + dbName: "jae-web-ui-example", + version: 2, + stores: configs, }); -// Wire backend to stores settings.setBackend(backend); providerKeys.setBackend(backend); customProviders.setBackend(backend); sessions.setBackend(backend); -// Create and set app storage const storage = new AppStorage(settings, providerKeys, sessions, customProviders, backend); setAppStorage(storage); @@ -69,353 +75,323 @@ let agent: Agent; let chatPanel: ChatPanel; let agentUnsubscribe: (() => void) | undefined; +// --- Feature instances --- +const commandPalette = document.createElement("command-palette") as CommandPalette; +const keyboardShortcuts = document.createElement("keyboard-shortcuts") as KeyboardShortcuts; +const memoryManager = document.createElement("memory-manager") as MemoryManager; +const costTracker = document.createElement("cost-tracker") as CostTracker; +document.body.appendChild(commandPalette); +document.body.appendChild(keyboardShortcuts); +document.body.appendChild(memoryManager); + +// --- Global keyboard handler --- +window.addEventListener("keydown", (e: KeyboardEvent) => { + const meta = e.metaKey || e.ctrlKey; + if (meta && e.key === "k") { e.preventDefault(); commandPalette.show(); return; } + if (meta && e.key === "e") { e.preventDefault(); handleExport(); return; } + if (meta && e.key === "n") { e.preventDefault(); newSession(); return; } + if (e.key === "?" && !(e.target instanceof HTMLInputElement) && !(e.target instanceof HTMLTextAreaElement)) { + keyboardShortcuts.toggle(); + } +}); + +// --- Setup command palette commands --- +function setupCommands() { + commandPalette.setCommands([ + { + id: "new-session", label: "New Session", description: "Start a fresh conversation", + shortcut: "Ctrl+N", keywords: ["new", "fresh", "start"], + action: newSession, + }, + { + id: "sessions", label: "Session History", description: "Browse and load past sessions", + shortcut: "Ctrl+H", keywords: ["history", "sessions", "past"], + action: () => SessionListDialog.open(async (id) => await loadSession(id), (id) => { if (id === currentSessionId) newSession(); }), + }, + { + id: "export-md", label: "Export as Markdown", description: "Download current session as .md", + shortcut: "Ctrl+E", keywords: ["export", "download", "save", "markdown"], + action: () => handleExport("markdown"), + }, + { + id: "export-json", label: "Export as JSON", description: "Download current session as .json", + keywords: ["export", "download", "save", "json"], + action: () => handleExport("json"), + }, + { + id: "memory", label: "Memory Manager", description: "Browse and manage stored memories", + keywords: ["memory", "remember", "recall", "brain"], + action: () => memoryManager.show(), + }, + { + id: "settings", label: "Settings", description: "Configure providers and models", + keywords: ["settings", "config", "provider", "api", "key", "model"], + action: () => SettingsDialog.open([new ProvidersModelsTab(), new ProxyTab()]), + }, + { + id: "shortcuts", label: "Keyboard Shortcuts", description: "View all keyboard shortcuts", + shortcut: "?", keywords: ["keyboard", "shortcuts", "help", "keys"], + action: () => keyboardShortcuts.show(), + }, + { + id: "cost", label: "Token Usage & Cost", description: "View API usage stats for this session", + keywords: ["tokens", "cost", "usage", "spend"], + action: () => costTracker.dispatchEvent(new MouseEvent("click")), + }, + ]); +} + +function handleExport(format: "markdown" | "json" = "markdown") { + if (!agent) return; + const messages = agent.state.messages; + const title = currentTitle || "JAE Session"; + if (format === "markdown") exportSessionAsMarkdown(messages, title); + else exportSessionAsJson(messages, title); +} + const generateTitle = (messages: AgentMessage[]): string => { - const firstUserMsg = messages.find((m) => m.role === "user" || m.role === "user-with-attachments"); - if (!firstUserMsg || (firstUserMsg.role !== "user" && firstUserMsg.role !== "user-with-attachments")) return ""; - - let text = ""; - const content = firstUserMsg.content; - - if (typeof content === "string") { - text = content; - } else { - const textBlocks = content.filter((c: any) => c.type === "text"); - text = textBlocks.map((c: any) => c.text || "").join(" "); - } - - text = text.trim(); - if (!text) return ""; - - const sentenceEnd = text.search(/[.!?]/); - if (sentenceEnd > 0 && sentenceEnd <= 50) { - return text.substring(0, sentenceEnd + 1); - } - return text.length <= 50 ? text : `${text.substring(0, 47)}...`; + const firstUserMsg = messages.find((m) => m.role === "user" || m.role === "user-with-attachments"); + if (!firstUserMsg || (firstUserMsg.role !== "user" && firstUserMsg.role !== "user-with-attachments")) return ""; + let text = ""; + const content = firstUserMsg.content; + if (typeof content === "string") { text = content; } + else { const textBlocks = content.filter((c: any) => c.type === "text"); text = textBlocks.map((c: any) => c.text || "").join(" "); } + text = text.trim(); + if (!text) return ""; + const sentenceEnd = text.search(/[.!?]/); + if (sentenceEnd > 0 && sentenceEnd <= 50) return text.substring(0, sentenceEnd + 1); + return text.length <= 50 ? text : `${text.substring(0, 47)}...`; }; const shouldSaveSession = (messages: AgentMessage[]): boolean => { - const hasUserMsg = messages.some((m: any) => m.role === "user" || m.role === "user-with-attachments"); - const hasAssistantMsg = messages.some((m: any) => m.role === "assistant"); - return hasUserMsg && hasAssistantMsg; + const hasUserMsg = messages.some((m: any) => m.role === "user" || m.role === "user-with-attachments"); + const hasAssistantMsg = messages.some((m: any) => m.role === "assistant"); + return hasUserMsg && hasAssistantMsg; }; const saveSession = async () => { - if (!storage.sessions || !currentSessionId || !agent || !currentTitle) return; - - const state = agent.state; - if (!shouldSaveSession(state.messages)) return; - - try { - // Create session data - const sessionData = { - id: currentSessionId, - title: currentTitle, - model: state.model!, - thinkingLevel: state.thinkingLevel, - messages: state.messages, - createdAt: new Date().toISOString(), - lastModified: new Date().toISOString(), - }; - - // Create session metadata - const metadata = { - id: currentSessionId, - title: currentTitle, - createdAt: sessionData.createdAt, - lastModified: sessionData.lastModified, - messageCount: state.messages.length, - usage: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - totalTokens: 0, - cost: { - input: 0, - output: 0, - cacheRead: 0, - cacheWrite: 0, - total: 0, - }, - }, - modelId: state.model?.id || null, - thinkingLevel: state.thinkingLevel, - preview: generateTitle(state.messages), - }; - - await storage.sessions.save(sessionData, metadata); - } catch (err) { - console.error("Failed to save session:", err); - } + if (!storage.sessions || !currentSessionId || !agent || !currentTitle) return; + const state = agent.state; + if (!shouldSaveSession(state.messages)) return; + try { + const sessionData = { + id: currentSessionId, title: currentTitle, model: state.model!, + thinkingLevel: state.thinkingLevel, messages: state.messages, + createdAt: new Date().toISOString(), lastModified: new Date().toISOString(), + }; + const metadata = { + id: currentSessionId, title: currentTitle, + createdAt: sessionData.createdAt, lastModified: sessionData.lastModified, + messageCount: state.messages.length, + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + modelId: state.model?.id || null, thinkingLevel: state.thinkingLevel, + preview: generateTitle(state.messages), + }; + await storage.sessions.save(sessionData, metadata); + } catch (err) { console.error("Failed to save session:", err); } }; const updateUrl = (sessionId: string) => { - const url = new URL(window.location.href); - url.searchParams.set("session", sessionId); - window.history.replaceState({}, "", url); + const url = new URL(window.location.href); + url.searchParams.set("session", sessionId); + window.history.replaceState({}, "", url); }; const createAgent = async (initialState?: Partial) => { - if (agentUnsubscribe) { - agentUnsubscribe(); - } + if (agentUnsubscribe) agentUnsubscribe(); - agent = new Agent({ - initialState: initialState || { - systemPrompt: `You are a helpful AI assistant with access to various tools. + agent = new Agent({ + initialState: initialState || { + systemPrompt: `You are a helpful AI assistant with access to various tools. Available tools: -- JavaScript REPL: Execute JavaScript code in a sandboxed browser environment (can do calculations, get time, process data, create visualizations, etc.) +- JavaScript REPL: Execute JavaScript code in a sandboxed browser environment +- Web Search: Search the web via DuckDuckGo for current information +- Image Generation: Generate images using Venice AI +- Text to Speech: Convert text to audio +- Memory: Save and recall information across sessions - Artifacts: Create interactive HTML, SVG, Markdown, and text artifacts Feel free to use these tools when needed to provide accurate and helpful responses.`, - model: getModel("anthropic", "claude-sonnet-4-5-20250929"), - thinkingLevel: "off", - messages: [], - tools: [], - }, - // Custom transformer: convert custom messages to LLM-compatible format - convertToLlm: customConvertToLlm, - }); + model: getModel("anthropic", "claude-sonnet-4-5-20250929"), + thinkingLevel: "off", + messages: [], + tools: [], + }, + convertToLlm: customConvertToLlm, + onApiKeyRequired: async (provider) => { + const key = await ApiKeyPromptDialog.prompt(provider); + if (key) await providerKeys.set(provider, key); + return key; + }, + getProviderApiKey: async (provider) => providerKeys.get(provider), + onStateChange: async (state, prevState) => { + if (prevState?.messages.length !== state.messages.length) { + if (!currentTitle) { + const generated = generateTitle(state.messages); + if (generated) { + currentTitle = generated; + if (!currentSessionId) currentSessionId = crypto.randomUUID(); + updateUrl(currentSessionId); + renderApp(); + } + } + await saveSession(); + } + }, + createTools: async (runtimeProvidersFactory) => { + const replTool = createJavaScriptReplTool(); + replTool.runtimeProvidersFactory = runtimeProvidersFactory; + return [replTool, createWebSearchTool(), createImageGenTool(), createTTSTool()]; + }, + }); - agentUnsubscribe = agent.subscribe((event: any) => { - if (event.type === "state-update") { - const messages = event.state.messages; + // Bind cost tracker to new agent + costTracker.bindAgent(agent); - // Generate title after first successful response - if (!currentTitle && shouldSaveSession(messages)) { - currentTitle = generateTitle(messages); - } - - // Create session ID on first successful save - if (!currentSessionId && shouldSaveSession(messages)) { - currentSessionId = crypto.randomUUID(); - updateUrl(currentSessionId); - } - - // Auto-save - if (currentSessionId) { - saveSession(); - } - - renderApp(); - } - }); - - await chatPanel.setAgent(agent, { - onApiKeyRequired: async (provider: string) => { - return await ApiKeyPromptDialog.prompt(provider); - }, - toolsFactory: (_agent, _agentInterface, _artifactsPanel, runtimeProvidersFactory) => { - // Create javascript_repl tool with access to attachments + artifacts - const replTool = createJavaScriptReplTool(); - replTool.runtimeProvidersFactory = runtimeProvidersFactory; - return [replTool, createWebSearchTool(), createImageGenTool(), createTTSTool()]; - }, - }); + chatPanel?.setAgent(agent); + if (!currentSessionId) currentSessionId = crypto.randomUUID(); }; const loadSession = async (sessionId: string): Promise => { - if (!storage.sessions) return false; - - const sessionData = await storage.sessions.get(sessionId); - if (!sessionData) { - console.error("Session not found:", sessionId); - return false; - } - - currentSessionId = sessionId; - const metadata = await storage.sessions.getMetadata(sessionId); - currentTitle = metadata?.title || ""; - - await createAgent({ - model: sessionData.model, - thinkingLevel: sessionData.thinkingLevel, - messages: sessionData.messages, - tools: [], - }); - - updateUrl(sessionId); - renderApp(); - return true; + if (!storage.sessions) return false; + const sessionData = await storage.sessions.get(sessionId); + if (!sessionData) { console.error("Session not found:", sessionId); return false; } + currentSessionId = sessionId; + const metadata = await storage.sessions.getMetadata(sessionId); + currentTitle = metadata?.title || ""; + await createAgent({ + model: sessionData.model, thinkingLevel: sessionData.thinkingLevel, + messages: sessionData.messages, tools: [], + }); + updateUrl(sessionId); + renderApp(); + return true; }; const newSession = () => { - const url = new URL(window.location.href); - url.search = ""; - window.location.href = url.toString(); + const url = new URL(window.location.href); + url.search = ""; + window.location.href = url.toString(); }; // ============================================================================ // RENDER // ============================================================================ const renderApp = () => { - const app = document.getElementById("app"); - if (!app) return; + const app = document.getElementById("app"); + if (!app) return; - const appHtml = html` -
- -
-
- ${Button({ - variant: "ghost", - size: "sm", - children: icon(History, "sm"), - onClick: () => { - SessionListDialog.open( - async (sessionId) => { - await loadSession(sessionId); - }, - (deletedSessionId) => { - // Only reload if the current session was deleted - if (deletedSessionId === currentSessionId) { - newSession(); - } - }, - ); - }, - title: "Sessions", - })} - ${Button({ - variant: "ghost", - size: "sm", - children: icon(Plus, "sm"), - onClick: newSession, - title: "New Session", - })} + const appHtml = html` +
+ +
+
+ ${Button({ variant: "ghost", size: "sm", children: icon(History, "sm"), + onClick: () => SessionListDialog.open( + async (id) => { await loadSession(id); }, + (id) => { if (id === currentSessionId) newSession(); } + ), title: "Sessions (Ctrl+H)" })} + ${Button({ variant: "ghost", size: "sm", children: icon(Plus, "sm"), onClick: newSession, title: "New Session (Ctrl+N)" })} + ${ + currentTitle + ? isEditingTitle + ? html`
+ ${Input({ + type: "text", value: currentTitle, className: "text-sm w-64", + onChange: async (e: Event) => { + const newTitle = (e.target as HTMLInputElement).value.trim(); + if (newTitle && newTitle !== currentTitle && storage.sessions && currentSessionId) { + await storage.sessions.updateTitle(currentSessionId, newTitle); + currentTitle = newTitle; + } + isEditingTitle = false; renderApp(); + }, + onKeyDown: async (e: KeyboardEvent) => { + if (e.key === "Enter") { + const newTitle = (e.target as HTMLInputElement).value.trim(); + if (newTitle && newTitle !== currentTitle && storage.sessions && currentSessionId) { + await storage.sessions.updateTitle(currentSessionId, newTitle); + currentTitle = newTitle; + } + isEditingTitle = false; renderApp(); + } else if (e.key === "Escape") { isEditingTitle = false; renderApp(); } + }, + })} +
` + : html`` + : html`JAE Web UI` + } +
- ${ - currentTitle - ? isEditingTitle - ? html`
- ${Input({ - type: "text", - value: currentTitle, - className: "text-sm w-64", - onChange: async (e: Event) => { - const newTitle = (e.target as HTMLInputElement).value.trim(); - if (newTitle && newTitle !== currentTitle && storage.sessions && currentSessionId) { - await storage.sessions.updateTitle(currentSessionId, newTitle); - currentTitle = newTitle; - } - isEditingTitle = false; - renderApp(); - }, - onKeyDown: async (e: KeyboardEvent) => { - if (e.key === "Enter") { - const newTitle = (e.target as HTMLInputElement).value.trim(); - if (newTitle && newTitle !== currentTitle && storage.sessions && currentSessionId) { - await storage.sessions.updateTitle(currentSessionId, newTitle); - currentTitle = newTitle; - } - isEditingTitle = false; - renderApp(); - } else if (e.key === "Escape") { - isEditingTitle = false; - renderApp(); - } - }, - })} -
` - : html`` - : html`Pi Web UI Example` - } -
-
- ${Button({ - variant: "ghost", - size: "sm", - children: icon(Bell, "sm"), - onClick: () => { - // Demo: Inject custom message (will appear on next agent run) - if (agent) { - agent.steer( - createSystemNotification( - "This is a custom message! It appears in the UI but is never sent to the LLM.", - ), - ); - } - }, - title: "Demo: Add Custom Notification", - })} - - ${Button({ - variant: "ghost", - size: "sm", - children: icon(Settings, "sm"), - onClick: () => SettingsDialog.open([new ProvidersModelsTab(), new ProxyTab()]), - title: "Settings", - })} -
-
+ +
+ + ${costTracker} + + ${Button({ variant: "ghost", size: "sm", children: icon(Brain, "sm"), + onClick: () => memoryManager.show(), title: "Memory Manager" })} + + ${Button({ variant: "ghost", size: "sm", children: icon(Download, "sm"), + onClick: () => handleExport(), title: "Export Session (Ctrl+E)" })} + + ${Button({ variant: "ghost", size: "sm", children: icon(Keyboard, "sm"), + onClick: () => keyboardShortcuts.show(), title: "Keyboard Shortcuts (?)" })} + + ${Button({ variant: "ghost", size: "sm", + children: html`⌘K`, + onClick: () => commandPalette.show(), title: "Command Palette (Ctrl+K)" })} + + ${Button({ variant: "ghost", size: "sm", children: icon(Settings, "sm"), + onClick: () => SettingsDialog.open([new ProvidersModelsTab(), new ProxyTab()]), + title: "Settings" })} +
+
- - ${chatPanel} -
- `; + + ${chatPanel} +
+ `; - render(appHtml, app); + render(appHtml, app); }; // ============================================================================ // INIT // ============================================================================ async function initApp() { - const app = document.getElementById("app"); - if (!app) throw new Error("App container not found"); + const app = document.getElementById("app"); + if (!app) throw new Error("App container not found"); - // Show loading - render( - html` -
-
Loading...
-
- `, - app, - ); + render( + html`
+
Loading...
+
`, + app, + ); - // TODO: Fix PersistentStorageDialog - currently broken - // Request persistent storage - // if (storage.sessions) { - // await PersistentStorageDialog.request(); - // } + chatPanel = new ChatPanel(); + setupCommands(); - // Create ChatPanel - chatPanel = new ChatPanel(); + const urlParams = new URLSearchParams(window.location.search); + const sessionIdFromUrl = urlParams.get("session"); - // Check for session in URL - const urlParams = new URLSearchParams(window.location.search); - const sessionIdFromUrl = urlParams.get("session"); + if (sessionIdFromUrl) { + const loaded = await loadSession(sessionIdFromUrl); + if (!loaded) { newSession(); return; } + } else { + await createAgent(); + } - if (sessionIdFromUrl) { - const loaded = await loadSession(sessionIdFromUrl); - if (!loaded) { - // Session doesn't exist, redirect to new session - newSession(); - return; - } - } else { - await createAgent(); - } - - renderApp(); + renderApp(); } initApp(); diff --git a/packages/web-ui/src/index.ts b/packages/web-ui/src/index.ts index 136d98a..1eee446 100644 --- a/packages/web-ui/src/index.ts +++ b/packages/web-ui/src/index.ts @@ -120,3 +120,10 @@ export { i18n, setLanguage, translations } from "./utils/i18n.js"; export { applyProxyIfNeeded, createStreamFn, isCorsError, shouldUseProxyForProvider } from "./utils/proxy-utils.js"; export { VeniceModelBrowser } from "./components/VeniceModelBrowser.js"; + +// Venice / community tools +export { createWebSearchTool, webSearchTool } from "./tools/web-search.js"; +export { createImageGenTool, imageGenTool } from "./tools/image-gen.js"; +export { createTTSTool, ttsTool } from "./tools/voice-tts.js"; +export { MermaidRenderer } from "./tools/renderers/MermaidRenderer.js"; +export { DiffRenderer } from "./tools/renderers/DiffRenderer.js"; diff --git a/packages/web-ui/src/tools/diff-viewer.ts b/packages/web-ui/src/tools/diff-viewer.ts new file mode 100644 index 0000000..82b9c42 --- /dev/null +++ b/packages/web-ui/src/tools/diff-viewer.ts @@ -0,0 +1,98 @@ + +import type { AgentTool } from "@jaeswift/jae-agent-core"; +import { Type } from "@sinclair/typebox"; +import type { ToolResultMessage } from "@jaeswift/jae-ai"; +import { html } from "lit"; +import { GitCompare } from "lucide"; +import { registerToolRenderer, renderHeader } from "./renderer-registry.js"; +import type { ToolRenderer, ToolRenderResult } from "./types.js"; + +export interface DiffDetails { + original: string; + modified: string; + filename?: string; +} + +interface DiffParams { + original: string; + modified: string; + filename?: string; +} + +const diffSchema = Type.Object({ + original: Type.String({ description: "Original file content" }), + modified: Type.String({ description: "Modified file content" }), + filename: Type.Optional(Type.String({ description: "Filename for display" })), +}); + +function computeLineDiff(original: string, modified: string): Array<{ type: "add" | "remove" | "same"; line: string }> { + const oldLines = original.split("\n"); + const newLines = modified.split("\n"); + const result: Array<{ type: "add" | "remove" | "same"; line: string }> = []; + const maxLen = Math.max(oldLines.length, newLines.length); + for (let i = 0; i < maxLen; i++) { + if (i >= oldLines.length) { result.push({ type: "add", line: newLines[i] }); } + else if (i >= newLines.length) { result.push({ type: "remove", line: oldLines[i] }); } + else if (oldLines[i] === newLines[i]) { result.push({ type: "same", line: oldLines[i] }); } + else { + result.push({ type: "remove", line: oldLines[i] }); + result.push({ type: "add", line: newLines[i] }); + } + } + return result; +} + +export const diffTool: AgentTool = { + name: "show_diff", + label: "Show Diff", + description: "Show a diff between two versions of code or text", + parameters: diffSchema, + async execute(toolCallId, params, signal) { + return { + content: [{ type: "text", text: `Diff shown for: ${params.filename || "file"}` }], + details: { original: params.original, modified: params.modified, filename: params.filename }, + }; + }, +}; + +class DiffRenderer implements ToolRenderer { + render(params: DiffParams | undefined, result: ToolResultMessage | undefined): ToolRenderResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + if (!result?.details) { + return { content: renderHeader(state, GitCompare, `Diff: ${params?.filename || "file"}`), isCustom: false }; + } + const { original, modified, filename } = result.details; + const diffLines = computeLineDiff(original, modified); + const adds = diffLines.filter(l => l.type === "add").length; + const removes = diffLines.filter(l => l.type === "remove").length; + + return { + content: html` +
+ ${renderHeader(state, GitCompare, html`Diff: ${filename || "file"} +${adds}-${removes}`)} +
+ ${diffLines.map((l, i) => html` +
+ ${i + 1} + ${ + l.type === "add" ? "+ " : l.type === "remove" ? "- " : " " + }${l.line} +
+ `)} +
+
+ `, + isCustom: false, + }; + } +} + +registerToolRenderer("show_diff", new DiffRenderer()); + +export function createDiffTool(): AgentTool { + return diffTool; +} diff --git a/packages/web-ui/src/tools/memory-tool.ts b/packages/web-ui/src/tools/memory-tool.ts new file mode 100644 index 0000000..586db4f --- /dev/null +++ b/packages/web-ui/src/tools/memory-tool.ts @@ -0,0 +1,164 @@ + +import type { AgentTool } from "@jaeswift/jae-agent-core"; +import { Type } from "@sinclair/typebox"; +import type { ToolResultMessage } from "@jaeswift/jae-ai"; +import { html } from "lit"; +import { Brain, BrainCircuit, Trash2 } from "lucide"; +import { registerToolRenderer, renderHeader } from "./renderer-registry.js"; +import type { ToolRenderer, ToolRenderResult } from "./types.js"; + +export interface MemoryEntry { + id: string; + content: string; + tags: string[]; + timestamp: string; +} + +export interface MemoryStore { + entries: MemoryEntry[]; +} + +const DB_NAME = "jae-memory"; +const DB_VERSION = 1; +const STORE_NAME = "memories"; + +let db: IDBDatabase | null = null; + +async function openDB(): Promise { + if (db) return db; + return new Promise((resolve, reject) => { + const req = indexedDB.open(DB_NAME, DB_VERSION); + req.onupgradeneeded = () => { + req.result.createObjectStore(STORE_NAME, { keyPath: "id" }); + }; + req.onsuccess = () => { db = req.result; resolve(db); }; + req.onerror = () => reject(req.error); + }); +} + +export async function memorySave(content: string, tags: string[] = []): Promise { + const db = await openDB(); + const entry: MemoryEntry = { id: crypto.randomUUID(), content, tags, timestamp: new Date().toISOString() }; + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + tx.objectStore(STORE_NAME).put(entry); + tx.oncomplete = () => resolve(entry.id); + tx.onerror = () => reject(tx.error); + }); +} + +export async function memoryLoad(): Promise { + const db = await openDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readonly"); + const req = tx.objectStore(STORE_NAME).getAll(); + req.onsuccess = () => resolve(req.result || []); + req.onerror = () => reject(req.error); + }); +} + +export async function memorySearch(query: string): Promise { + const all = await memoryLoad(); + const q = query.toLowerCase(); + return all.filter(e => e.content.toLowerCase().includes(q) || e.tags.some(t => t.toLowerCase().includes(q))); +} + +export async function memoryDelete(id: string): Promise { + const db = await openDB(); + return new Promise((resolve, reject) => { + const tx = db.transaction(STORE_NAME, "readwrite"); + tx.objectStore(STORE_NAME).delete(id); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); +} + +// --- Save Memory Tool --- +const saveMemorySchema = Type.Object({ + content: Type.String({ description: "Information to remember" }), + tags: Type.Optional(Type.Array(Type.String(), { description: "Tags for categorisation" })), +}); + +export const saveMemoryTool: AgentTool = { + name: "memory_save", + label: "Save Memory", + description: "Save a piece of information to long-term memory for future sessions.", + parameters: saveMemorySchema, + async execute(toolCallId, params, signal) { + const id = await memorySave(params.content, params.tags || []); + return { + content: [{ type: "text", text: `Memory saved with ID: ${id}` }], + details: { id, content: params.content }, + }; + }, +}; + +// --- Recall Memory Tool --- +const recallMemorySchema = Type.Object({ + query: Type.String({ description: "Search query to find relevant memories" }), +}); + +export const recallMemoryTool: AgentTool = { + name: "memory_recall", + label: "Recall Memory", + description: "Search long-term memory for relevant information.", + parameters: recallMemorySchema, + async execute(toolCallId, params, signal) { + const results = await memorySearch(params.query); + const text = results.length === 0 + ? `No memories found for: ${params.query}` + : results.map(r => `[${r.timestamp.slice(0, 10)}] ${r.content}`).join("\n\n"); + return { + content: [{ type: "text", text }], + details: { results }, + }; + }, +}; + +// --- Renderers --- +class SaveMemoryRenderer implements ToolRenderer { + render(params: any, result: ToolResultMessage<{ id: string; content: string }> | undefined): ToolRenderResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + return { + content: html` +
+ ${renderHeader(state, Brain, `Memory saved`)} + ${result?.details ? html`
${result.details.content}
` : ""} +
+ `, + isCustom: false, + }; + } +} + +class RecallMemoryRenderer implements ToolRenderer { + render(params: any, result: ToolResultMessage<{ results: MemoryEntry[] }> | undefined): ToolRenderResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + const results = result?.details?.results || []; + return { + content: html` +
+ ${renderHeader(state, BrainCircuit, `Memory recall: ${params?.query || ""}`)} + ${results.length > 0 ? html` +
+ ${results.map(r => html` +
+
${r.timestamp.slice(0, 10)}
+
${r.content}
+
+ `)} +
+ ` : ""} +
+ `, + isCustom: false, + }; + } +} + +registerToolRenderer("memory_save", new SaveMemoryRenderer()); +registerToolRenderer("memory_recall", new RecallMemoryRenderer()); + +export function createMemoryTools() { + return [saveMemoryTool, recallMemoryTool]; +} diff --git a/packages/web-ui/src/tools/mermaid-diagram.ts b/packages/web-ui/src/tools/mermaid-diagram.ts new file mode 100644 index 0000000..cba150b --- /dev/null +++ b/packages/web-ui/src/tools/mermaid-diagram.ts @@ -0,0 +1,103 @@ + +import type { AgentTool } from "@jaeswift/jae-agent-core"; +import { Type } from "@sinclair/typebox"; +import type { ToolResultMessage } from "@jaeswift/jae-ai"; +import { html } from "lit"; +import { GitBranch } from "lucide"; +import { registerToolRenderer, renderHeader } from "./renderer-registry.js"; +import type { ToolRenderer, ToolRenderResult } from "./types.js"; + +export interface MermaidDetails { + diagram: string; + rendered: boolean; + error?: string; +} + +interface MermaidParams { + diagram: string; + title?: string; +} + +const mermaidSchema = Type.Object({ + diagram: Type.String({ description: "Mermaid diagram source code" }), + title: Type.Optional(Type.String({ description: "Optional title for the diagram" })), +}); + +export const mermaidTool: AgentTool = { + name: "render_diagram", + label: "Render Diagram", + description: "Render a Mermaid diagram (flowchart, sequence, gantt, class diagram, etc.)", + parameters: mermaidSchema, + async execute(toolCallId, params, signal) { + return { + content: [{ type: "text", text: `Diagram rendered: ${params.title || "Untitled"}` }], + details: { diagram: params.diagram, rendered: true }, + }; + }, +}; + +let mermaidLoaded = false; +async function loadMermaid(): Promise { + if ((window as any).mermaid) return (window as any).mermaid; + return new Promise((resolve, reject) => { + const script = document.createElement("script"); + script.src = "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"; + script.onload = () => { + const m = (window as any).mermaid; + m.initialize({ startOnLoad: false, theme: document.documentElement.classList.contains("dark") ? "dark" : "default" }); + mermaidLoaded = true; + resolve(m); + }; + script.onerror = reject; + document.head.appendChild(script); + }); +} + +class MermaidRenderer implements ToolRenderer { + render(params: MermaidParams | undefined, result: ToolResultMessage | undefined): ToolRenderResult { + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + const diagram = result?.details?.diagram || params?.diagram || ""; + const title = params?.title || "Diagram"; + + if (!diagram) { + return { content: renderHeader(state, GitBranch, "Rendering diagram..."), isCustom: false }; + } + + const containerId = `mermaid-${Math.random().toString(36).slice(2)}`; + + const renderDiagram = async (container: HTMLElement) => { + try { + const mermaid = await loadMermaid(); + const { svg } = await mermaid.render(containerId + "-svg", diagram); + container.innerHTML = svg; + container.style.maxWidth = "100%"; + const svgEl = container.querySelector("svg"); + if (svgEl) { + svgEl.style.maxWidth = "100%"; + svgEl.style.height = "auto"; + } + } catch (err: any) { + container.innerHTML = `
Diagram error: ${err.message}
`; + } + }; + + return { + content: html` +
+ ${renderHeader(state, GitBranch, `Diagram: ${title}`)} +
el && renderDiagram(el)} + >
+
+ `, + isCustom: false, + }; + } +} + +registerToolRenderer("render_diagram", new MermaidRenderer()); + +export function createMermaidTool(): AgentTool { + return mermaidTool; +} diff --git a/packages/web-ui/src/tools/renderers/DiffRenderer.ts b/packages/web-ui/src/tools/renderers/DiffRenderer.ts new file mode 100644 index 0000000..44c9773 --- /dev/null +++ b/packages/web-ui/src/tools/renderers/DiffRenderer.ts @@ -0,0 +1,47 @@ +import { html } from "lit"; +import { FileText } from "lucide"; +import type { ToolResultMessage } from "@jaeswift/jae-ai"; +import { renderHeader } from "../renderer-registry.js"; +import type { ToolRenderer, ToolRenderResult } from "../types.js"; +import { registerToolRenderer } from "../renderer-registry.js"; + +export class DiffRenderer implements ToolRenderer { + render(params: any, result: ToolResultMessage | undefined, isStreaming?: boolean): ToolRenderResult { + const rawContent = result?.content; + const resultText = Array.isArray(rawContent) + ? rawContent.filter((c: any) => c.type === "text").map((c: any) => c.text).join("\n") + : typeof rawContent === "string" ? rawContent : ""; + const diff = params?.diff || params?.patch || resultText || ""; + const filename = params?.file || params?.filename || ""; + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + const label = filename ? "Diff: " + filename : "File Diff"; + + const lines = diff.split("\n"); + + return { + content: html` +
+ ${renderHeader(state, FileText, label)} + ${diff ? html` +
+
${lines.map((line: string) => {
+                let cls = "block";
+                if (line.startsWith("+") && !line.startsWith("+++")) cls = "text-green-500 bg-green-500/10 block px-1";
+                else if (line.startsWith("-") && !line.startsWith("---")) cls = "text-red-500 bg-red-500/10 block px-1";
+                else if (line.startsWith("@@")) cls = "text-blue-400 block px-1";
+                else if (line.startsWith("diff ") || line.startsWith("index ")) cls = "text-muted-foreground block px-1";
+                else cls = "block px-1";
+                return html`${line}
+`;
+              })}
+
+ ` : ""} +
+ `, + isCustom: false, + }; + } +} + +registerToolRenderer("diff", new DiffRenderer()); +registerToolRenderer("patch", new DiffRenderer()); diff --git a/packages/web-ui/src/tools/renderers/MermaidRenderer.ts b/packages/web-ui/src/tools/renderers/MermaidRenderer.ts new file mode 100644 index 0000000..7ada211 --- /dev/null +++ b/packages/web-ui/src/tools/renderers/MermaidRenderer.ts @@ -0,0 +1,53 @@ +import { html } from "lit"; +import { GitBranch } from "lucide"; +import type { ToolResultMessage } from "@jaeswift/jae-ai"; +import { renderHeader } from "../renderer-registry.js"; +import type { ToolRenderer, ToolRenderResult } from "../types.js"; +import { registerToolRenderer } from "../renderer-registry.js"; + +export class MermaidRenderer implements ToolRenderer { + render(params: any, result: ToolResultMessage | undefined, isStreaming?: boolean): ToolRenderResult { + const rawContent = result?.content; + const resultText = Array.isArray(rawContent) + ? rawContent.filter((c: any) => c.type === "text").map((c: any) => c.text).join("\n") + : typeof rawContent === "string" ? rawContent : ""; + const diagram = params?.diagram || params?.code || resultText || ""; + const state = result ? (result.isError ? "error" : "complete") : "inprogress"; + const id = "mermaid-" + Math.random().toString(36).slice(2); + + return { + content: html` +
+ ${renderHeader(state, GitBranch, "Diagram")} + ${diagram ? html` +
+
${diagram}
+ +
+ ` : ""} +
+ `, + isCustom: false, + }; + } +} + +registerToolRenderer("mermaid", new MermaidRenderer()); +registerToolRenderer("diagram", new MermaidRenderer());