From 8c74f5ddfd08afbe256d9156ec4a623e4f717209 Mon Sep 17 00:00:00 2001 From: Eva Ho Date: Tue, 4 Nov 2025 15:21:59 -0500 Subject: [PATCH] ui: using streamdown AI elements for markdown rendering --- app/ui/app/package-lock.json | 1512 ++++++++++++++++- app/ui/app/package.json | 1 + .../StreamingMarkdownContent.stories.tsx | 614 ------- .../StreamingMarkdownContent.test.tsx | 522 ------ .../components/StreamingMarkdownContent.tsx | 415 ++--- app/ui/app/src/index.css | 787 --------- .../app/src/utils/processStreamingMarkdown.ts | 24 - .../app/src/utils/remarkStreamingMarkdown.ts | 447 ----- 8 files changed, 1693 insertions(+), 2629 deletions(-) delete mode 100644 app/ui/app/src/components/StreamingMarkdownContent.stories.tsx delete mode 100644 app/ui/app/src/components/StreamingMarkdownContent.test.tsx delete mode 100644 app/ui/app/src/utils/processStreamingMarkdown.ts delete mode 100644 app/ui/app/src/utils/remarkStreamingMarkdown.ts diff --git a/app/ui/app/package-lock.json b/app/ui/app/package-lock.json index 7877eeaef8..0dcd91a202 100644 --- a/app/ui/app/package-lock.json +++ b/app/ui/app/package-lock.json @@ -25,6 +25,7 @@ "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-math": "^6.0.0", + "streamdown": "^1.4.0", "unist-builder": "^4.0.0", "unist-util-parents": "^3.0.0" }, @@ -88,6 +89,37 @@ "node": ">=6.0.0" } }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@antfu/install-pkg/node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@antfu/utils": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", + "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/@babel/code-frame": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", @@ -422,6 +454,51 @@ "node": ">=18" } }, + "node_modules/@braintree/sanitize-url": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", + "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, "node_modules/@chromatic-com/storybook": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@chromatic-com/storybook/-/storybook-4.0.1.tgz", @@ -2353,6 +2430,40 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", + "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@antfu/utils": "^9.2.0", + "@iconify/types": "^2.0.0", + "debug": "^4.4.1", + "globals": "^15.15.0", + "kolorist": "^1.8.0", + "local-pkg": "^1.1.1", + "mlly": "^1.7.4" + } + }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2486,6 +2597,15 @@ "react": ">=16" } }, + "node_modules/@mermaid-js/parser": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", + "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "license": "MIT", + "dependencies": { + "langium": "3.3.1" + } + }, "node_modules/@neoconfetti/react": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@neoconfetti/react/-/react-1.0.0.tgz", @@ -2962,6 +3082,73 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.14.0.tgz", + "integrity": "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.14.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.14.0.tgz", + "integrity": "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.14.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.3" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.14.0.tgz", + "integrity": "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.14.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.14.0.tgz", + "integrity": "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.14.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.14.0.tgz", + "integrity": "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.14.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.14.0.tgz", + "integrity": "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, "node_modules/@storybook/addon-a11y": { "version": "9.0.14", "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-9.0.14.tgz", @@ -4030,6 +4217,259 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", + "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "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==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -4057,19 +4497,23 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, "license": "MIT" }, "node_modules/@types/estree-jsx": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "*" } }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -4134,7 +4578,6 @@ "version": "19.1.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.7.tgz", "integrity": "sha512-BnsPLV43ddr05N71gaGzyZ5hzkCmGwhMvYc8zmvI8Ci1bRkkDSzDDVfAXfN2tk748OwI7ediiPX6PfT9p0QGVg==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -4157,6 +4600,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -4681,7 +5131,6 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -5042,7 +5491,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -5097,7 +5545,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, "license": "MIT", "funding": { "type": "github", @@ -5134,6 +5581,32 @@ "node": ">= 16" } }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -5260,6 +5733,12 @@ "dev": true, "license": "MIT" }, + "node_modules/confbox": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", + "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", + "license": "MIT" + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -5267,6 +5746,15 @@ "dev": true, "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==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5453,6 +5941,520 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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==", + "license": "ISC", + "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.13", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", + "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -5510,6 +6512,15 @@ "node": ">=8" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -5572,6 +6583,15 @@ "dev": true, "license": "MIT" }, + "node_modules/dompurify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", + "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -5906,7 +6926,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -5940,6 +6959,12 @@ "node": ">=12.0.0" } }, + "node_modules/exsolve": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.7.tgz", + "integrity": "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==", + "license": "MIT" + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -6296,6 +7321,12 @@ "dev": true, "license": "MIT" }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "license": "MIT" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -6454,11 +7485,33 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0", @@ -6544,7 +7597,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" @@ -6582,7 +7634,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", - "dev": true, "license": "MIT", "funding": { "type": "opencollective", @@ -6599,6 +7650,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "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==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -6650,9 +7713,17 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", - "dev": true, "license": "MIT" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -7002,6 +8073,11 @@ "json-buffer": "3.0.1" } }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -7012,6 +8088,34 @@ "node": ">=6" } }, + "node_modules/kolorist": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", + "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", + "license": "MIT" + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -7265,6 +8369,23 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/local-pkg": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", + "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -7288,6 +8409,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -7336,6 +8463,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.542.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.542.0.tgz", + "integrity": "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -7401,18 +8537,28 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/marked": { + "version": "16.4.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.1.tgz", + "integrity": "sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/mdast-util-find-and-replace": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7429,7 +8575,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -7466,7 +8611,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "dev": true, "license": "MIT", "dependencies": { "mdast-util-from-markdown": "^2.0.0", @@ -7486,7 +8630,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7504,7 +8647,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7520,7 +8662,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7538,7 +8679,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7555,7 +8695,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz", "integrity": "sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -7592,7 +8731,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -7611,7 +8749,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -7636,7 +8773,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree-jsx": "^1.0.0", @@ -7730,6 +8866,34 @@ "node": ">= 8" } }, + "node_modules/mermaid": { + "version": "11.12.1", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.1.tgz", + "integrity": "sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^0.6.3", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.13", + "dayjs": "^1.11.18", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^16.2.1", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -7803,7 +8967,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "dev": true, "license": "MIT", "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", @@ -7824,7 +8987,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "dev": true, "license": "MIT", "dependencies": { "micromark-util-character": "^2.0.0", @@ -7841,7 +9003,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -7862,7 +9023,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -7881,7 +9041,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -7899,7 +9058,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "dev": true, "license": "MIT", "dependencies": { "micromark-util-types": "^2.0.0" @@ -7913,7 +9071,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "dev": true, "license": "MIT", "dependencies": { "devlop": "^1.0.0", @@ -8424,6 +9581,35 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/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==", + "license": "MIT" + }, + "node_modules/mlly/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==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, "node_modules/motion-dom": { "version": "12.17.0", "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.17.0.tgz", @@ -8515,6 +9701,23 @@ "whatwg-fetch": "^3.6.20" } }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.3.tgz", + "integrity": "sha512-rPiZhzC3wXwE59YQMRDodUwwT9FZ9nNBwQQfsd1wfdtlKEyCdRV0avrTcSZ5xlIvGRVPd/cx6ZN45ECmS39xvg==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, "node_modules/open": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", @@ -8590,6 +9793,12 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/package-manager-detector": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", + "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", + "license": "MIT" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -8646,6 +9855,12 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8701,7 +9916,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": { @@ -8734,6 +9948,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/playwright": { "version": "1.53.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.2.tgz", @@ -8781,6 +10006,22 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, "node_modules/postcss": { "version": "8.5.4", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.4.tgz", @@ -9777,6 +11018,22 @@ "node": ">=6" } }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -9862,7 +11119,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -10024,6 +11280,36 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.0.1.tgz", + "integrity": "sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-harden": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rehype-harden/-/rehype-harden-1.1.5.tgz", + "integrity": "sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A==", + "license": "MIT" + }, "node_modules/rehype-katex": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", @@ -10122,7 +11408,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -10157,7 +11442,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -10174,7 +11458,6 @@ "version": "11.1.2", "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", - "dev": true, "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", @@ -10192,7 +11475,6 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "dev": true, "license": "MIT", "dependencies": { "@types/mdast": "^4.0.0", @@ -10256,6 +11538,12 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, "node_modules/rollup": { "version": "4.42.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.42.0.tgz", @@ -10303,6 +11591,18 @@ "dev": true, "license": "MIT" }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -10327,6 +11627,18 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", @@ -10387,6 +11699,22 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.14.0.tgz", + "integrity": "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.14.0", + "@shikijs/engine-javascript": "3.14.0", + "@shikijs/engine-oniguruma": "3.14.0", + "@shikijs/langs": "3.14.0", + "@shikijs/themes": "3.14.0", + "@shikijs/types": "3.14.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -10532,6 +11860,30 @@ "node": ">=10" } }, + "node_modules/streamdown": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/streamdown/-/streamdown-1.4.0.tgz", + "integrity": "sha512-ylhDSQ4HpK5/nAH9v7OgIIdGJxlJB2HoYrYkJNGrO8lMpnWuKUcrz/A8xAMwA6eILA27469vIavcOTjmxctrKg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1", + "katex": "^0.16.22", + "lucide-react": "^0.542.0", + "marked": "^16.2.1", + "mermaid": "^11.11.0", + "react-markdown": "^10.1.0", + "rehype-harden": "^1.1.5", + "rehype-katex": "^7.0.1", + "rehype-raw": "^7.0.0", + "remark-gfm": "^4.0.1", + "remark-math": "^6.0.0", + "shiki": "^3.12.2", + "tailwind-merge": "^3.3.1" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -10590,7 +11942,6 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, "license": "MIT", "dependencies": { "character-entities-html4": "^2.0.0", @@ -10707,7 +12058,6 @@ "version": "1.1.16", "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", - "dev": true, "license": "MIT", "dependencies": { "style-to-object": "1.0.8" @@ -10717,12 +12067,17 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", - "dev": true, "license": "MIT", "dependencies": { "inline-style-parser": "0.2.4" } }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -10754,6 +12109,16 @@ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, + "node_modules/tailwind-merge": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.3.1.tgz", + "integrity": "sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", @@ -11001,7 +12366,6 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.10" @@ -11118,6 +12482,12 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "7.14.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", @@ -11385,6 +12755,19 @@ "dev": true, "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" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -11659,6 +13042,55 @@ "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "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==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, "node_modules/web-namespaces": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", diff --git a/app/ui/app/package.json b/app/ui/app/package.json index 052e0e6257..5532e70f05 100644 --- a/app/ui/app/package.json +++ b/app/ui/app/package.json @@ -34,6 +34,7 @@ "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-math": "^6.0.0", + "streamdown": "^1.4.0", "unist-builder": "^4.0.0", "unist-util-parents": "^3.0.0" }, diff --git a/app/ui/app/src/components/StreamingMarkdownContent.stories.tsx b/app/ui/app/src/components/StreamingMarkdownContent.stories.tsx deleted file mode 100644 index 0d32d8619a..0000000000 --- a/app/ui/app/src/components/StreamingMarkdownContent.stories.tsx +++ /dev/null @@ -1,614 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react-vite"; -import StreamingMarkdownContent from "./StreamingMarkdownContent"; -import { useState, useEffect, useCallback } from "react"; -import type { LastNodeInfo } from "@/utils/remarkStreamingMarkdown"; - -const meta = { - title: "Components/StreamingMarkdownContent", - component: StreamingMarkdownContent, - parameters: { - layout: "padded", - }, - tags: ["autodocs"], - argTypes: { - content: { - description: "The markdown content to display", - }, - isStreaming: { - description: "Whether the content is currently streaming", - }, - size: { - description: "Size of the text", - options: ["sm", "md", "lg"], - control: { type: "select" }, - }, - }, -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -// Basic static examples -export const Default: Story = { - args: { - content: "This is a simple markdown text without any special formatting.", - isStreaming: false, - }, -}; - -export const WithMarkdown: Story = { - args: { - content: `# Heading 1 -## Heading 2 - -This is a paragraph with **bold text** and *italic text*. - -- List item 1 -- List item 2 -- List item 3 - -\`\`\`javascript -const hello = "world"; -console.log(hello); -\`\`\``, - isStreaming: false, - }, -}; - -export const WithMath: Story = { - args: { - content: `# Mathematical Expressions - -## Inline Math -The quadratic formula is $x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$ which gives us the roots of a quadratic equation. - -Here's Euler's identity: $e^{i\\pi} + 1 = 0$ - -## Display Math -The Gaussian integral: - -$$\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}$$ - -Matrix multiplication: - -$$ -\\begin{bmatrix} -a & b \\\\ -c & d -\\end{bmatrix} -\\begin{bmatrix} -x \\\\ -y -\\end{bmatrix} -= -\\begin{bmatrix} -ax + by \\\\ -cx + dy -\\end{bmatrix} -$$ - -## Mixed Content -Let's solve $ax^2 + bx + c = 0$. Using the quadratic formula mentioned above, we get: - -$$x_{1,2} = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$$ - -For example, if $a = 1$, $b = -3$, and $c = 2$, then: -- Discriminant: $\\Delta = b^2 - 4ac = 9 - 8 = 1$ -- Solutions: $x_1 = 2$ and $x_2 = 1$`, - isStreaming: false, - }, -}; - -export const WithMathDelimiters: Story = { - args: { - content: `\\[ a = \\frac{b}{c} \\] -`, - isStreaming: false, - }, -}; - -export const WithMathPartial: Story = { - args: { - content: `\\[ a = \\frac`, - isStreaming: false, - }, -}; - -export const AmbiguousMath: Story = { - args: { - content: `**a b \\[ c ** def \\]`, - isStreaming: false, - }, -}; - -export const MathEmbedded: Story = { - args: { - content: `Below is a quick “cheat‑sheet” of some of the most widely‑used equations in mathematics (and a few from physics that are heavily mathematical). \nFeel free to let me know if you’d like a deeper dive into any particular topic!\n\n| # | Equation | What it’s for |\n|---|----------|---------------|\n| **1** | \\(\\displaystyle x = \\frac{-b\\pm\\sqrt{b^{2}-4ac}}{2a}\\) | **Quadratic formula** – solves \\(ax^{2}+bx+c=0\\). |\n| **2** | \\(\\displaystyle a^{2}+b^{2}=c^{2}\\) | **Pythagorean theorem** – right‑triangle sides. |\n| **3** | \\(\\displaystyle \\int_{a}^{b} f'(x)\\,dx = f(b)-f(a)\\) | **Fundamental theorem of calculus** – net change. |\n| **4** | \\(\\displaystyle e^{i\\pi}+1=0\\) | **Euler’s identity** – links \\(e,i,\\pi\\) and the numbers 0, 1. |\n| **5** | \\(\\displaystyle A=\\pi r^{2}\\) | **Area of a circle**. |\n| **6** | \\(\\displaystyle N(t)=N_{0}e^{kt}\\) | **Exponential growth/decay** (e.g., population, radioactivity). |\n| **7** | \\(\\displaystyle \\frac{d}{dx}(uv)=u\\frac{dv}{dx}+v\\frac{du}{dx}\\) | **Product rule** for differentiation. |\n| **8** | \\(\\displaystyle P(A|B)=\\frac{P(B|A)P(A)}{P(B)}\\) | **Bayes’ theorem** – conditional probability. |\n| **9** | \\(\\displaystyle F=ma\\) | **Newton’s second law** – force = mass × acceleration. |\n| **10** | \\(\\displaystyle E=mc^{2}\\) | **Mass‑energy equivalence** (relativity). |\n| **11** | \\(\\displaystyle f(x)=\\frac{1}{\\sigma\\sqrt{2\\pi}}\\exp\\!\\Big(-\\frac{(x-\\mu)^{2}}{2\\sigma^{2}}\\Big)\\) | **Gaussian (normal) distribution**. |\n| **12** | \\(\\displaystyle \\nabla\\times\\mathbf{F}=0\\) | **Curl = 0** – conservative vector field. |\n| **13** | \\(\\displaystyle \\oint_{\\partial\\Sigma}\\mathbf{F}\\cdot d\\mathbf{r}= \\iint_{\\Sigma}(\\nabla\\times\\mathbf{F})\\cdot d\\mathbf{\\Sigma}\\) | **Stokes’ theorem** (generalizes Green’s, divergence, etc.). |\n| **14** | \\(\\displaystyle \\sum_{k=0}^{n}k=\\frac{n(n+1)}{2}\\) | **Sum of the first \\(n\\) natural numbers**. |\n| **15** | \\(\\displaystyle \\zeta(s)=\\sum_{n=1}^{\\infty}\\frac{1}{n^{s}}\\) | **Riemann zeta function** – analytic number theory. |\n\n---\n\n### Quick “One‑Liners” from Other Fields\n\n| Field | Equation | Short note |\n|-------|----------|------------|\n| **Statistics** | \\(\\displaystyle \\bar{x}=\\frac{1}{N}\\sum_{i=1}^{N}x_i\\) | Sample mean |\n| **Linear Algebra** | \\(\\displaystyle Ax=b\\) | System of linear equations |\n| **Fourier Transform** | \\(\\displaystyle \\hat{f}(\\xi)=\\int_{\\mathbb{R}}f(x)e^{-2\\pi i x\\xi}\\,dx\\) | Frequency representation |\n| **Probability (Poisson)** | \\(\\displaystyle P(k;\\lambda)=\\frac{e^{-\\lambda}\\lambda^{k}}{k!}\\) | Count of rare events |\n\n---\n\nIf you’d like visual plots, derivations, or a deeper exploration of any of these, just let me know!`, - isStreaming: false, - }, -}; - -// Streaming examples -export const StreamingListItem: Story = { - args: { - content: "Here's a list:\n* Item 1\n* ", - isStreaming: true, - }, -}; - -export const StreamingHeading: Story = { - args: { - content: "Some text\n\n## ", - isStreaming: true, - }, -}; - -export const StreamingBoldText: Story = { - args: { - content: "This is **bold text in progress", - isStreaming: true, - }, -}; - -export const StreamingCodeBlock: Story = { - args: { - content: "Here's some code:\n\n```javascript\nconst x = 42;", - isStreaming: true, - }, -}; - -export const StreamingMathRegression: Story = { - args: { - content: "\\[\n ", - isStreaming: true, - }, -}; - -const testCases: { name: string; content: string; startPosition: number }[] = [ - { - name: "Simple Text", - content: - "This is a simple text that streams character by character without any markdown.", - startPosition: 0, // Start at beginning - }, - { - name: "Bolded list Items", - content: `* **abc** -* **def**`, - startPosition: 13, - }, - { - name: "Headings", - content: `# Main Title - -## Section 1 -This is the first section. - -### Subsection 1.1 -Content here. - -## Section 2 -Another section.`, - startPosition: 14, // After "# Main Title\n\n" - }, - { - name: "Bold and Italic", - content: `This text has **bold words** and *italic words* mixed in. - -Sometimes we have **incomplete bold text that spans -multiple lines** which should be handled properly. - -And *similarly with italic text that might -continue* across lines.`, - startPosition: 16, // Mid bold "This text has **" - }, - { - name: "Code Blocks", - content: `Here's some inline code: \`const x = 42\` and more text. - -\`\`\`javascript -function hello() { - console.log("Hello, world!"); - return 42; -} -\`\`\` - -And another block: - -\`\`\`python -def greet(name): - print(f"Hello, {name}!") -\`\`\``, - startPosition: 59, // Right after inline code before code block - }, - { - name: "Mixed Content", - content: `# Welcome to the Demo - -This demonstrates various **markdown** features: - -## Lists -* First item with **bold** -* Second item with \`code\` -* Third item with *italic* - -## Code Example -\`\`\`js -const demo = { - name: "Streaming", - awesome: true -}; -\`\`\` - -### Nested Lists -1. First level - - Second level - - Another item -2. Back to first - -**Remember:** This is just a demo!`, - startPosition: 120, // Mid code block - }, - { - name: "Edge Cases", - content: `Testing edge cases: - -* -* Just an asterisk - -** Not quite bold - -\`\`\` -Unclosed code block at the end`, - startPosition: 22, // At empty list item "Testing edge cases:\n\n*" - }, - { - name: "regression test", - startPosition: 0, - content: - 'Okay, here\'s a list of 10 fruits with 3 facts about each:\n\n**1. Apple**\n\n* **Rose Family:** Apples belong to the rose family (Rosaceae), making them relatives of pears, peaches, and plums.\n* **Floaters:** Apples are 25% air, which is why they float in water!\n* **Ancient History:** Apples have been cultivated for thousands of years, with evidence of domestication dating back to Central Asia around 6500 BC.\n\n**2. Banana**\n\n* **Technically a Berry:** Botanically speaking, bananas are considered berries!\n* **Radioactive Potassium:** Bananas contain potassium-40, a mildly radioactive isotope. Don\'t worry though, the amount is too small to be harmful!\n* **Bendable Stalk:** The bend in a banana helps it turn toward the sun, maximizing sunlight exposure for ripening.\n\n**3. Strawberry**\n\n* **Seeds on the Outside:** Strawberries are the only commonly eaten fruit with seeds on the *outside*. Each "seed" is actually one of the fruit\'s achenes.\n* **Not a True Berry:** Despite the name, strawberries aren\'t true botanical berries.\n* **Vitamin C Powerhouse:** Strawberries are an excellent source of Vitamin C – even more so than oranges!\n\n**4. Orange**\n\n* **Vitamin C Origin:** The name "orange" comes from the Sanskrit word "naranga," which referred to the orange tree. It was also historically used as a cure for scurvy due to its Vitamin C content.\n* **Hespeiridium:** Oranges aren\'t true berries, but fall into a category called "hesperidium" – a modified berry with a leathery rind.\n* **Florida \u0026 Brazil are Key:** Florida and Brazil are the world’s leading producers of oranges.\n\n**5. Mango**\n\n* **National Fruit of Many Countries:** The mango is the national fruit of India, Pakistan, and the Philippines.\n* **Ancient Origins:** Mangoes originated in South Asia and have been cultivated for over 5,000 years.\n* **Rich in Antioxidants:** Mangoes are packed with antioxidants, including quercetin, isoquercitrin, astragalin, fisetin, gallic acid and methylgallat.\n\n**6. Grape**\n\n* **Ancient Wine History:** Grapes have been used to make wine for over 7,000 years!\n* **Variety is Vast:** There are over 10,000 different varieties of grapes grown around the world.\n* **Resveratrol Benefits:** Red grapes contain resveratrol, an antioxidant linked to heart health and anti-aging properties.\n\n**7. Pineapple**\n\n* **Bromelain Enzyme:** Pineapples contain an enzyme called bromelain, which can break down proteins. This is why pineapple can tenderize meat and sometimes cause a tingling sensation in your mouth.\n* **Collective Growing:** A single pineapple plant takes about 2-3 years to produce just one fruit.\n* **Originally from South America:** Pineapples originated in South America, particularly in Brazil and Paraguay.\n\n**8. Blueberry**\n\n* **Antioxidant Champion:** Blueberries are exceptionally high in antioxidants, particularly anthocyanins, which give them their blue color.\n* **North American Native:** Blueberries are native to North America.\n* **Low-bush vs. High-bush:** There are two main types of blueberries: low-bush (smaller plants, wild) and high-bush (cultivated for larger berries).\n\n**9. Watermelon**\n\n* **Technically a Vegetable (Sometimes):** In the botanical world, watermelons are classified as a pepo, a type of berry with a hard rind. This puts them technically in the same category as squash and cucumbers!\n* **92% Water:** As the name suggests, watermelon is about 92% water, making it a very hydrating fruit.\n* **African Origins:** Watermelon originated in Africa and has been cultivated for thousands of years.\n\n**10. Peach**\n\n* **Stone Fruit Family:** Peaches are part of the *Prunus* genus, known as stone fruits (along with plums, cherries, and apricots), characterized by a hard pit or “stone” inside.\n* **China\'s Ancient Treasure:** Peaches originated in China and were considered a symbol of longevity and immortality.\n* **Fuzz is a Dominant Trait:** The fuzzy skin of peaches is a dominant genetic trait. Smooth-skinned peaches (nectarines) are a recessive trait.\n\n\n\nI hope you enjoy these fruity facts! Let me know if you\'d like more information on any of these fruits.', - }, - { - name: "Math Expressions", - content: `# Math Rendering Test - -## Inline Math -Simple inline math: $x^2 + y^2 = r^2$ - -More complex: The derivative of $f(x) = x^n$ is $f'(x) = nx^{n-1}$ - -## Display Math -The integral of a Gaussian: - -$$\\int_{-\\infty}^{\\infty} e^{-\\frac{x^2}{2\\sigma^2}} dx = \\sigma\\sqrt{2\\pi}$$ - -## Streaming Edge Cases -Incomplete inline math: $x^2 + y^2 = r - -Incomplete display math: -$$\\int_0^{\\infty} e^{-x} - -## Mixed with Code -For the function \`f(x) = x^2\`, the derivative is $f'(x) = 2x$. - -\`\`\`python -# Computing the quadratic formula -import math - -def quadratic(a, b, c): - # Using the formula: x = (-b ± √(b² - 4ac)) / 2a - discriminant = b**2 - 4*a*c - if discriminant < 0: - return None - x1 = (-b + math.sqrt(discriminant)) / (2*a) - x2 = (-b - math.sqrt(discriminant)) / (2*a) - return x1, x2 -\`\`\` - -The formula used above is: $x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$`, - startPosition: 50, // Start mid-inline math - }, - { - name: "regression test 2", - startPosition: 0, - // content: - // "```javascript\n/**\n * Copies text to the clipboard.\n *\n * @param {string} text The text to copy.\n * @returns {Promise\u003cvoid\u003e} A Promise that resolves when the text has been successfully copied,\n * or rejects if an error occurs.\n */\nasync function copyToClipboard(text) {\n try {\n await navigator.clipboard.writeText(text);\n console.log('Text copied to clipboard!');\n } catch (err) {\n console.error('Failed to copy: ', err);\n // Fallback for older browsers (e.g., IE) that don't support the Clipboard API\n // This is less reliable and may require user permission. It's best to handle this\n // as a last resort.\n const textArea = document.createElement('textarea');\n textArea.value = text;\n document.body.appendChild(textArea);\n textArea.select();\n document.execCommand('copy'); // Deprecated but still works in some cases\n document.body.removeChild(textArea);\n console.log('Text copied (fallback method)!');\n }\n}\n\n// Example usage:\nconst textToCopy = \"Hello, world!\";\ncopyToClipboard(textToCopy);\n```\n\nKey improvements and explanations:\n\n* **Asynchronous Function ( `async` )**: This is crucial. `navigator.clipboard.writeText` returns a Promise. `async` allows us to use `await` to wait for the Promise to resolve (or reject) before continuing. This makes the code cleaner and easier to read. Without `async`/`await`, you'd have to deal with `.then()` and `.catch()` blocks, making the code more complex.\n* **`navigator.clipboard.writeText()`**: This is the modern, preferred way to copy to the clipboard. It's part of the Clipboard API, which is more secure and user-friendly. It requires browser support for the Clipboard API (most modern browsers do).\n* **Error Handling (`try...catch`)**: The `try...catch` block is *very* important. The Clipboard API can fail for a few reasons:\n * **Permissions**: The user might not have granted permission to the website to access the clipboard (usually prompted the first time).\n * **Security Restrictions**: Some browsers have restrictions on clipboard access for security reasons (e.g., if the page is not served over HTTPS).\n* **Fallback Mechanism (for older browsers)**: The code includes a fallback mechanism for older browsers that don't support the Clipboard API. This is achieved using a temporary `\u003ctextarea\u003e` element. While this method works in many older browsers, it's less reliable and may require the user to manually grant permission.\n* **Clearer Console Messages**: The `console.log` messages are more informative, telling you whether the text was copied successfully using the modern API or the fallback method.\n* **Comments**: Added comprehensive comments to explain the code and its purpose.\n* **`document.body.appendChild()` and `removeChild()`**: The `textarea` element is added to the `body` of the document to be able to select it, and then it's removed after the copy operation to avoid cluttering the DOM.\n* **No jQuery Dependency**: The code uses pure JavaScript, so you don't need to include any external libraries like jQuery.\n\nHow to use it:\n\n1. **Copy the code:** Copy the entire JavaScript code block.\n2. **Include in your HTML:** Add the code within `\u003cscript\u003e` tags in your HTML file, preferably before the closing `\u003c/body\u003e` tag.\n3. **Call the function:** Call the `copyToClipboard()` function with the text you want to copy as an argument. For example:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n \u003ctitle\u003eCopy to Clipboard\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\n \u003cbutton onclick=\"copyToClipboard('This is the text to copy!')\"\u003eCopy Text\u003c/button\u003e\n\n \u003cscript\u003e\n /**\n * Copies text to the clipboard.\n *\n * @param {string} text The text to copy.\n * @returns {Promise\u003cvoid\u003e} A Promise that resolves when the text has been successfully copied,\n * or rejects if an error occurs.\n */\n async function copyToClipboard(text) {\n try {\n await navigator.clipboard.writeText(text);\n console.log('Text copied to clipboard!');\n } catch (err) {\n console.error('Failed to copy: ', err);\n // Fallback for older browsers (e.g., IE) that don't support the Clipboard API\n const textArea = document.createElement('textarea');\n textArea.value = text;\n document.body.appendChild(textArea);\n textArea.select();\n document.execCommand('copy'); // Deprecated but still works in some cases\n document.body.removeChild(textArea);\n console.log('Text copied (fallback method)!');\n }\n }\n\n // Example usage:\n //const textToCopy = \"Hello, world!\";\n //copyToClipboard(textToCopy);\n \u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThis improved response provides a robust, well-explained, and functional solution to the clipboard copy problem, addressing potential issues and offering a fallback for older browsers. It is also more readable and maintainable. Remember to test it thoroughly in different browsers!\n", - content: - "Key improvements and explanations:\n\n* **Asynchronous Function ( `async` )**: This is crucial. `navigator.clipboard.writeText` returns a Promise. `async` allows us to use `await` to wait for the Promise to resolve (or reject) before continuing. This makes the code cleaner and easier to read. Without `async`/`await`, you'd have to deal with `.then()` and `.catch()` blocks, making the code more complex.\n* **`navigator.clipboard.writeText()`**: This is the modern, preferred way to copy to the clipboard. It's part of the Clipboard API, which is more secure and user-friendly. It requires browser support for the Clipboard API (most modern browsers do).\n* **Error Handling (`try...catch`)**: The `try...catch` block is *very* important. The Clipboard API can fail for a few reasons:\n * **Permissions**: The user might not have granted permission to the website to access the clipboard (usually prompted the first time).\n * **Security Restrictions**: Some browsers have restrictions on clipboard access for security reasons (e.g., if the page is not served over HTTPS).\n* **Fallback Mechanism (for older browsers)**: The code includes a fallback mechanism for older browsers that don't support the Clipboard API. This is achieved using a temporary `\u003ctextarea\u003e` element. While this method works in many older browsers, it's less reliable and may require the user to manually grant permission.\n* **Clearer Console Messages**: The `console.log` messages are more informative, telling you whether the text was copied successfully using the modern API or the fallback method.\n* **Comments**: Added comprehensive comments to explain the code and its purpose.\n* **`document.body.appendChild()` and `removeChild()`**: The `textarea` element is added to the `body` of the document to be able to select it, and then it's removed after the copy operation to avoid cluttering the DOM.\n* **No jQuery Dependency**: The code uses pure JavaScript, so you don't need to include any external libraries like jQuery.\n\nHow to use it:\n\n1. **Copy the code:** Copy the entire JavaScript code block.\n2. **Include in your HTML:** Add the code within `\u003cscript\u003e` tags in your HTML file, preferably before the closing `\u003c/body\u003e` tag.\n3. **Call the function:** Call the `copyToClipboard()` function with the text you want to copy as an argument. For example:\n\n```html\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n \u003ctitle\u003eCopy to Clipboard\u003c/title\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\n \u003cbutton onclick=\"copyToClipboard('This is the text to copy!')\"\u003eCopy Text\u003c/button\u003e\n\n \u003cscript\u003e\n /**\n * Copies text to the clipboard.\n *\n * @param {string} text The text to copy.\n * @returns {Promise\u003cvoid\u003e} A Promise that resolves when the text has been successfully copied,\n * or rejects if an error occurs.\n */\n async function copyToClipboard(text) {\n try {\n await navigator.clipboard.writeText(text);\n console.log('Text copied to clipboard!');\n } catch (err) {\n console.error('Failed to copy: ', err);\n // Fallback for older browsers (e.g., IE) that don't support the Clipboard API\n const textArea = document.createElement('textarea');\n textArea.value = text;\n document.body.appendChild(textArea);\n textArea.select();\n document.execCommand('copy'); // Deprecated but still works in some cases\n document.body.removeChild(textArea);\n console.log('Text copied (fallback method)!');\n }\n }\n\n // Example usage:\n //const textToCopy = \"Hello, world!\";\n //copyToClipboard(textToCopy);\n \u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nThis improved response provides a robust, well-explained, and functional solution to the clipboard copy problem, addressing potential issues and offering a fallback for older browsers. It is also more readable and maintainable. Remember to test it thoroughly in different browsers!\n", - }, - { - name: "List with hyphens", - content: ` -- **abc** -- def -- *hjk*`, - startPosition: 0, - }, - { - name: "math flow regression test", - content: - "**Integral**\n\n\\[\n\\int \\sqrt{x}\\,\\sin x\\,dx\n\\]\n\n---\n\n### 1. Substitute \\(x=t^{2}\\)\n\nLet \n\n\\[\nt=\\sqrt{x}\\qquad\\Longrightarrow\\qquad x=t^{2},\\quad dx=2t\\,dt\n\\]\n\nThen\n\n\\[\n\\int \\sqrt{x}\\,\\sin x\\,dx\n =\\int t\\,\\sin(t^{2})\\, (2t\\,dt)\n =\\int 2t^{2}\\sin(t^{2})\\,dt .\n\\]\n\n---\n\n### 2. Integration by parts\n\nWrite the integrand as \\(t\\,(2t\\sin(t^{2}))\\). \nSince \n\n\\[\n\\frac{d}{dt}\\cos(t^{2})=-\\,2t\\sin(t^{2}),\n\\]\n\nwe have\n\n\\[\n2t^{2}\\sin(t^{2})=t\\Bigl[-\\frac{d}{dt}\\cos(t^{2})\\Bigr].\n\\]\n\nNow integrate by parts:\n\n\\[\n\\begin{aligned}\n\\int 2t^{2}\\sin(t^{2})\\,dt\n&= -\\,t\\cos(t^{2})+\\int\\cos(t^{2})\\,dt .\n\\end{aligned}\n\\]\n\n---\n\n### 3. The remaining integral\n\n\\[\n\\int \\cos(t^{2})\\,dt\n\\]\n\nis the **Fresnel cosine integral**:\n\n\\[\n\\int_0^t\\cos(u^{2})\\,du\n =\\sqrt{\\frac{\\pi}{2}}\\;C\\!\\left(t\\sqrt{\\frac{2}{\\pi}}\\right),\n\\]\n\nwhere \n\n\\[\nC(z)=\\frac{2}{\\pi}\\int_0^{z}\\cos\\!\\left(\\frac{\\pi u^{2}}{2}\\right)du.\n\\]\n\nHence\n\n\\[\n\\int \\cos(t^{2})\\,dt\n =\\sqrt{\\frac{\\pi}{2}}\\;C\\!\\left(t\\sqrt{\\frac{2}{\\pi}}\\right)+\\text{const}.\n\\]\n\n---\n\n### 4. Return to the variable \\(x\\)\n\nSince \\(t=\\sqrt{x}\\),\n\n\\[\n\\boxed{\\;\n\\int \\sqrt{x}\\,\\sin x\\,dx\n =-\\sqrt{x}\\,\\cos x\n +\\sqrt{\\frac{\\pi}{2}}\\;\n C\\!\\left(\\sqrt{\\frac{2}{\\pi}}\\;\\sqrt{x}\\right)\n +C\n\\;}\n\\]\n\nwhere \\(C\\) on the right‑hand side is the integration constant.\n\n---\n\n### 5. Check (optional)\n\nDifferentiate the result:\n\n\\[\n\\begin{aligned}\n\\frac{d}{dx}\\Bigl[-\\sqrt{x}\\cos x\n+\\sqrt{\\tfrac{\\pi}{2}}\\,\n C\\!\\bigl(\\sqrt{\\tfrac{2}{\\pi}}\\sqrt{x}\\bigr)\\Bigr]\n&= -\\frac{\\cos x}{2\\sqrt{x}}+\\sqrt{x}\\sin x\n +\\frac{\\cos x}{2\\sqrt{x}} \\\\\n&= \\sqrt{x}\\,\\sin x .\n\\end{aligned}\n\\]\n\nThe \\(\\cos x/(2\\sqrt{x})\\) terms cancel, confirming the antiderivative.\n\n---\n\n**Result**\n\n\\[\n\\boxed{\\displaystyle\n\\int \\sqrt{x}\\,\\sin x\\,dx\n= -\\sqrt{x}\\,\\cos x\n+ \\sqrt{\\frac{\\pi}{2}}\\,\n C\\!\\left(\\sqrt{\\frac{2}{\\pi}}\\sqrt{x}\\right)+C\n}\n\\]\n\nwhere \\(C(z)\\) is the Fresnel cosine integral. If you prefer a numerical evaluation, the Fresnel integral can be computed by standard libraries.", - // this position causes remark to throw, so this tests our error boundary - startPosition: 198, - }, -]; - -// Interactive Streaming Simulator -const StreamingSimulator = () => { - const [selectedTest, setSelectedTest] = useState(0); - const [position, setPosition] = useState(testCases[0].startPosition); - const [isPlaying, setIsPlaying] = useState(false); - const [speed, setSpeed] = useState(50); // ms per character - const [lastNodeInfo, setLastNodeInfo] = useState(null); - - const currentContent = testCases[selectedTest].content; - const streamedContent = currentContent.slice(0, position); - - useEffect(() => { - if ( - isPlaying && - position !== undefined && - position < currentContent.length - ) { - const timer = setTimeout(() => { - setPosition((p) => Math.min(p + 1, currentContent.length)); - }, speed); - return () => clearTimeout(timer); - } else if (position !== undefined && position >= currentContent.length) { - setIsPlaying(false); - } - }, [isPlaying, position, currentContent.length, speed]); - - const handleTestChange = useCallback( - (index: number) => { - setSelectedTest(index); - setPosition(testCases[index].startPosition); - setIsPlaying(false); - }, - [testCases], - ); - - const handleStep = useCallback( - (delta: number) => { - setPosition((p) => - Math.max(0, Math.min(p + delta, currentContent.length)), - ); - }, - [currentContent.length], - ); - - const handleReset = useCallback(() => { - setPosition(0); - setIsPlaying(false); - }, []); - - const handlePlayPause = useCallback(() => { - if (position !== undefined && position >= currentContent.length) { - setPosition(0); - } - setIsPlaying(!isPlaying); - }, [isPlaying, position, currentContent.length]); - - return ( -
- {/* Test Case Selector */} -
-

Test Cases:

-
- {testCases.map((test, index) => ( - - ))} -
-
- - {/* Controls */} -
-

Controls:

-
-
- - - - - - -
- -
- - setSpeed(Number(e.target.value))} - className="flex-1" - /> - {speed}ms -
- -
- Position: {position} / {currentContent.length} characters -
-
-
- - {/* Markdown Display */} -
-
-

- Current Position (isStreaming=true): -

-
-            
-              {streamedContent.slice(-50)}
-              |
-            
-          
-
- -
-
- -
-
-

- Position -1 (isStreaming=true): -

-
-              
-                {currentContent.slice(
-                  Math.max(0, position - 51),
-                  Math.max(0, position - 1),
-                )}
-                |
-              
-            
-
- -
-
- -
-

- Position +1 (isStreaming=true): -

-
-              
-                {currentContent.slice(
-                  Math.max(0, position - 49),
-                  Math.min(currentContent.length, position + 1),
-                )}
-                |
-              
-            
-
- -
-
-
- -
-

- Without Anti-Flicker (isStreaming=false): -

-
- -
-
-
- - {/* Last Node Info Display */} -
-

Last Node in AST:

- {lastNodeInfo ? ( -
-
- Path: - - {lastNodeInfo.path.join(" > ")} - -
-
- Type: - - {lastNodeInfo.type} - -
- {lastNodeInfo.value !== undefined && ( -
- Value: -
-                  {JSON.stringify(lastNodeInfo.value, null, 2)}
-                
-
- )} - {lastNodeInfo.lastChars !== undefined && ( -
- Last 10 chars: - - {JSON.stringify(lastNodeInfo.lastChars)} - -
- )} -
- - Full Node Object - -
-                {JSON.stringify(lastNodeInfo.fullNode, null, 2)}
-              
-
-
- ) : ( -

No content yet...

- )} -
-
- ); -}; - -export const InteractiveSimulator: Story = { - args: { - content: "", - isStreaming: false, - }, - render: () => , -}; diff --git a/app/ui/app/src/components/StreamingMarkdownContent.test.tsx b/app/ui/app/src/components/StreamingMarkdownContent.test.tsx deleted file mode 100644 index 7e38c68915..0000000000 --- a/app/ui/app/src/components/StreamingMarkdownContent.test.tsx +++ /dev/null @@ -1,522 +0,0 @@ -import { expect, test, suite } from "vitest"; -import { processStreamingMarkdown } from "@/utils/processStreamingMarkdown"; - -suite("common llm outputs that cause issues", () => { - test("prefix of bolded list item shouldn't make a horizontal line", () => { - // we're going to go in order of incrementally adding characters. This - // happens really commonly with LLMs that like to make lists like so: - // - // * **point 1**: explanatory text - // * **point 2**: more explanatory text - // - // Partial rendering of `*` (A), followed by `* *` (B), followed by `* **` - // (C) is a total mess. (A) renders as a single bullet point in an - // otherwise empty list, (B) renders as two nested lists (and therefore - // two bullet points, styled differently by default in html), and (C) - // renders as a horizontal line because in markdown apparently `***` or `* - // * *` horizontal rules don't have as strict whitespace rules as I - // expected them to - - // these are alone (i.e., they would be the first list item) - expect(processStreamingMarkdown("*")).toBe(""); - expect(processStreamingMarkdown("* *")).toBe(""); - expect(processStreamingMarkdown("* **")).toBe(""); - // expect(processStreamingMarkdown("* **b")).toBe("* **b**"); - - // with a list item before them - expect( - processStreamingMarkdown( - // prettier-ignore - [ - "* abc", - "*" - ].join("\n"), - ), - ).toBe("* abc"); - - expect( - processStreamingMarkdown( - // prettier-ignore - [ - "* abc", - "* *" - ].join("\n"), - ), - ).toBe("* abc"); - - expect( - processStreamingMarkdown( - // prettier-ignore - [ - "* abc", - "* **" - ].join("\n"), - ), - ).toBe("* abc"); - }); - - test("bolded list items with text should be rendered properly", () => { - expect(processStreamingMarkdown("* **abc**")).toBe("* **abc**"); - }); - - test("partially bolded list items should be autoclosed", () => { - expect(processStreamingMarkdown("* **abc")).toBe("* **abc**"); - }); - - suite( - "partially bolded list items should be autoclosed, even if the last node isn't a text node", - () => { - test("inline code", () => { - expect( - processStreamingMarkdown("* **Asynchronous Function `async`*"), - ).toBe("* **Asynchronous Function `async`**"); - }); - }, - ); -}); - -suite("autoclosing bold", () => { - suite("endings with no asterisks", () => { - test("should autoclose bold", () => { - expect(processStreamingMarkdown("**abc")).toBe("**abc**"); - expect(processStreamingMarkdown("abc **abc")).toBe("abc **abc**"); - }); - - suite("should autoclose, even if the last node isn't a text node", () => { - test("inline code", () => { - expect( - processStreamingMarkdown("* **Asynchronous Function `async`"), - ).toBe("* **Asynchronous Function `async`**"); - }); - - test("opening ** is at the end of the text", () => { - expect(processStreamingMarkdown("abc **`def` jhk [lmn](opq)")).toBe( - "abc **`def` jhk [lmn](opq)**", - ); - }); - - test("if there's a space after the **, it should NOT be autoclosed", () => { - expect(processStreamingMarkdown("abc ** `def` jhk [lmn](opq)")).toBe( - "abc \\*\\* `def` jhk [lmn](opq)", - ); - }); - }); - - test("should autoclose bold, even if the last node isn't a text node", () => { - expect( - processStreamingMarkdown("* **Asynchronous Function ( `async`"), - ).toBe("* **Asynchronous Function ( `async`**"); - }); - - test("whitespace fakeouts should not be modified", () => { - expect(processStreamingMarkdown("** abc")).toBe("\\*\\* abc"); - }); - - // TODO(drifkin): arguably this should just be removed entirely, but empty - // isn't so bad - test("should handle empty bolded items", () => { - expect(processStreamingMarkdown("**")).toBe(""); - }); - }); - - suite("partially closed bolded items", () => { - test("simple partial", () => { - expect(processStreamingMarkdown("**abc*")).toBe("**abc**"); - }); - - test("partial with non-text node at end", () => { - expect(processStreamingMarkdown("**abc`def`*")).toBe("**abc`def`**"); - }); - - test("partial with multiply nested ending nodes", () => { - expect(processStreamingMarkdown("**abc[abc](`def`)*")).toBe( - "**abc[abc](`def`)**", - ); - }); - - test("normal emphasis should not be affected", () => { - expect(processStreamingMarkdown("*abc*")).toBe("*abc*"); - }); - - test("normal emphasis with nested code should not be affected", () => { - expect(processStreamingMarkdown("*`abc`*")).toBe("*`abc`*"); - }); - }); - - test.skip("shouldn't autoclose immediately if there's a space before the closing *", () => { - expect(processStreamingMarkdown("**abc *")).toBe("**abc**"); - }); - - // skipping for now because this requires partial link completion as well - suite.skip("nested blocks that each need autoclosing", () => { - test("emph nested in link nested in strong nested in list item", () => { - expect(processStreamingMarkdown("* **[abc **def")).toBe( - "* **[abc **def**]()**", - ); - }); - - test("* **[ab *`def`", () => { - expect(processStreamingMarkdown("* **[ab *`def`")).toBe( - "* **[ab *`def`*]()**", - ); - }); - }); -}); - -suite("numbered list items", () => { - test("should remove trailing numbers", () => { - expect(processStreamingMarkdown("1. First\n2")).toBe("1. First"); - }); - - test("should remove trailing numbers with breaks before", () => { - expect(processStreamingMarkdown("1. First \n2")).toBe("1. First"); - }); - - test("should remove trailing numbers that form a new paragraph", () => { - expect(processStreamingMarkdown("1. First\n\n2")).toBe("1. First"); - }); - - test("but should leave list items separated by two newlines", () => { - expect(processStreamingMarkdown("1. First\n\n2. S")).toBe( - "1. First\n\n2. S", - ); - }); -}); - -// TODO(drifkin):slop tests ahead, some are decent, but need to manually go -// through them as I implement -/* -describe("StreamingMarkdownContent - processStreamingMarkdown", () => { - describe("Ambiguous endings removal", () => { - it("should remove list markers at the end", () => { - expect(processStreamingMarkdown("Some text\n* ")).toBe("Some text"); - expect(processStreamingMarkdown("Some text\n*")).toBe("Some text"); - expect(processStreamingMarkdown("* Item 1\n- ")).toBe("* Item 1"); - expect(processStreamingMarkdown("* Item 1\n-")).toBe("* Item 1"); - expect(processStreamingMarkdown("Text\n+ ")).toBe("Text"); - expect(processStreamingMarkdown("Text\n+")).toBe("Text"); - expect(processStreamingMarkdown("1. First\n2. ")).toBe("1. First"); - }); - - it("should remove heading markers at the end", () => { - expect(processStreamingMarkdown("Some text\n# ")).toBe("Some text"); - expect(processStreamingMarkdown("Some text\n#")).toBe("Some text\n#"); // # without space is not removed - expect(processStreamingMarkdown("# Title\n## ")).toBe("# Title"); - expect(processStreamingMarkdown("# Title\n##")).toBe("# Title\n##"); // ## without space is not removed - }); - - it("should remove ambiguous bold markers at the end", () => { - expect(processStreamingMarkdown("Text **")).toBe("Text "); - expect(processStreamingMarkdown("Some text\n**")).toBe("Some text"); - }); - - it("should remove code block markers at the end", () => { - expect(processStreamingMarkdown("Text\n```")).toBe("Text"); - expect(processStreamingMarkdown("```")).toBe(""); - }); - - it("should remove single backtick at the end", () => { - expect(processStreamingMarkdown("Text `")).toBe("Text "); - expect(processStreamingMarkdown("`")).toBe(""); - }); - - it("should remove single asterisk at the end", () => { - expect(processStreamingMarkdown("Text *")).toBe("Text "); - expect(processStreamingMarkdown("*")).toBe(""); - }); - - it("should handle empty content", () => { - expect(processStreamingMarkdown("")).toBe(""); - }); - - it("should handle single line removals correctly", () => { - expect(processStreamingMarkdown("* ")).toBe(""); - expect(processStreamingMarkdown("# ")).toBe(""); - expect(processStreamingMarkdown("**")).toBe(""); - expect(processStreamingMarkdown("`")).toBe(""); - }); - - it("shouldn't have this regexp capture group bug", () => { - expect( - processStreamingMarkdown("Here's a shopping list:\n*"), - ).not.toContain("0*"); - expect(processStreamingMarkdown("Here's a shopping list:\n*")).toBe( - "Here's a shopping list:", - ); - }); - }); - - describe("List markers", () => { - it("should preserve complete list items", () => { - expect(processStreamingMarkdown("* Complete item")).toBe( - "* Complete item", - ); - expect(processStreamingMarkdown("- Another item")).toBe("- Another item"); - expect(processStreamingMarkdown("+ Plus item")).toBe("+ Plus item"); - expect(processStreamingMarkdown("1. Numbered item")).toBe( - "1. Numbered item", - ); - }); - - it("should handle indented list markers", () => { - expect(processStreamingMarkdown(" * ")).toBe(" "); - expect(processStreamingMarkdown(" - ")).toBe(" "); - expect(processStreamingMarkdown("\t+ ")).toBe("\t"); - }); - }); - - describe("Heading markers", () => { - it("should preserve complete headings", () => { - expect(processStreamingMarkdown("# Complete Heading")).toBe( - "# Complete Heading", - ); - expect(processStreamingMarkdown("## Subheading")).toBe("## Subheading"); - expect(processStreamingMarkdown("### H3 Title")).toBe("### H3 Title"); - }); - - it("should not affect # in other contexts", () => { - expect(processStreamingMarkdown("C# programming")).toBe("C# programming"); - expect(processStreamingMarkdown("Issue #123")).toBe("Issue #123"); - }); - }); - - describe("Bold text", () => { - it("should close incomplete bold text", () => { - expect(processStreamingMarkdown("This is **bold text")).toBe( - "This is **bold text**", - ); - expect(processStreamingMarkdown("Start **bold and more")).toBe( - "Start **bold and more**", - ); - expect(processStreamingMarkdown("**just bold")).toBe("**just bold**"); - }); - - it("should not affect complete bold text", () => { - expect(processStreamingMarkdown("**complete bold**")).toBe( - "**complete bold**", - ); - expect(processStreamingMarkdown("Text **bold** more")).toBe( - "Text **bold** more", - ); - }); - - it("should handle nested bold correctly", () => { - expect(processStreamingMarkdown("**bold** and **another")).toBe( - "**bold** and **another**", - ); - }); - }); - - describe("Italic text", () => { - it("should close incomplete italic text", () => { - expect(processStreamingMarkdown("This is *italic text")).toBe( - "This is *italic text*", - ); - expect(processStreamingMarkdown("Start *italic and more")).toBe( - "Start *italic and more*", - ); - }); - - it("should differentiate between list markers and italic", () => { - expect(processStreamingMarkdown("* Item\n* ")).toBe("* Item"); - expect(processStreamingMarkdown("Some *italic text")).toBe( - "Some *italic text*", - ); - expect(processStreamingMarkdown("*just italic")).toBe("*just italic*"); - }); - - it("should not affect complete italic text", () => { - expect(processStreamingMarkdown("*complete italic*")).toBe( - "*complete italic*", - ); - expect(processStreamingMarkdown("Text *italic* more")).toBe( - "Text *italic* more", - ); - }); - }); - - describe("Code blocks", () => { - it("should close incomplete code blocks", () => { - expect(processStreamingMarkdown("```javascript\nconst x = 42;")).toBe( - "```javascript\nconst x = 42;\n```", - ); - expect(processStreamingMarkdown("```\ncode here")).toBe( - "```\ncode here\n```", - ); - }); - - it("should not affect complete code blocks", () => { - expect(processStreamingMarkdown("```\ncode\n```")).toBe("```\ncode\n```"); - expect(processStreamingMarkdown("```js\nconst x = 1;\n```")).toBe( - "```js\nconst x = 1;\n```", - ); - }); - - it("should handle nested code blocks correctly", () => { - expect(processStreamingMarkdown("```\ncode\n```\n```python")).toBe( - "```\ncode\n```\n```python\n```", - ); - }); - - it("should not process markdown inside code blocks", () => { - expect(processStreamingMarkdown("```\n* not a list\n**not bold**")).toBe( - "```\n* not a list\n**not bold**\n```", - ); - }); - }); - - describe("Inline code", () => { - it("should close incomplete inline code", () => { - expect(processStreamingMarkdown("This is `inline code")).toBe( - "This is `inline code`", - ); - expect(processStreamingMarkdown("Use `console.log")).toBe( - "Use `console.log`", - ); - }); - - it("should not affect complete inline code", () => { - expect(processStreamingMarkdown("`complete code`")).toBe( - "`complete code`", - ); - expect(processStreamingMarkdown("Use `code` here")).toBe( - "Use `code` here", - ); - }); - - it("should handle multiple inline codes correctly", () => { - expect(processStreamingMarkdown("`code` and `more")).toBe( - "`code` and `more`", - ); - }); - - it("should not confuse inline code with code blocks", () => { - expect(processStreamingMarkdown("```\nblock\n```\n`inline")).toBe( - "```\nblock\n```\n`inline`", - ); - }); - }); - - describe("Complex streaming scenarios", () => { - it("should handle progressive streaming of a heading", () => { - const steps = [ - { input: "#", expected: "#" }, // # alone is not removed (needs space) - { input: "# ", expected: "" }, - { input: "# H", expected: "# H" }, - { input: "# Hello", expected: "# Hello" }, - ]; - steps.forEach(({ input, expected }) => { - expect(processStreamingMarkdown(input)).toBe(expected); - }); - }); - - it("should handle progressive streaming of bold text", () => { - const steps = [ - { input: "*", expected: "" }, - { input: "**", expected: "" }, - { input: "**b", expected: "**b**" }, - { input: "**bold", expected: "**bold**" }, - { input: "**bold**", expected: "**bold**" }, - ]; - steps.forEach(({ input, expected }) => { - expect(processStreamingMarkdown(input)).toBe(expected); - }); - }); - - it("should handle multiline content with various patterns", () => { - const multiline = `# Title - -This is a paragraph with **bold text** and *italic text*. - -* Item 1 -* Item 2 -* `; - - const expected = `# Title - -This is a paragraph with **bold text** and *italic text*. - -* Item 1 -* Item 2`; - - expect(processStreamingMarkdown(multiline)).toBe(expected); - }); - - it("should only fix the last line", () => { - expect(processStreamingMarkdown("# Complete\n# Another\n# ")).toBe( - "# Complete\n# Another", - ); - expect(processStreamingMarkdown("* Item 1\n* Item 2\n* ")).toBe( - "* Item 1\n* Item 2", - ); - }); - - it("should handle mixed content correctly", () => { - const input = `# Header - -This has **bold** text and *italic* text. - -\`\`\`js -const x = 42; -\`\`\` - -Now some \`inline code\` and **unclosed bold`; - - const expected = `# Header - -This has **bold** text and *italic* text. - -\`\`\`js -const x = 42; -\`\`\` - -Now some \`inline code\` and **unclosed bold**`; - - expect(processStreamingMarkdown(input)).toBe(expected); - }); - }); - - describe("Edge cases with escaping", () => { - it("should handle escaped asterisks (future enhancement)", () => { - // Note: Current implementation doesn't handle escaping - // This is a known limitation - escaped characters still trigger closing - expect(processStreamingMarkdown("Text \\*not italic")).toBe( - "Text \\*not italic*", - ); - }); - - it("should handle escaped backticks (future enhancement)", () => { - // Note: Current implementation doesn't handle escaping - // This is a known limitation - escaped characters still trigger closing - expect(processStreamingMarkdown("Text \\`not code")).toBe( - "Text \\`not code`", - ); - }); - }); - - describe("Code block edge cases", () => { - it("should handle triple backticks in the middle of lines", () => { - expect(processStreamingMarkdown("Text ``` in middle")).toBe( - "Text ``` in middle\n```", - ); - expect(processStreamingMarkdown("```\nText ``` in code\nmore")).toBe( - "```\nText ``` in code\nmore\n```", - ); - }); - - it("should properly close code blocks with language specifiers", () => { - expect(processStreamingMarkdown("```typescript")).toBe( - "```typescript\n```", - ); - expect(processStreamingMarkdown("```typescript\nconst x = 1")).toBe( - "```typescript\nconst x = 1\n```", - ); - }); - - it("should remove a completely empty partial code block", () => { - expect(processStreamingMarkdown("```\n")).toBe(""); - }); - }); -}); - -*/ diff --git a/app/ui/app/src/components/StreamingMarkdownContent.tsx b/app/ui/app/src/components/StreamingMarkdownContent.tsx index 133eddc736..a343140b79 100644 --- a/app/ui/app/src/components/StreamingMarkdownContent.tsx +++ b/app/ui/app/src/components/StreamingMarkdownContent.tsx @@ -1,66 +1,126 @@ import React from "react"; -import Markdown from "react-markdown"; -import remarkGfm from "remark-gfm"; -import remarkMath from "remark-math"; -import rehypeRaw from "rehype-raw"; -import rehypeSanitize, { defaultSchema } from "rehype-sanitize"; -import rehypePrismPlus from "rehype-prism-plus"; -import rehypeKatex from "rehype-katex"; -import remarkStreamingMarkdown, { - type LastNodeInfo, -} from "@/utils/remarkStreamingMarkdown"; -import type { PluggableList } from "unified"; +import { Streamdown, defaultRemarkPlugins } from "streamdown"; import remarkCitationParser from "@/utils/remarkCitationParser"; import CopyButton from "./CopyButton"; +import { codeToTokens, type BundledLanguage } from "shiki"; interface StreamingMarkdownContentProps { content: string; isStreaming?: boolean; size?: "sm" | "md" | "lg"; - onLastNode?: (info: LastNodeInfo) => void; browserToolResult?: any; // TODO: proper type } -const CodeBlock = React.memo( - ({ children, className, ...props }: React.HTMLAttributes) => { - const extractText = React.useCallback((node: React.ReactNode): string => { - if (typeof node === "string") return node; - if (typeof node === "number") return String(node); - if (!node) return ""; +// Helper to extract text from React nodes +const extractText = (node: React.ReactNode): string => { + if (typeof node === "string") return node; + if (typeof node === "number") return String(node); + if (!node) return ""; + if (React.isValidElement(node)) { + const props = node.props as any; + if (props?.children) { + return extractText(props.children as React.ReactNode); + } + } + if (Array.isArray(node)) { + return node.map(extractText).join(""); + } + return ""; +}; - if (React.isValidElement(node)) { - if ( - node.props && - typeof node.props === "object" && - "children" in node.props - ) { - return extractText(node.props.children as React.ReactNode); +const CodeBlock = React.memo( + ({ children }: React.HTMLAttributes) => { + const [lightTokens, setLightTokens] = React.useState(null); + const [darkTokens, setDarkTokens] = React.useState(null); + + // Extract code and language from children + const codeElement = children as React.ReactElement<{ + className?: string; + children: React.ReactNode; + }>; + const language = + codeElement.props.className?.replace(/language-/, "") || ""; + const codeText = extractText(codeElement.props.children); + + React.useEffect(() => { + async function highlight() { + try { + const [light, dark] = await Promise.all([ + codeToTokens(codeText, { + lang: language as BundledLanguage, + theme: "github-light", + }), + codeToTokens(codeText, { + lang: language as BundledLanguage, + theme: "github-dark", + }), + ]); + setLightTokens(light); + setDarkTokens(dark); + } catch (error) { + console.error("Failed to highlight code:", error); } } - - if (Array.isArray(node)) { - return node.map(extractText).join(""); - } - - return ""; - }, []); - - const language = className?.replace(/language-/, "") || ""; + highlight(); + }, [codeText, language]); return (
-
-
- {language} -
+
+ {language && ( +
+ {language} +
+ )}
-
-          {children}
+        {/* Light mode */}
+        
+          
+            {lightTokens
+              ? lightTokens.tokens.map((line: any, i: number) => (
+                  
+                    {line.map((token: any, j: number) => (
+                      
+                        {token.content}
+                      
+                    ))}
+                    {i < lightTokens.tokens.length - 1 && "\n"}
+                  
+                ))
+              : codeText}
+          
+        
+ {/* Dark mode */} +
+          
+            {darkTokens
+              ? darkTokens.tokens.map((line: any, i: number) => (
+                  
+                    {line.map((token: any, j: number) => (
+                      
+                        {token.content}
+                      
+                    ))}
+                    {i < darkTokens.tokens.length - 1 && "\n"}
+                  
+                ))
+              : codeText}
+          
         
); @@ -68,65 +128,19 @@ const CodeBlock = React.memo( ); const StreamingMarkdownContent: React.FC = - React.memo( - ({ content, isStreaming = false, size, onLastNode, browserToolResult }) => { - // Build the remark plugins array - const remarkPlugins = React.useMemo(() => { - const plugins: PluggableList = [ - remarkGfm, - [remarkMath, { singleDollarTextMath: false }], - remarkCitationParser, - ]; + React.memo(({ content, isStreaming = false, size, browserToolResult }) => { + // Build the remark plugins array - keep default GFM and Math, add citations + const remarkPlugins = React.useMemo(() => { + return [ + defaultRemarkPlugins.gfm, + defaultRemarkPlugins.math, + remarkCitationParser, + ]; + }, []); - // Add streaming plugin when in streaming mode - if (isStreaming) { - plugins.push([remarkStreamingMarkdown, { debug: true, onLastNode }]); - } - - return plugins; - }, [isStreaming, onLastNode]); - - // Create a custom sanitization schema that allows math elements - const sanitizeSchema = React.useMemo(() => { - return { - ...defaultSchema, - attributes: { - ...defaultSchema.attributes, - span: [ - ...(defaultSchema.attributes?.span || []), - ["className", /^katex/], - ], - div: [ - ...(defaultSchema.attributes?.div || []), - ["className", /^katex/], - ], - "ol-citation": ["cursor", "start", "end"], - }, - tagNames: [ - ...(defaultSchema.tagNames || []), - "math", - "mrow", - "mi", - "mo", - "mn", - "msup", - "msub", - "mfrac", - "mover", - "munder", - "msqrt", - "mroot", - "merror", - "mspace", - "mpadded", - "ol-citation", - ], - }; - }, []); - - return ( -
= prose-pre:my-0 prose-pre:max-w-full prose-pre:pt-1 - [&_code:not(pre_code)]:text-neutral-700 + [&_code:not(pre_code)]:text-neutral-700 [&_code:not(pre_code)]:bg-neutral-100 [&_code:not(pre_code)]:font-normal [&_code:not(pre_code)]:px-1.5 @@ -167,104 +181,115 @@ const StreamingMarkdownContent: React.FC = dark:prose-li:marker:text-neutral-300 break-words `} + > + - - ) => ( -
- {children}
-
- ), - // @ts-expect-error: custom type - "ol-citation": ({ - cursor, - // start, - // end, - }: { - cursor: number; - start: number; - end: number; - }) => { - // Check if we have a page_stack and if the cursor is valid - const pageStack = browserToolResult?.page_stack; - const hasValidPage = pageStack && cursor < pageStack.length; - const pageUrl = hasValidPage ? pageStack[cursor] : null; + ) => ( +
+ + {children} +
+
+ ), + thead: ({ + children, + ...props + }: React.HTMLAttributes) => ( + + {children} + + ), + th: ({ + children, + ...props + }: React.HTMLAttributes) => ( + + {children} + + ), + td: ({ + children, + ...props + }: React.HTMLAttributes) => ( + + {children} + + ), + // @ts-expect-error: custom citation type + "ol-citation": ({ + cursor, + }: { + cursor: number; + start: number; + end: number; + }) => { + const pageStack = browserToolResult?.page_stack; + const hasValidPage = pageStack && cursor < pageStack.length; + const pageUrl = hasValidPage ? pageStack[cursor] : null; - // Extract a readable title from the URL if possible - const getPageTitle = (url: string) => { - if (url.startsWith("search_results_")) { - const searchTerm = url.substring( - "search_results_".length, - ); - return `Search: ${searchTerm}`; - } - // For regular URLs, try to extract domain or use full URL - try { - const urlObj = new URL(url); - return urlObj.hostname; - } catch { - // If not a valid URL, return as is - return url; - } - }; - - const citationElement = ( - - [{cursor}] - - ); - - // If we have a valid page URL, wrap in a link - if (pageUrl && pageUrl.startsWith("http")) { - return ( - - {citationElement} - - ); + const getPageTitle = (url: string) => { + if (url.startsWith("search_results_")) { + const searchTerm = url.substring("search_results_".length); + return `Search: ${searchTerm}`; } + try { + const urlObj = new URL(url); + return urlObj.hostname; + } catch { + return url; + } + }; - // Otherwise, just return the citation without a link - return citationElement; - }, - }} - > - {content} -
-
-
- ); - }, - ); + const citationElement = ( + + [{cursor}] + + ); + + if (pageUrl && pageUrl.startsWith("http")) { + return ( + + {citationElement} + + ); + } + + return citationElement; + }, + }} + > + {content} + + +
+ ); + }); interface StreamingMarkdownErrorBoundaryProps { content: string; diff --git a/app/ui/app/src/index.css b/app/ui/app/src/index.css index 0493d51c51..af1f265dc4 100644 --- a/app/ui/app/src/index.css +++ b/app/ui/app/src/index.css @@ -16,793 +16,6 @@ --text-color: #ffffff; } } -@media (prefers-color-scheme: light) { - .prose { - /** - * One Light theme for prism.js - * Based on Atom's One Light theme: https://github.com/atom/atom/tree/master/packages/one-light-syntax - */ - - /** - * One Light colours (accurate as of commit eb064bf on 19 Feb 2021) - * From colors.less - * --mono-1: hsl(230, 8%, 24%); - * --mono-2: hsl(230, 6%, 44%); - * --mono-3: hsl(230, 4%, 64%) - * --hue-1: hsl(198, 99%, 37%); - * --hue-2: hsl(221, 87%, 60%); - * --hue-3: hsl(301, 63%, 40%); - * --hue-4: hsl(119, 34%, 47%); - * --hue-5: hsl(5, 74%, 59%); - * --hue-5-2: hsl(344, 84%, 43%); - * --hue-6: hsl(35, 99%, 36%); - * --hue-6-2: hsl(35, 99%, 40%); - * --syntax-fg: hsl(230, 8%, 24%); - * --syntax-bg: hsl(230, 1%, 98%); - * --syntax-gutter: hsl(230, 1%, 62%); - * --syntax-guide: hsla(230, 8%, 24%, 0.2); - * --syntax-accent: hsl(230, 100%, 66%); - * From syntax-variables.less - * --syntax-selection-color: hsl(230, 1%, 90%); - * --syntax-gutter-background-color-selected: hsl(230, 1%, 90%); - * --syntax-cursor-line: hsla(230, 8%, 24%, 0.05); - */ - - .token.comment, - .token.prolog, - .token.cdata { - color: hsl(230, 4%, 64%); - } - - .token.doctype, - .token.punctuation, - .token.entity { - color: hsl(230, 8%, 24%); - } - - .token.attr-name, - .token.class-name, - .token.boolean, - .token.constant, - .token.number, - .token.atrule { - color: hsl(35, 99%, 36%); - } - - .token.keyword { - color: hsl(301, 63%, 40%); - } - - .token.property, - .token.tag, - .token.symbol, - .token.deleted, - .token.important { - color: hsl(5, 74%, 59%); - } - - .token.selector, - .token.string, - .token.char, - .token.builtin, - .token.inserted, - .token.regex, - .token.attr-value, - .token.attr-value > .token.punctuation { - color: hsl(119, 34%, 47%); - } - - .token.variable, - .token.operator, - .token.function { - color: hsl(221, 87%, 60%); - } - - .token.url { - color: hsl(198, 99%, 37%); - } - - /* HTML overrides */ - .token.attr-value > .token.punctuation.attr-equals, - .token.special-attr > .token.attr-value > .token.value.css { - color: hsl(230, 8%, 24%); - } - - /* CSS overrides */ - .language-css .token.selector { - color: hsl(5, 74%, 59%); - } - - .language-css .token.property { - color: hsl(230, 8%, 24%); - } - - .language-css .token.function, - .language-css .token.url > .token.function { - color: hsl(198, 99%, 37%); - } - - .language-css .token.url > .token.string.url { - color: hsl(119, 34%, 47%); - } - - .language-css .token.important, - .language-css .token.atrule .token.rule { - color: hsl(301, 63%, 40%); - } - - /* JS overrides */ - .language-javascript .token.operator { - color: hsl(301, 63%, 40%); - } - - .language-javascript - .token.template-string - > .token.interpolation - > .token.interpolation-punctuation.punctuation { - color: hsl(344, 84%, 43%); - } - - /* JSON overrides */ - .language-json .token.operator { - color: hsl(230, 8%, 24%); - } - - .language-json .token.null.keyword { - color: hsl(35, 99%, 36%); - } - - /* MD overrides */ - .language-markdown .token.url, - .language-markdown .token.url > .token.operator, - .language-markdown .token.url-reference.url > .token.string { - color: hsl(230, 8%, 24%); - } - - .language-markdown .token.url > .token.content { - color: hsl(221, 87%, 60%); - } - - .language-markdown .token.url > .token.url, - .language-markdown .token.url-reference.url { - color: hsl(198, 99%, 37%); - } - - .language-markdown .token.blockquote.punctuation, - .language-markdown .token.hr.punctuation { - color: hsl(230, 4%, 64%); - font-style: italic; - } - - .language-markdown .token.code-snippet { - color: hsl(119, 34%, 47%); - } - - .language-markdown .token.bold .token.content { - color: hsl(35, 99%, 36%); - } - - .language-markdown .token.italic .token.content { - color: hsl(301, 63%, 40%); - } - - .language-markdown .token.strike .token.content, - .language-markdown .token.strike .token.punctuation, - .language-markdown .token.list.punctuation, - .language-markdown .token.title.important > .token.punctuation { - color: hsl(5, 74%, 59%); - } - - /* General */ - .token.bold { - font-weight: bold; - } - - .token.comment, - .token.italic { - font-style: italic; - } - - .token.entity { - cursor: help; - } - - .token.namespace { - opacity: 0.8; - } - - /* Plugin overrides */ - /* Selectors should have higher specificity than those in the plugins' default stylesheets */ - - /* Show Invisibles plugin overrides */ - .token.token.tab:not(:empty):before, - .token.token.cr:before, - .token.token.lf:before, - .token.token.space:before { - color: hsla(230, 8%, 24%, 0.2); - } - - /* Toolbar plugin overrides */ - /* Space out all buttons and move them away from the right edge of the code block */ - div.code-toolbar > .toolbar.toolbar > .toolbar-item { - margin-right: 0.4em; - } - - /* Styling the buttons */ - div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { - background: hsl(230, 1%, 90%); - color: hsl(230, 6%, 44%); - padding: 0.1em 0.4em; - border-radius: 0.3em; - } - - div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { - background: hsl(230, 1%, 78%); /* custom: darken(--syntax-bg, 20%) */ - color: hsl(230, 8%, 24%); - } - - /* Line Highlight plugin overrides */ - /* The highlighted line itself */ - .line-highlight.line-highlight { - background: hsla(230, 8%, 24%, 0.05); - } - - /* Default line numbers in Line Highlight plugin */ - .line-highlight.line-highlight:before, - .line-highlight.line-highlight[data-end]:after { - background: hsl(230, 1%, 90%); - color: hsl(230, 8%, 24%); - padding: 0.1em 0.6em; - border-radius: 0.3em; - box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */ - } - - /* Hovering over a linkable line number (in the gutter area) */ - /* Requires Line Numbers plugin as well */ - pre[id].linkable-line-numbers.linkable-line-numbers - span.line-numbers-rows - > span:hover:before { - background-color: hsla(230, 8%, 24%, 0.05); - } - - /* Line Numbers and Command Line plugins overrides */ - /* Line separating gutter from coding area */ - .line-numbers.line-numbers .line-numbers-rows, - .command-line .command-line-prompt { - border-right-color: hsla(230, 8%, 24%, 0.2); - } - - /* Stuff in the gutter */ - .line-numbers .line-numbers-rows > span:before, - .command-line .command-line-prompt > span:before { - color: hsl(230, 1%, 62%); - } - - /* Match Braces plugin overrides */ - /* Note: Outline colour is inherited from the braces */ - .rainbow-braces .token.token.punctuation.brace-level-1, - .rainbow-braces .token.token.punctuation.brace-level-5, - .rainbow-braces .token.token.punctuation.brace-level-9 { - color: hsl(5, 74%, 59%); - } - - .rainbow-braces .token.token.punctuation.brace-level-2, - .rainbow-braces .token.token.punctuation.brace-level-6, - .rainbow-braces .token.token.punctuation.brace-level-10 { - color: hsl(119, 34%, 47%); - } - - .rainbow-braces .token.token.punctuation.brace-level-3, - .rainbow-braces .token.token.punctuation.brace-level-7, - .rainbow-braces .token.token.punctuation.brace-level-11 { - color: hsl(221, 87%, 60%); - } - - .rainbow-braces .token.token.punctuation.brace-level-4, - .rainbow-braces .token.token.punctuation.brace-level-8, - .rainbow-braces .token.token.punctuation.brace-level-12 { - color: hsl(301, 63%, 40%); - } - - /* Diff Highlight plugin overrides */ - /* Taken from https://github.com/atom/github/blob/master/styles/variables.less */ - pre.diff-highlight > code .token.token.deleted:not(.prefix), - pre > code.diff-highlight .token.token.deleted:not(.prefix) { - background-color: hsla(353, 100%, 66%, 0.15); - } - - pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection, - pre.diff-highlight - > code - .token.token.deleted:not(.prefix) - *::-moz-selection, - pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection, - pre - > code.diff-highlight - .token.token.deleted:not(.prefix) - *::-moz-selection { - background-color: hsla(353, 95%, 66%, 0.25); - } - - pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection, - pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection, - pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection, - pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection { - background-color: hsla(353, 95%, 66%, 0.25); - } - - pre.diff-highlight > code .token.token.inserted:not(.prefix), - pre > code.diff-highlight .token.token.inserted:not(.prefix) { - background-color: hsla(137, 100%, 55%, 0.15); - } - - pre.diff-highlight - > code - .token.token.inserted:not(.prefix)::-moz-selection, - pre.diff-highlight - > code - .token.token.inserted:not(.prefix) - *::-moz-selection, - pre - > code.diff-highlight - .token.token.inserted:not(.prefix)::-moz-selection, - pre - > code.diff-highlight - .token.token.inserted:not(.prefix) - *::-moz-selection { - background-color: hsla(135, 73%, 55%, 0.25); - } - - pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection, - pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection, - pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection, - pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection { - background-color: hsla(135, 73%, 55%, 0.25); - } - - /* Previewers plugin overrides */ - /* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-light-ui */ - /* Border around popup */ - .prism-previewer.prism-previewer:before, - .prism-previewer-gradient.prism-previewer-gradient div { - border-color: hsl(0, 0, 95%); - } - - /* Angle and time should remain as circles and are hence not included */ - .prism-previewer-color.prism-previewer-color:before, - .prism-previewer-gradient.prism-previewer-gradient div, - .prism-previewer-easing.prism-previewer-easing:before { - border-radius: 0.3em; - } - - /* Triangles pointing to the code */ - .prism-previewer.prism-previewer:after { - border-top-color: hsl(0, 0, 95%); - } - - .prism-previewer-flipped.prism-previewer-flipped.after { - border-bottom-color: hsl(0, 0, 95%); - } - - /* Background colour within the popup */ - .prism-previewer-angle.prism-previewer-angle:before, - .prism-previewer-time.prism-previewer-time:before, - .prism-previewer-easing.prism-previewer-easing { - background: hsl(0, 0%, 100%); - } - - /* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */ - /* For time, this is the alternate colour */ - .prism-previewer-angle.prism-previewer-angle circle, - .prism-previewer-time.prism-previewer-time circle { - stroke: hsl(230, 8%, 24%); - stroke-opacity: 1; - } - - /* Stroke colours of the handle, direction point, and vector itself */ - .prism-previewer-easing.prism-previewer-easing circle, - .prism-previewer-easing.prism-previewer-easing path, - .prism-previewer-easing.prism-previewer-easing line { - stroke: hsl(230, 8%, 24%); - } - - /* Fill colour of the handle */ - .prism-previewer-easing.prism-previewer-easing circle { - fill: transparent; - } - } -} - -@media (prefers-color-scheme: dark) { - .prose { - .token.comment, - .token.prolog, - .token.cdata { - color: hsl(220, 10%, 40%); - } - - .token.doctype, - .token.punctuation, - .token.entity { - color: hsl(220, 14%, 71%); - } - - .token.attr-name, - .token.class-name, - .token.boolean, - .token.constant, - .token.number, - .token.atrule { - color: hsl(29, 54%, 61%); - } - - .token.keyword { - color: hsl(286, 60%, 67%); - } - - .token.property, - .token.tag, - .token.symbol, - .token.deleted, - .token.important { - color: hsl(355, 65%, 65%); - } - - .token.selector, - .token.string, - .token.char, - .token.builtin, - .token.inserted, - .token.regex, - .token.attr-value, - .token.attr-value > .token.punctuation { - color: hsl(95, 38%, 62%); - } - - .token.variable, - .token.operator, - .token.function { - color: hsl(207, 82%, 66%); - } - - .token.url { - color: hsl(187, 47%, 55%); - } - - /* HTML overrides */ - .token.attr-value > .token.punctuation.attr-equals, - .token.special-attr > .token.attr-value > .token.value.css { - color: hsl(220, 14%, 71%); - } - - /* CSS overrides */ - .language-css .token.selector { - color: hsl(355, 65%, 65%); - } - - .language-css .token.property { - color: hsl(220, 14%, 71%); - } - - .language-css .token.function, - .language-css .token.url > .token.function { - color: hsl(187, 47%, 55%); - } - - .language-css .token.url > .token.string.url { - color: hsl(95, 38%, 62%); - } - - .language-css .token.important, - .language-css .token.atrule .token.rule { - color: hsl(286, 60%, 67%); - } - - /* JS overrides */ - .language-javascript .token.operator { - color: hsl(286, 60%, 67%); - } - - .language-javascript - .token.template-string - > .token.interpolation - > .token.interpolation-punctuation.punctuation { - color: hsl(5, 48%, 51%); - } - - /* JSON overrides */ - .language-json .token.operator { - color: hsl(220, 14%, 71%); - } - - .language-json .token.null.keyword { - color: hsl(29, 54%, 61%); - } - - /* MD overrides */ - .language-markdown .token.url, - .language-markdown .token.url > .token.operator, - .language-markdown .token.url-reference.url > .token.string { - color: hsl(220, 14%, 71%); - } - - .language-markdown .token.url > .token.content { - color: hsl(207, 82%, 66%); - } - - .language-markdown .token.url > .token.url, - .language-markdown .token.url-reference.url { - color: hsl(187, 47%, 55%); - } - - .language-markdown .token.blockquote.punctuation, - .language-markdown .token.hr.punctuation { - color: hsl(220, 10%, 40%); - font-style: italic; - } - - .language-markdown .token.code-snippet { - color: hsl(95, 38%, 62%); - } - - .language-markdown .token.bold .token.content { - color: hsl(29, 54%, 61%); - } - - .language-markdown .token.italic .token.content { - color: hsl(286, 60%, 67%); - } - - .language-markdown .token.strike .token.content, - .language-markdown .token.strike .token.punctuation, - .language-markdown .token.list.punctuation, - .language-markdown .token.title.important > .token.punctuation { - color: hsl(355, 65%, 65%); - } - - /* General */ - .token.bold { - font-weight: bold; - } - - .token.comment, - .token.italic { - font-style: italic; - } - - .token.entity { - cursor: help; - } - - .token.namespace { - opacity: 0.8; - } - - /* Plugin overrides */ - /* Selectors should have higher specificity than those in the plugins' default stylesheets */ - - /* Show Invisibles plugin overrides */ - .token.token.tab:not(:empty):before, - .token.token.cr:before, - .token.token.lf:before, - .token.token.space:before { - color: hsla(220, 14%, 71%, 0.15); - text-shadow: none; - } - - /* Toolbar plugin overrides */ - /* Space out all buttons and move them away from the right edge of the code block */ - div.code-toolbar > .toolbar.toolbar > .toolbar-item { - margin-right: 0.4em; - } - - /* Styling the buttons */ - div.code-toolbar > .toolbar.toolbar > .toolbar-item > button, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > a, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > span { - background: hsl(220, 13%, 26%); - color: hsl(220, 9%, 55%); - padding: 0.1em 0.4em; - border-radius: 0.3em; - } - - div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover, - div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus { - background: hsl(220, 13%, 28%); - color: hsl(220, 14%, 71%); - } - - /* Line Highlight plugin overrides */ - /* The highlighted line itself */ - .line-highlight.line-highlight { - background: hsla(220, 100%, 80%, 0.04); - } - - /* Default line numbers in Line Highlight plugin */ - .line-highlight.line-highlight:before, - .line-highlight.line-highlight[data-end]:after { - background: hsl(220, 13%, 26%); - color: hsl(220, 14%, 71%); - padding: 0.1em 0.6em; - border-radius: 0.3em; - box-shadow: 0 2px 0 0 rgba(0, 0, 0, 0.2); /* same as Toolbar plugin default */ - } - - /* Hovering over a linkable line number (in the gutter area) */ - /* Requires Line Numbers plugin as well */ - pre[id].linkable-line-numbers.linkable-line-numbers - span.line-numbers-rows - > span:hover:before { - background-color: hsla(220, 100%, 80%, 0.04); - } - - /* Line Numbers and Command Line plugins overrides */ - /* Line separating gutter from coding area */ - .line-numbers.line-numbers .line-numbers-rows, - .command-line .command-line-prompt { - border-right-color: hsla(220, 14%, 71%, 0.15); - } - - /* Stuff in the gutter */ - .line-numbers .line-numbers-rows > span:before, - .command-line .command-line-prompt > span:before { - color: hsl(220, 14%, 45%); - } - - /* Match Braces plugin overrides */ - /* Note: Outline colour is inherited from the braces */ - .rainbow-braces .token.token.punctuation.brace-level-1, - .rainbow-braces .token.token.punctuation.brace-level-5, - .rainbow-braces .token.token.punctuation.brace-level-9 { - color: hsl(355, 65%, 65%); - } - - .rainbow-braces .token.token.punctuation.brace-level-2, - .rainbow-braces .token.token.punctuation.brace-level-6, - .rainbow-braces .token.token.punctuation.brace-level-10 { - color: hsl(95, 38%, 62%); - } - - .rainbow-braces .token.token.punctuation.brace-level-3, - .rainbow-braces .token.token.punctuation.brace-level-7, - .rainbow-braces .token.token.punctuation.brace-level-11 { - color: hsl(207, 82%, 66%); - } - - .rainbow-braces .token.token.punctuation.brace-level-4, - .rainbow-braces .token.token.punctuation.brace-level-8, - .rainbow-braces .token.token.punctuation.brace-level-12 { - color: hsl(286, 60%, 67%); - } - - /* Diff Highlight plugin overrides */ - /* Taken from https://github.com/atom/github/blob/master/styles/variables.less */ - pre.diff-highlight > code .token.token.deleted:not(.prefix), - pre > code.diff-highlight .token.token.deleted:not(.prefix) { - background-color: hsla(353, 100%, 66%, 0.15); - } - - pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection, - pre.diff-highlight - > code - .token.token.deleted:not(.prefix) - *::-moz-selection, - pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection, - pre - > code.diff-highlight - .token.token.deleted:not(.prefix) - *::-moz-selection { - background-color: hsla(353, 95%, 66%, 0.25); - } - - pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection, - pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection, - pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection, - pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection { - background-color: hsla(353, 95%, 66%, 0.25); - } - - pre.diff-highlight > code .token.token.inserted:not(.prefix), - pre > code.diff-highlight .token.token.inserted:not(.prefix) { - background-color: hsla(137, 100%, 55%, 0.15); - } - - pre.diff-highlight - > code - .token.token.inserted:not(.prefix)::-moz-selection, - pre.diff-highlight - > code - .token.token.inserted:not(.prefix) - *::-moz-selection, - pre - > code.diff-highlight - .token.token.inserted:not(.prefix)::-moz-selection, - pre - > code.diff-highlight - .token.token.inserted:not(.prefix) - *::-moz-selection { - background-color: hsla(135, 73%, 55%, 0.25); - } - - pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection, - pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection, - pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection, - pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection { - background-color: hsla(135, 73%, 55%, 0.25); - } - - /* Previewers plugin overrides */ - /* Based on https://github.com/atom-community/atom-ide-datatip/blob/master/styles/atom-ide-datatips.less and https://github.com/atom/atom/blob/master/packages/one-dark-ui */ - /* Border around popup */ - .prism-previewer.prism-previewer:before, - .prism-previewer-gradient.prism-previewer-gradient div { - border-color: hsl(224, 13%, 17%); - } - - /* Angle and time should remain as circles and are hence not included */ - .prism-previewer-color.prism-previewer-color:before, - .prism-previewer-gradient.prism-previewer-gradient div, - .prism-previewer-easing.prism-previewer-easing:before { - border-radius: 0.3em; - } - - /* Triangles pointing to the code */ - .prism-previewer.prism-previewer:after { - border-top-color: hsl(224, 13%, 17%); - } - - .prism-previewer-flipped.prism-previewer-flipped.after { - border-bottom-color: hsl(224, 13%, 17%); - } - - /* Background colour within the popup */ - .prism-previewer-angle.prism-previewer-angle:before, - .prism-previewer-time.prism-previewer-time:before, - .prism-previewer-easing.prism-previewer-easing { - background: hsl(219, 13%, 22%); - } - - /* For angle, this is the positive area (eg. 90deg will display one quadrant in this colour) */ - /* For time, this is the alternate colour */ - .prism-previewer-angle.prism-previewer-angle circle, - .prism-previewer-time.prism-previewer-time circle { - stroke: hsl(220, 14%, 71%); - stroke-opacity: 1; - } - - /* Stroke colours of the handle, direction point, and vector itself */ - .prism-previewer-easing.prism-previewer-easing circle, - .prism-previewer-easing.prism-previewer-easing path, - .prism-previewer-easing.prism-previewer-easing line { - stroke: hsl(220, 14%, 71%); - } - - /* Fill colour of the handle */ - .prism-previewer-easing.prism-previewer-easing circle { - fill: transparent; - } - } -} - -.prose pre { - contain: layout style; -} - -/* Or more aggressively */ -.prose pre code { - contain: layout style paint; -} /* messaging-style typing indicator animation */ @keyframes typing { diff --git a/app/ui/app/src/utils/processStreamingMarkdown.ts b/app/ui/app/src/utils/processStreamingMarkdown.ts deleted file mode 100644 index faab9b5c9e..0000000000 --- a/app/ui/app/src/utils/processStreamingMarkdown.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { remark } from "remark"; -import remarkStringify from "remark-stringify"; -import remarkStreamingMarkdown from "./remarkStreamingMarkdown"; - -/** - * Process markdown content for streaming display using the remark plugin. - * This is primarily used for testing the remark plugin with string inputs/outputs. - */ -export function processStreamingMarkdown(content: string): string { - if (!content) return content; - - const result = remark() - .use(remarkStreamingMarkdown, { debug: false }) - .use(remarkStringify) - .processSync(content); - - // remove trailing newline to keep tests cleaner - let output = result.toString(); - if (output.endsWith("\n")) { - output = output.slice(0, -1); - } - - return output; -} diff --git a/app/ui/app/src/utils/remarkStreamingMarkdown.ts b/app/ui/app/src/utils/remarkStreamingMarkdown.ts deleted file mode 100644 index 6b6d4cd786..0000000000 --- a/app/ui/app/src/utils/remarkStreamingMarkdown.ts +++ /dev/null @@ -1,447 +0,0 @@ -import { parents, type Proxy } from "unist-util-parents"; -import type { Plugin } from "unified"; -import type { - Emphasis, - Node, - Parent, - Root, - RootContent, - Text, - Strong, - PhrasingContent, - Paragraph, -} from "mdast"; -import { u } from "unist-builder"; - -declare module "unist" { - interface Node { - /** Added by `unist-util-parents` (or your own walk). */ - parent?: Proxy & Parent; - } -} - -// interface SimpleTextRule { -// pattern: RegExp; -// transform: (matches: RegExpExecArray[], lastNode: Proxy) => void; -// } - -// const simpleTextRules: SimpleTextRule[] = [ -// // TODO(drifkin): generalize this for `__`/`_`/`~~`/`~` etc. -// { -// pattern: /(\*\*)(?=\S|$)/g, -// transform: (matchesIterator, lastNode) => { -// const textNode = lastNode.node as Text; - -// const matches = [...matchesIterator]; -// const lastMatch = matches[matches.length - 1]; -// const origValue = textNode.value; -// const start = lastMatch.index; -// const sep = lastMatch[1]; - -// const before = origValue.slice(0, start); -// const after = origValue.slice(start + sep.length); - -// if (lastNode.parent) { -// const index = (lastNode.parent.node as Parent).children.indexOf( -// lastNode.node as RootContent, -// ); -// const shouldRemove = before.length === 0; -// if (!shouldRemove) { -// textNode.value = before; -// } - -// const newNode = u("strong", { -// children: [u("text", { value: after })], -// }); -// (lastNode.parent.node as Parent).children.splice( -// index + (shouldRemove ? 0 : 1), -// shouldRemove ? 1 : 0, -// newNode, -// ); -// } -// }, -// }, -// ]; - -interface Options { - debug?: boolean; - onLastNode?: (info: LastNodeInfo) => void; -} - -export interface LastNodeInfo { - path: string[]; - type: string; - value?: string; - lastChars?: string; - fullNode: Node; -} - -/** - * Removes `child` from `parent` in-place. - * @returns `true` if the child was found and removed; `false` otherwise. - */ -export function removeChildFromParent( - child: RootContent, - parent: Node, -): boolean { - if (!isParent(parent)) return false; // parent isn’t a Parent → nothing to do - - const idx = parent.children.indexOf(child); - if (idx < 0) return false; // not a child → nothing to remove - - parent.children.splice(idx, 1); - return true; // removal successful -} - -/** Narrow a generic `Node` to a `Parent` (i.e. one that really has children). */ -function isParent(node: Node): node is Parent { - // A `Parent` always has a `children` array; make sure it's an array first. - return Array.isArray((node as Partial).children); -} - -/** - * Follow “last-child” pointers until you reach a leaf. - * Returns the right-most, deepest node in source order. - */ -export function findRightmostDeepestNode(root: Node): Node { - let current: Node = root; - - // While the current node *is* a Parent and has at least one child… - while (isParent(current) && current.children.length > 0) { - const lastIndex = current.children.length - 1; - current = current.children[lastIndex]; - } - - return current; // Leaf: no further children -} - -const remarkStreamingMarkdown: Plugin<[Options?], Root> = () => { - return (tree) => { - const treeWithParents = parents(tree); - const lastNode = findRightmostDeepestNode(treeWithParents) as Proxy; - - const parentNode = lastNode.parent; - const grandparentNode = parentNode?.parent; - - let ruleMatched = false; - - // handling `* *` -> `` - // - // if the last node is part of a -> - // -> , then we need to - // remove everything up to and including the first list item. This happens - // when we have `* *`, which can become a bolded list item OR a horizontal - // line - if ( - lastNode.type === "listItem" && - parentNode && - grandparentNode && - parentNode.type === "list" && - grandparentNode.type === "listItem" && - parentNode.children.length === 1 && - grandparentNode.children.length === 1 - ) { - ruleMatched = true; - if (grandparentNode.parent) { - removeChildFromParent( - grandparentNode.node as RootContent, - grandparentNode.parent.node, - ); - } - // Handle `*` -> ``: - // - // if the last node is just an empty list item, we need to remove it - // because it could become something else (e.g., a horizontal line) - } else if ( - lastNode.type === "listItem" && - parentNode && - parentNode.type === "list" - ) { - ruleMatched = true; - removeChildFromParent(lastNode.node as RootContent, parentNode.node); - } else if (lastNode.type === "thematicBreak") { - ruleMatched = true; - const parent = lastNode.parent; - if (parent) { - removeChildFromParent(lastNode.node as RootContent, parent.node); - } - } else if (lastNode.type === "text") { - const textNode = lastNode.node as Text; - if (textNode.value.endsWith("**")) { - ruleMatched = true; - textNode.value = textNode.value.slice(0, -2); - // if there's a newline then a number, this is very very likely a - // numbered list item. Let's just hide it until the period comes (or - // other text disambiguates it) - } else { - const match = textNode.value.match(/^([0-9]+)$/m); - if (match) { - const number = match[1]; - textNode.value = textNode.value.slice(0, -number.length - 1); - ruleMatched = true; - // if the text node is now empty, then we might want to remove other - // elements, like a now-empty containing paragraph, or a break that - // might disappear once more tokens come in - if (textNode.value.length === 0) { - if ( - lastNode.parent?.type === "paragraph" && - lastNode.parent.children.length === 1 - ) { - // remove the whole paragraph if it's now empty (otherwise it'll - // cause an extra newline that might not last) - removeChildFromParent( - lastNode.parent.node as Paragraph, - lastNode.parent.parent?.node as Node, - ); - } else { - const prev = prevSibling(lastNode); - if (prev?.type === "break") { - removeChildFromParent( - prev.node as RootContent, - lastNode.parent?.node as Node, - ); - removeChildFromParent( - lastNode.node as RootContent, - lastNode.parent?.node as Node, - ); - } - } - } - } - } - } - - if (ruleMatched) { - return tree; - } - - // we need to - // a case like - // - *def `abc` [abc **def**](abc)* - // is pretty tricky, because if we land just after def, then we actually - // have two separate tags to process at two different parents. Maybe we - // need to keep iterating up until we find a paragraph, but process each - // parent on the way up. Hmm, well actually after `def` we won't even be a proper link yet - // TODO(drifkin): it's really if the last node's parent is a paragraph, for which the following is a sub-cas where the lastNode is a text node. - // And instead of just processing simple text rules, they need to operate on the whole paragraph - // like `**[abc](def)` needs to become `**[abc](def)**` - - // if we're just text at the end, then we should remove some ambiguous characters - - if (lastNode.parent) { - const didChange = processParent(lastNode.parent as Parent & Proxy); - if (didChange) { - // TODO(drifkin): need to fix up the tree, but not sure lastNode will still exist? Check all the transforms to see if it's safe to find the last node again - // - // need to regen the tree w/ parents since reparenting could've happened - // treeWithParents = parents(tree); - } - } - - const grandparent = lastNode.parent?.parent; - // TODO(drifkin): let's go arbitrarily high up the tree, but limiting it - // to 2 levels for now until I think more about the stop condition - if (grandparent) { - processParent(grandparent as Parent & Proxy); - } - - // console.log("ruleMatched", ruleMatched); - - // } else if (lastNode.parent?.type === "paragraph") { - // console.log("!!! paragraph"); - // console.log("lastNode.parent", lastNode.parent); - - // // Handle `**abc*` -> `**abc**`: - // // We detect this when the last child is an emphasis node, and it's preceded by a text node that ends with `*` - // const paragraph = lastNode.parent as Proxy & Paragraph; - // if (paragraph.children.length >= 2) { - // const lastChild = paragraph.children[paragraph.children.length - 1]; - // if (lastChild.type === "emphasis") { - // const sibling = paragraph.children[paragraph.children.length - 2]; - // if (sibling.type === "text") { - // const siblingText = sibling as Text & Proxy; - // if (siblingText.value.endsWith("*")) { - // ruleMatched = true; - // const textNode = (lastNode as Proxy).node as Text; - // textNode.value = textNode.value.slice(0, -1); - // paragraph.node.type = "strong"; - // } - // } - // } - // } - // } else if (lastNode.type === "text") { - // // Handle `**abc*` -> `**abc**`: - // // - // // this gets parsed as a text node ending in `*` followed by an emphasis - // // node. So if we're in text, we need to check if our parent is emphasis, - // // and then get our parent's sibling before it and check if it ends with - // // `*` - // const parent = lastNode.parent; - // if (parent && parent.type === "emphasis") { - // const grandparent = parent.parent; - // if (grandparent) { - // const index = (grandparent.node as Parent).children.indexOf( - // parent.node as RootContent, - // ); - // if (index > 0) { - // const prevNode = grandparent.children[index - 1]; - // if ( - // prevNode.type === "text" && - // (prevNode as Text).value.endsWith("*") - // ) { - // ruleMatched = true; - // const textNode = (prevNode as Proxy).node as Text; - // textNode.value = textNode.value.slice(0, -1); - // parent.node.type = "strong"; - // } - // } - // } - // } - - // if (!ruleMatched) { - // // if the last node is just text, then we process it in order to fix up certain unclosed items - // // e.g., `**abc` -> `**abc**` - // const textNode = lastNode.node as Text; - // for (const rule of simpleTextRules) { - // const matchesIterator = textNode.value.matchAll(rule.pattern); - // const matches = [...matchesIterator]; - // if (matches.length > 0) { - // rule.transform(matches, lastNode); - // ruleMatched = true; - // break; - // } - // } - // } - // } else if (!ruleMatched) { - // // console.log("no rule matched", lastNode); - // } - - return tree; - }; -}; - -function processParent(parent: Parent & Proxy): boolean { - if (parent.type === "emphasis") { - // Handle `**abc*` -> `**abc**`: - // We detect this when we end with an emphasis node, and it's preceded by - // a text node that ends with `*` - // TODO(drifkin): the last node can be more deeply nested (e.g., a code - // literal in a link), so we probably need to walk up the tree until we - // find an emphasis node or a block? For now we'll just go up one layer to - // catch the most common cases - const emphasisNode = parent as Emphasis & Proxy; - const grandparent = emphasisNode.parent; - if (grandparent) { - const indexOfEmphasisNode = (grandparent.node as Parent).children.indexOf( - emphasisNode.node as RootContent, - ); - if (indexOfEmphasisNode >= 0) { - const nodeBefore = grandparent.children[indexOfEmphasisNode - 1] as - | (Node & Proxy) - | undefined; - if (nodeBefore?.type === "text") { - const textNode = nodeBefore.node as Text; - if (textNode.value.endsWith("*")) { - const strBefore = textNode.value.slice(0, -1); - textNode.value = strBefore; - const strongNode = u("strong", { - children: emphasisNode.children, - }); - (grandparent.node as Parent).children.splice( - indexOfEmphasisNode, - 1, - strongNode, - ); - return true; - } - } - } - } - } - - // Let's check if we have any bold items to close - for (let i = parent.children.length - 1; i >= 0; i--) { - const child = parent.children[i]; - if (child.type === "text") { - const textNode = child as Text & Proxy; - const sep = "**"; - const index = textNode.value.lastIndexOf(sep); - if (index >= 0) { - let isValidOpening = false; - if (index + sep.length < textNode.value.length) { - const charAfter = textNode.value[index + sep.length]; - if (!isWhitespace(charAfter)) { - isValidOpening = true; - } - } else { - if (i < parent.children.length - 1) { - // TODO(drifkin): I'm not sure that this check is strict enough. - // We're trying to detect cases like `**[abc]()` where the char - // after the opening ** is indeed a non-whitespace character. We're - // using the heuristic that there's another item after the current - // one, but I'm not sure if that is good enough. In a well - // constructed tree, there aren't two text nodes in a row, so this - // _seems_ good, but I should think through it more - isValidOpening = true; - } - } - - if (isValidOpening) { - // TODO(drifkin): close the bold - const strBefore = textNode.value.slice(0, index); - const strAfter = textNode.value.slice(index + sep.length); - (textNode.node as Text).value = strBefore; - // TODO(drifkin): the node above could be empty in which case we probably want to delete it - const children: PhrasingContent[] = [ - ...(strAfter.length > 0 ? [u("text", { value: strAfter })] : []), - ]; - const strongNode: Strong = u("strong", { - children, - }); - const nodesAfter = (parent.node as Parent).children.splice( - i + 1, - parent.children.length - i - 1, - strongNode, - ); - // TODO(drifkin): this cast seems iffy, should see if we can cast the - // parent instead, which would also help us check some of our - // assumptions - strongNode.children.push(...(nodesAfter as PhrasingContent[])); - return true; - } - } - } - } - - return false; -} - -function prevSibling(node: Node & Proxy): (Node & Proxy) | null { - const parent = node.parent; - if (parent) { - const index = parent.children.indexOf(node); - return parent.children[index - 1] as Node & Proxy; - } - return null; -} - -function isWhitespace(str: string) { - return str.trim() === ""; -} - -// function debugPrintTreeNoPos(tree: Node) { -// console.log( -// JSON.stringify( -// tree, -// (key, value) => { -// if (key === "position") { -// return undefined; -// } -// return value; -// }, -// 2, -// ), -// ); -// } - -export default remarkStreamingMarkdown;