From e2df16893e147cbeea8ff42d529c917b59564540 Mon Sep 17 00:00:00 2001 From: matevip Date: Sun, 26 Apr 2026 18:42:27 +0800 Subject: [PATCH] fix(wiki): smaller batch-create + resume button for partial generation --- .../java/vip/mate/wiki/WikiProperties.java | 7 +- mateclaw-ui/package.json | 3 + mateclaw-ui/pnpm-lock.yaml | 948 +++++++++++++++++- mateclaw-ui/src/assets/main.css | 4 + .../src/components/chat/MessageBubble.vue | 97 ++ .../src/composables/useKatexRenderer.ts | 106 ++ .../src/composables/useMarkdownRenderer.ts | 293 +++++- .../src/composables/useMermaidRenderer.ts | 151 +++ mateclaw-ui/src/i18n/locales/en-US.ts | 1 + mateclaw-ui/src/i18n/locales/zh-CN.ts | 1 + mateclaw-ui/src/views/AgentContext.vue | 5 + mateclaw-ui/src/views/ChatConsole.vue | 18 +- .../Wiki/components/RawMaterialPanel.vue | 13 +- 13 files changed, 1597 insertions(+), 50 deletions(-) create mode 100644 mateclaw-ui/src/composables/useKatexRenderer.ts create mode 100644 mateclaw-ui/src/composables/useMermaidRenderer.ts diff --git a/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java index 759dcf6a..8a3df9f1 100644 --- a/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java +++ b/mateclaw-server/src/main/java/vip/mate/wiki/WikiProperties.java @@ -92,10 +92,11 @@ public class WikiProperties { * Pages planned by route are chunked into sub-batches of this size; * a local liveIndex is updated between sub-batches so later pages can * link to earlier ones created in the same chunk. - * Default 5: keeps output tokens per call predictable while covering - * the typical 3–5 creates per chunk in a single call. + * Default 2: keeps total output tokens well under typical provider caps + * (~2k–3k completion tokens) so the FILE-block JSON doesn't get truncated + * mid-object. Raising this risks unparseable JSON skips on long content. */ - private int batchCreatePageSize = 3; + private int batchCreatePageSize = 2; /** * RFC-047: Minimum chunk length (chars) for the chunk-fallback mechanism. diff --git a/mateclaw-ui/package.json b/mateclaw-ui/package.json index 5dfc4140..55e810e3 100644 --- a/mateclaw-ui/package.json +++ b/mateclaw-ui/package.json @@ -18,8 +18,10 @@ "echarts": "^6.0.0", "element-plus": "^2.9.1", "highlight.js": "^11.11.1", + "katex": "^0.16.45", "marked": "^15.0.6", "marked-highlight": "^2.2.3", + "mermaid": "^11.14.0", "pinia": "^3.0.1", "vue": "^3.5.13", "vue-i18n": "9.14.4", @@ -27,6 +29,7 @@ }, "devDependencies": { "@tailwindcss/vite": "^4.2.2", + "@types/katex": "^0.16.8", "@vitejs/plugin-vue": "^6.0.5", "@vue/tsconfig": "^0.7.0", "autoprefixer": "^10.4.20", diff --git a/mateclaw-ui/pnpm-lock.yaml b/mateclaw-ui/pnpm-lock.yaml index 028d18d9..594d7f60 100644 --- a/mateclaw-ui/pnpm-lock.yaml +++ b/mateclaw-ui/pnpm-lock.yaml @@ -29,12 +29,18 @@ importers: highlight.js: specifier: ^11.11.1 version: 11.11.1 + katex: + specifier: ^0.16.45 + version: 0.16.45 marked: specifier: ^15.0.6 version: 15.0.12 marked-highlight: specifier: ^2.2.3 version: 2.2.3(marked@15.0.12) + mermaid: + specifier: ^11.14.0 + version: 11.14.0 pinia: specifier: ^3.0.1 version: 3.0.4(typescript@5.7.3)(vue@3.5.31(typescript@5.7.3)) @@ -51,6 +57,9 @@ importers: '@tailwindcss/vite': specifier: ^4.2.2 version: 4.2.2(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0)) + '@types/katex': + specifier: ^0.16.8 + version: 0.16.8 '@vitejs/plugin-vue': specifier: ^6.0.5 version: 6.0.5(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0))(vue@3.5.31(typescript@5.7.3)) @@ -81,6 +90,9 @@ importers: packages: + '@antfu/install-pkg@1.1.0': + resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} @@ -98,6 +110,24 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@braintree/sanitize-url@7.1.2': + resolution: {integrity: sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==} + + '@chevrotain/cst-dts-gen@12.0.0': + resolution: {integrity: sha512-fSL4KXjTl7cDgf0B5Rip9Q05BOrYvkJV/RrBTE/bKDN096E4hN/ySpcBK5B24T76dlQ2i32Zc3PAE27jFnFrKg==} + + '@chevrotain/gast@12.0.0': + resolution: {integrity: sha512-1ne/m3XsIT8aEdrvT33so0GUC+wkctpUPK6zU9IlOyJLUbR0rg4G7ZiApiJbggpgPir9ERy3FRjT6T7lpgetnQ==} + + '@chevrotain/regexp-to-ast@12.0.0': + resolution: {integrity: sha512-p+EW9MaJwgaHguhoqwOtx/FwuGr+DnNn857sXWOi/mClXIkPGl3rn7hGNWvo31HA3vyeQxjqe+H36yZJwYU8cA==} + + '@chevrotain/types@12.0.0': + resolution: {integrity: sha512-S+04vjFQKeuYw0/eW3U52LkAHQsB1ASxsPGsLPUyQgrZ2iNNibQrsidruDzjEX2JYfespXMG0eZmXlhA6z7nWA==} + + '@chevrotain/utils@12.0.0': + resolution: {integrity: sha512-lB59uJoaGIfOOL9knQqQRfhl9g7x8/wqFkp13zTdkRu1huG9kg6IJs1O8hqj9rs6h7orGxHJUKb+mX3rPbWGhA==} + '@ctrl/tinycolor@4.2.0': resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} engines: {node: '>=14'} @@ -326,6 +356,12 @@ packages: resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} engines: {node: '>=18.18'} + '@iconify/types@2.0.0': + resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} + + '@iconify/utils@3.1.1': + resolution: {integrity: sha512-MwzoDtw9rO1x+qfgLTV/IVXsHDBqeYZoMIQC8SfxfYSlaSUG+oWiAcoiB1yajAda6mqblm4/1/w2E8tRu7a7Tw==} + '@intlify/core-base@9.14.4': resolution: {integrity: sha512-vtZCt7NqWhKEtHa3SD/322DlgP5uR9MqWxnE0y8Q0tjDs9H5Lxhss+b5wv8rmuXRoHKLESNgw9d+EN9ybBbj9g==} engines: {node: '>= 16'} @@ -354,6 +390,9 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@mermaid-js/parser@1.1.0': + resolution: {integrity: sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw==} + '@rolldown/pluginutils@1.0.0-rc.2': resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} @@ -391,79 +430,66 @@ packages: resolution: {integrity: sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g==} cpu: [arm] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.60.1': resolution: {integrity: sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg==} cpu: [arm] os: [linux] - libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.60.1': resolution: {integrity: sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ==} cpu: [arm64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.60.1': resolution: {integrity: sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA==} cpu: [arm64] os: [linux] - libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.60.1': resolution: {integrity: sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ==} cpu: [loong64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.60.1': resolution: {integrity: sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw==} cpu: [loong64] os: [linux] - libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.60.1': resolution: {integrity: sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw==} cpu: [ppc64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.60.1': resolution: {integrity: sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg==} cpu: [ppc64] os: [linux] - libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.60.1': resolution: {integrity: sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg==} cpu: [riscv64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.60.1': resolution: {integrity: sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg==} cpu: [riscv64] os: [linux] - libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.60.1': resolution: {integrity: sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ==} cpu: [s390x] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.60.1': resolution: {integrity: sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg==} cpu: [x64] os: [linux] - libc: [glibc] '@rollup/rollup-linux-x64-musl@4.60.1': resolution: {integrity: sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w==} cpu: [x64] os: [linux] - libc: [musl] '@rollup/rollup-openbsd-x64@4.60.1': resolution: {integrity: sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw==} @@ -536,28 +562,24 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.2.2': resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.2.2': resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.2.2': resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.2.2': resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} @@ -592,12 +614,111 @@ packages: peerDependencies: vite: ^5.2.0 || ^6 || ^7 || ^8 + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/katex@0.16.8': + resolution: {integrity: sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==} + '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} @@ -610,6 +731,9 @@ packages: '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} + '@upsetjs/venn.js@2.0.0': + resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} + '@vitejs/plugin-vue@6.0.5': resolution: {integrity: sha512-bL3AxKuQySfk1iGcBsQnoRVexTPJq0Z/ixFVM8OhVJAP6ZXXXLtM7NFKWhLl30Kg7uTBqIaPXbh+nuQCuBDedg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -766,6 +890,15 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chevrotain-allstar@0.4.1: + resolution: {integrity: sha512-PvVJm3oGqrveUVW2Vt/eZGeiAIsJszYweUcYwcskg9e+IubNYKKD+rHHem7A6XVO22eDAL+inxNIGAzZ/VIWlA==} + peerDependencies: + chevrotain: ^12.0.0 + + chevrotain@12.0.0: + resolution: {integrity: sha512-csJvb+6kEiQaqo1woTdSAuOWdN0WTLIydkKrBnS+V5gZz0oqBrp4kQ35519QgK6TpBThiG3V1vNSHlIkv4AglQ==} + engines: {node: '>=22.0.0'} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -777,13 +910,30 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + copy-anything@4.0.5: resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} engines: {node: '>=18'} + cose-base@1.0.3: + resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} + + cose-base@2.2.0: + resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -796,6 +946,162 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cytoscape-cose-bilkent@4.1.0: + resolution: {integrity: sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape-fcose@2.2.0: + resolution: {integrity: sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==} + peerDependencies: + cytoscape: ^3.2.0 + + cytoscape@3.33.2: + resolution: {integrity: sha512-sj4HXd3DokGhzZAdjDejGvTPLqlt84vNFN8m7bGsOzDY5DyVcxIb2ejIXat2Iy7HxWhdT/N1oKyheJ5YdpsGuw==} + engines: {node: '>=0.10'} + + d3-array@2.12.1: + resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-axis@3.0.0: + resolution: {integrity: sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==} + engines: {node: '>=12'} + + d3-brush@3.0.0: + resolution: {integrity: sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==} + engines: {node: '>=12'} + + d3-chord@3.0.1: + resolution: {integrity: sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-contour@4.0.2: + resolution: {integrity: sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==} + engines: {node: '>=12'} + + d3-delaunay@6.0.4: + resolution: {integrity: sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-dsv@3.0.1: + resolution: {integrity: sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==} + engines: {node: '>=12'} + hasBin: true + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-fetch@3.0.1: + resolution: {integrity: sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==} + engines: {node: '>=12'} + + d3-force@3.0.0: + resolution: {integrity: sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-geo@3.1.1: + resolution: {integrity: sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==} + engines: {node: '>=12'} + + d3-hierarchy@3.1.2: + resolution: {integrity: sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@1.0.9: + resolution: {integrity: sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-polygon@3.0.1: + resolution: {integrity: sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==} + engines: {node: '>=12'} + + d3-quadtree@3.0.1: + resolution: {integrity: sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==} + engines: {node: '>=12'} + + d3-random@3.0.1: + resolution: {integrity: sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==} + engines: {node: '>=12'} + + d3-sankey@0.12.3: + resolution: {integrity: sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==} + + d3-scale-chromatic@3.1.0: + resolution: {integrity: sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@1.3.7: + resolution: {integrity: sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + d3@7.9.0: + resolution: {integrity: sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==} + engines: {node: '>=12'} + + dagre-d3-es@7.0.14: + resolution: {integrity: sha512-P4rFMVq9ESWqmOgK+dlXvOtLwYg0i7u0HBGJER0LZDJT2VHIPAMZ/riPxqJceWMStH5+E61QxFra9kIS3AqdMg==} + dayjs@1.11.20: resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} @@ -811,6 +1117,9 @@ packages: deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + delaunator@5.1.0: + resolution: {integrity: sha512-AGrQ4QSgssa1NGmWmLPqN5NY2KajF5MqxetNEO+o0n3ZwZZeTmt7bBnvzHWrmkZFxGgr4HdyFgelzgi06otLuQ==} + delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -1017,6 +1326,9 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + hachure-fill@0.5.2: + resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1040,6 +1352,10 @@ packages: hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1052,6 +1368,13 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + internmap@1.0.1: + resolution: {integrity: sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1084,9 +1407,26 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + katex@0.16.45: + resolution: {integrity: sha512-pQpZbdBu7wCTmQUh7ufPmLr0pFoObnGUoL/yhtwJDgmmQpbkg/0HSVti25Fu4rmd1oCR6NGWe9vqTWuWv3GcNA==} + hasBin: true + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + khroma@2.1.0: + resolution: {integrity: sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==} + + langium@4.2.2: + resolution: {integrity: sha512-JUshTRAfHI4/MF9dH2WupvjSXyn8JBuUEWazB8ZVJUtXutT0doDlAv1XKbZ1Pb5sMexa8FF4CFBc0iiul7gbUQ==} + engines: {node: '>=20.10.0', npm: '>=10.2.3'} + + layout-base@1.0.2: + resolution: {integrity: sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==} + + layout-base@2.0.1: + resolution: {integrity: sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==} + levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1126,28 +1466,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -1198,6 +1534,11 @@ packages: engines: {node: '>= 18'} hasBin: true + marked@16.4.2: + resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -1205,6 +1546,9 @@ packages: memoize-one@6.0.0: resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + mermaid@11.14.0: + resolution: {integrity: sha512-GSGloRsBs+JINmmhl0JDwjpuezCsHB4WGI4NASHxL3fHo3o/BRXTxhDLKnln8/Q0lRFRyDdEjmk1/d5Sn1Xz8g==} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} @@ -1219,6 +1563,9 @@ packages: mitt@3.0.1: resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -1254,6 +1601,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1261,6 +1611,9 @@ packages: path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + path-data-parser@0.1.0: + resolution: {integrity: sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1269,6 +1622,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + perfect-debounce@1.0.0: resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} @@ -1288,6 +1644,15 @@ packages: typescript: optional: true + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + points-on-curve@0.2.0: + resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} + + points-on-path@0.2.1: + resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + postcss-selector-parser@6.1.2: resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} @@ -1318,11 +1683,23 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + robust-predicates@3.0.3: + resolution: {integrity: sha512-NS3levdsRIUOmiJ8FZWCP7LG3QpJyrs/TE0Zpf1yvZu8cAJJ6QMW92H1c7kWpdIHo8RvmLxN/o2JXTKHp74lUA==} + rollup@4.60.1: resolution: {integrity: sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + roughjs@4.6.6: + resolution: {integrity: sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==} + + rw@1.3.3: + resolution: {integrity: sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -1348,6 +1725,9 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} + stylis@4.4.0: + resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + superjson@2.2.6: resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} engines: {node: '>=16'} @@ -1363,10 +1743,18 @@ packages: resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} engines: {node: '>=6'} + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + ts-dedent@2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + tslib@2.3.0: resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} @@ -1383,6 +1771,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + update-browserslist-db@1.2.3: resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true @@ -1395,6 +1786,10 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uuid@11.1.0: + resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} + hasBin: true + vite@7.3.1: resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1435,6 +1830,23 @@ packages: yaml: optional: true + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + vscode-uri@3.1.0: resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} @@ -1494,6 +1906,11 @@ packages: snapshots: + '@antfu/install-pkg@1.1.0': + dependencies: + package-manager-detector: 1.6.0 + tinyexec: 1.1.1 + '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.28.5': {} @@ -1507,6 +1924,23 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@braintree/sanitize-url@7.1.2': {} + + '@chevrotain/cst-dts-gen@12.0.0': + dependencies: + '@chevrotain/gast': 12.0.0 + '@chevrotain/types': 12.0.0 + + '@chevrotain/gast@12.0.0': + dependencies: + '@chevrotain/types': 12.0.0 + + '@chevrotain/regexp-to-ast@12.0.0': {} + + '@chevrotain/types@12.0.0': {} + + '@chevrotain/utils@12.0.0': {} + '@ctrl/tinycolor@4.2.0': {} '@element-plus/icons-vue@2.3.2(vue@3.5.31(typescript@5.7.3))': @@ -1659,6 +2093,14 @@ snapshots: '@humanwhocodes/retry@0.4.3': {} + '@iconify/types@2.0.0': {} + + '@iconify/utils@3.1.1': + dependencies: + '@antfu/install-pkg': 1.1.0 + '@iconify/types': 2.0.0 + mlly: 1.8.2 + '@intlify/core-base@9.14.4': dependencies: '@intlify/message-compiler': 9.14.4 @@ -1690,6 +2132,10 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + '@mermaid-js/parser@1.1.0': + dependencies: + langium: 4.2.2 + '@rolldown/pluginutils@1.0.0-rc.2': {} '@rollup/rollup-android-arm-eabi@4.60.1': @@ -1837,10 +2283,131 @@ snapshots: tailwindcss: 4.2.2 vite: 7.3.1(jiti@2.6.1)(lightningcss@1.32.0) + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + '@types/estree@1.0.8': {} + '@types/geojson@7946.0.16': {} + '@types/json-schema@7.0.15': {} + '@types/katex@0.16.8': {} + '@types/lodash-es@4.17.12': dependencies: '@types/lodash': 4.17.24 @@ -1852,6 +2419,11 @@ snapshots: '@types/web-bluetooth@0.0.20': {} + '@upsetjs/venn.js@2.0.0': + optionalDependencies: + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + '@vitejs/plugin-vue@6.0.5(vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0))(vue@3.5.31(typescript@5.7.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.2 @@ -2053,6 +2625,19 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chevrotain-allstar@0.4.1(chevrotain@12.0.0): + dependencies: + chevrotain: 12.0.0 + lodash-es: 4.18.1 + + chevrotain@12.0.0: + dependencies: + '@chevrotain/cst-dts-gen': 12.0.0 + '@chevrotain/gast': 12.0.0 + '@chevrotain/regexp-to-ast': 12.0.0 + '@chevrotain/types': 12.0.0 + '@chevrotain/utils': 12.0.0 + color-convert@2.0.1: dependencies: color-name: 1.1.4 @@ -2063,12 +2648,26 @@ snapshots: dependencies: delayed-stream: 1.0.0 + commander@7.2.0: {} + + commander@8.3.0: {} + concat-map@0.0.1: {} + confbox@0.1.8: {} + copy-anything@4.0.5: dependencies: is-what: 5.5.0 + cose-base@1.0.3: + dependencies: + layout-base: 1.0.2 + + cose-base@2.2.0: + dependencies: + layout-base: 2.0.1 + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -2079,6 +2678,190 @@ snapshots: csstype@3.2.3: {} + cytoscape-cose-bilkent@4.1.0(cytoscape@3.33.2): + dependencies: + cose-base: 1.0.3 + cytoscape: 3.33.2 + + cytoscape-fcose@2.2.0(cytoscape@3.33.2): + dependencies: + cose-base: 2.2.0 + cytoscape: 3.33.2 + + cytoscape@3.33.2: {} + + d3-array@2.12.1: + dependencies: + internmap: 1.0.1 + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-axis@3.0.0: {} + + d3-brush@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3-chord@3.0.1: + dependencies: + d3-path: 3.1.0 + + d3-color@3.1.0: {} + + d3-contour@4.0.2: + dependencies: + d3-array: 3.2.4 + + d3-delaunay@6.0.4: + dependencies: + delaunator: 5.1.0 + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-dsv@3.0.1: + dependencies: + commander: 7.2.0 + iconv-lite: 0.6.3 + rw: 1.3.3 + + d3-ease@3.0.1: {} + + d3-fetch@3.0.1: + dependencies: + d3-dsv: 3.0.1 + + d3-force@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-quadtree: 3.0.1 + d3-timer: 3.0.1 + + d3-format@3.1.2: {} + + d3-geo@3.1.1: + dependencies: + d3-array: 3.2.4 + + d3-hierarchy@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@1.0.9: {} + + d3-path@3.1.0: {} + + d3-polygon@3.0.1: {} + + d3-quadtree@3.0.1: {} + + d3-random@3.0.1: {} + + d3-sankey@0.12.3: + dependencies: + d3-array: 2.12.1 + d3-shape: 1.3.7 + + d3-scale-chromatic@3.1.0: + dependencies: + d3-color: 3.1.0 + d3-interpolate: 3.0.1 + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@1.3.7: + dependencies: + d3-path: 1.0.9 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + d3@7.9.0: + dependencies: + d3-array: 3.2.4 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.2 + d3-delaunay: 6.0.4 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.2 + d3-geo: 3.1.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.1.0 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.1.0 + d3-selection: 3.0.0 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1(d3-selection@3.0.0) + d3-zoom: 3.0.0 + + dagre-d3-es@7.0.14: + dependencies: + d3: 7.9.0 + lodash-es: 4.18.1 + dayjs@1.11.20: {} debug@4.4.3: @@ -2087,6 +2870,10 @@ snapshots: deep-is@0.1.4: {} + delaunator@5.1.0: + dependencies: + robust-predicates: 3.0.3 + delayed-stream@1.0.0: {} detect-libc@2.1.2: {} @@ -2354,6 +3141,8 @@ snapshots: graceful-fs@4.2.11: {} + hachure-fill@0.5.2: {} + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -2370,6 +3159,10 @@ snapshots: hookable@5.5.3: {} + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} import-fresh@3.3.1: @@ -2379,6 +3172,10 @@ snapshots: imurmurhash@0.1.4: {} + internmap@1.0.1: {} + + internmap@2.0.3: {} + is-extglob@2.1.1: {} is-glob@4.0.3: @@ -2401,10 +3198,29 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + katex@0.16.45: + dependencies: + commander: 8.3.0 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 + khroma@2.1.0: {} + + langium@4.2.2: + dependencies: + '@chevrotain/regexp-to-ast': 12.0.0 + chevrotain: 12.0.0 + chevrotain-allstar: 0.4.1(chevrotain@12.0.0) + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + layout-base@1.0.2: {} + + layout-base@2.0.1: {} + levn@0.4.1: dependencies: prelude-ls: 1.2.1 @@ -2485,10 +3301,36 @@ snapshots: marked@15.0.12: {} + marked@16.4.2: {} + math-intrinsics@1.1.0: {} memoize-one@6.0.0: {} + mermaid@11.14.0: + dependencies: + '@braintree/sanitize-url': 7.1.2 + '@iconify/utils': 3.1.1 + '@mermaid-js/parser': 1.1.0 + '@types/d3': 7.4.3 + '@upsetjs/venn.js': 2.0.0 + cytoscape: 3.33.2 + cytoscape-cose-bilkent: 4.1.0(cytoscape@3.33.2) + cytoscape-fcose: 2.2.0(cytoscape@3.33.2) + d3: 7.9.0 + d3-sankey: 0.12.3 + dagre-d3-es: 7.0.14 + dayjs: 1.11.20 + dompurify: 3.3.3 + katex: 0.16.45 + khroma: 2.1.0 + lodash-es: 4.18.1 + marked: 16.4.2 + roughjs: 4.6.6 + stylis: 4.4.0 + ts-dedent: 2.2.0 + uuid: 11.1.0 + mime-db@1.52.0: {} mime-types@2.1.35: @@ -2501,6 +3343,13 @@ snapshots: mitt@3.0.1: {} + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + ms@2.1.3: {} muggle-string@0.4.1: {} @@ -2534,16 +3383,22 @@ snapshots: dependencies: p-limit: 3.1.0 + package-manager-detector@1.6.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 path-browserify@1.0.1: {} + path-data-parser@0.1.0: {} + path-exists@4.0.0: {} path-key@3.1.1: {} + pathe@2.0.3: {} + perfect-debounce@1.0.0: {} picocolors@1.1.1: {} @@ -2557,6 +3412,19 @@ snapshots: optionalDependencies: typescript: 5.7.3 + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + points-on-curve@0.2.0: {} + + points-on-path@0.2.1: + dependencies: + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 @@ -2580,6 +3448,8 @@ snapshots: rfdc@1.4.1: {} + robust-predicates@3.0.3: {} + rollup@4.60.1: dependencies: '@types/estree': 1.0.8 @@ -2611,6 +3481,17 @@ snapshots: '@rollup/rollup-win32-x64-msvc': 4.60.1 fsevents: 2.3.3 + roughjs@4.6.6: + dependencies: + hachure-fill: 0.5.2 + path-data-parser: 0.1.0 + points-on-curve: 0.2.0 + points-on-path: 0.2.1 + + rw@1.3.3: {} + + safer-buffer@2.1.2: {} + semver@7.7.4: {} shebang-command@2.0.0: @@ -2625,6 +3506,8 @@ snapshots: strip-json-comments@3.1.1: {} + stylis@4.4.0: {} + superjson@2.2.6: dependencies: copy-anything: 4.0.5 @@ -2637,11 +3520,15 @@ snapshots: tapable@2.3.2: {} + tinyexec@1.1.1: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + ts-dedent@2.2.0: {} + tslib@2.3.0: {} type-check@0.4.0: @@ -2652,6 +3539,8 @@ snapshots: typescript@5.7.3: {} + ufo@1.6.3: {} + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: browserslist: 4.28.2 @@ -2664,6 +3553,8 @@ snapshots: util-deprecate@1.0.2: {} + uuid@11.1.0: {} + vite@7.3.1(jiti@2.6.1)(lightningcss@1.32.0): dependencies: esbuild: 0.27.5 @@ -2677,6 +3568,21 @@ snapshots: jiti: 2.6.1 lightningcss: 1.32.0 + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + vscode-uri@3.1.0: {} vue-component-type-helpers@3.2.6: {} diff --git a/mateclaw-ui/src/assets/main.css b/mateclaw-ui/src/assets/main.css index 1a8a40eb..552155fe 100644 --- a/mateclaw-ui/src/assets/main.css +++ b/mateclaw-ui/src/assets/main.css @@ -117,6 +117,8 @@ --mc-attachment-color: #7B3F1E; --mc-thinking-bg: #F5F0EB; --mc-thinking-text: #5A4030; + --mc-mermaid-bg: #FAFAF8; + --mc-mermaid-border: #DDD5CC; --mc-thinking-hover: rgba(217, 119, 87, 0.1); --mc-thinking-icon-bg: rgba(217, 119, 87, 0.12); --mc-thinking-border: rgba(217, 119, 87, 0.2); @@ -232,6 +234,8 @@ html.dark { --mc-attachment-color: #F0C4A0; --mc-thinking-bg: #231C17; --mc-thinking-text: #C4A898; + --mc-mermaid-bg: #1A1410; + --mc-mermaid-border: #3D3028; --mc-thinking-hover: rgba(224, 136, 96, 0.1); --mc-thinking-icon-bg: rgba(224, 136, 96, 0.12); --mc-thinking-border: rgba(224, 136, 96, 0.2); diff --git a/mateclaw-ui/src/components/chat/MessageBubble.vue b/mateclaw-ui/src/components/chat/MessageBubble.vue index 0eb470fc..1105e384 100644 --- a/mateclaw-ui/src/components/chat/MessageBubble.vue +++ b/mateclaw-ui/src/components/chat/MessageBubble.vue @@ -1597,6 +1597,103 @@ watch(isGenerating, (generating) => { border-radius: 0; } +/* ===== Code block: line numbers via CSS counter (keeps DOM minimal) ===== */ +.markdown-body :deep(.code-block ol.hljs-lines) { + counter-reset: ln; + padding: 12px 16px; + margin: 0; + list-style: none; + font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace; +} +.markdown-body :deep(.code-block ol.hljs-lines > li) { + counter-increment: ln; + padding-left: 3.5em; + position: relative; + white-space: pre; + min-height: 1.3em; +} +.markdown-body :deep(.code-block ol.hljs-lines > li::before) { + content: counter(ln); + position: absolute; + left: 0; + width: 2.8em; + text-align: right; + color: #94a3b8; + user-select: none; + opacity: 0.55; + font-size: 0.92em; +} +/* Highlighted line spans inherit highlight.js colours; keep them on the line itself. */ + +/* ===== Code block: collapsible (long blocks / large JSON) ===== */ +.markdown-body :deep(details.code-block--collapsible) { + /* Reuse the .code-block visuals — already applied via the shared class. */ +} +.markdown-body :deep(details.code-block--collapsible > summary) { + list-style: none; + cursor: pointer; +} +.markdown-body :deep(details.code-block--collapsible > summary::-webkit-details-marker) { + display: none; +} +.markdown-body :deep(.code-block__lines) { + display: none; + font-size: 11px; + color: #94a3b8; + margin: 0 12px; + padding: 2px 8px; + border-radius: 4px; + background: rgba(255, 255, 255, 0.06); + user-select: none; +} +/* Show line-count badge only when the block is collapsible AND collapsed. */ +.markdown-body :deep(details.code-block--collapsible:not([open]) .code-block__lines) { + display: inline-block; +} +.markdown-body :deep(details.code-block--collapsible:not([open]) > pre) { + display: none; +} + +/* ===== Mermaid block ===== */ +.markdown-body :deep(.mermaid-block) { + margin: 14px 0; + padding: 16px; + border-radius: 12px; + background: var(--mc-mermaid-bg, #f8fafc); + border: 1px solid var(--mc-mermaid-border, #e2e8f0); + text-align: center; + overflow-x: auto; +} +.markdown-body :deep(.mermaid-block svg) { + max-width: 100%; + height: auto; +} +.markdown-body :deep(.mermaid-block.mermaid-error) { + background: #fef2f2; + border-color: #fecaca; + color: #b91c1c; + text-align: left; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + white-space: pre-wrap; +} + +/* ===== KaTeX inline / block ===== */ +.markdown-body :deep(.katex-inline) { + font-size: 1em; +} +.markdown-body :deep(.katex-block) { + display: block; + margin: 12px 0; + text-align: center; + overflow-x: auto; +} +.markdown-body :deep(.katex-error) { + color: #b91c1c; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 0.92em; +} + .markdown-body :deep(img) { max-width: 100%; height: auto; diff --git a/mateclaw-ui/src/composables/useKatexRenderer.ts b/mateclaw-ui/src/composables/useKatexRenderer.ts new file mode 100644 index 00000000..675d22c4 --- /dev/null +++ b/mateclaw-ui/src/composables/useKatexRenderer.ts @@ -0,0 +1,106 @@ +import { type Ref, watch, nextTick } from 'vue' + +// Lazy-load KaTeX to keep initial bundle small (~280 KB saved). +type KatexLib = typeof import('katex').default +let katexModule: KatexLib | null = null +async function getKatex(): Promise { + if (!katexModule) { + katexModule = (await import('katex')).default + // Side-effect import for the stylesheet — Vite tree-shakes this when + // unused at build time, so users without LaTeX in any visible message + // never download katex.min.css. + await import('katex/dist/katex.min.css') + } + return katexModule +} + +/** + * Composable that observes a container for `.katex-inline` / `.katex-block` + * placeholder elements (emitted by `useMarkdownRenderer.preprocessLatex`) + * and replaces them with KaTeX-typeset HTML. + * + * Mirrors the structure of `useEChartsRenderer` so behaviour is predictable: + * MutationObserver picks up new placeholders as messages stream in, and + * mounted elements are tracked via WeakMap to avoid double-renders. + */ +export function useKatexRenderer(containerRef: Ref) { + // Track elements we've already rendered so re-scans are cheap. + const rendered = new WeakSet() + const mounting = new Set() + let observer: MutationObserver | null = null + + async function mountElement(el: HTMLElement) { + if (rendered.has(el) || mounting.has(el)) return + mounting.add(el) + const tex = decodeURIComponent(el.getAttribute('data-tex') || '') + if (!tex) { + mounting.delete(el) + return + } + try { + const katex = await getKatex() + const isBlock = el.classList.contains('katex-block') + katex.render(tex, el, { + throwOnError: false, // failed input renders as red TeX, never throws + displayMode: isBlock, + output: 'html', + trust: false, // don't trust input (it's user/LLM content) + strict: 'ignore', // tolerate non-standard commands + }) + rendered.add(el) + } catch (e) { + console.error('[KatexRenderer] render error:', e) + // Fall back to the raw TeX so the user can still read it. + el.textContent = tex + el.classList.add('katex-error') + } finally { + mounting.delete(el) + } + } + + function scanAndMount() { + const container = containerRef.value + if (!container) return + const blocks = container.querySelectorAll( + '.katex-inline[data-tex]:not(.katex-error), .katex-block[data-tex]:not(.katex-error)', + ) + blocks.forEach((el) => { + if (!rendered.has(el) && !mounting.has(el) && !el.querySelector('.katex')) { + mountElement(el) + } + }) + } + + function attachObserver(container: HTMLElement) { + observer?.disconnect() + observer = new MutationObserver(() => { + // Defer to nextTick so all of Vue's batched DOM updates settle first. + nextTick(() => scanAndMount()) + }) + observer.observe(container, { childList: true, subtree: true }) + scanAndMount() + } + + function startObserving() { + const container = containerRef.value + if (container) attachObserver(container) + } + + // If the ref is null at composable-call time (e.g. v-if container), wait + // for it to materialise. + const stopContainerWatch = watch( + () => containerRef.value, + (newContainer) => { + if (newContainer && !observer) attachObserver(newContainer) + }, + { immediate: false }, + ) + + function dispose() { + stopContainerWatch() + observer?.disconnect() + observer = null + } + + return { startObserving, dispose, scanAndMount } +} diff --git a/mateclaw-ui/src/composables/useMarkdownRenderer.ts b/mateclaw-ui/src/composables/useMarkdownRenderer.ts index a6ce981f..aeb0602e 100644 --- a/mateclaw-ui/src/composables/useMarkdownRenderer.ts +++ b/mateclaw-ui/src/composables/useMarkdownRenderer.ts @@ -1,8 +1,11 @@ import { Marked } from 'marked' +import type { Tokens } from 'marked' import hljs from 'highlight.js' import DOMPurify from 'dompurify' -// 语言映射 +// --------------------------------------------------------------------------- +// Language metadata +// --------------------------------------------------------------------------- const LANG_DISPLAY: Record = { js: 'JavaScript', javascript: 'JavaScript', ts: 'TypeScript', typescript: 'TypeScript', py: 'Python', python: 'Python', java: 'Java', kt: 'Kotlin', kotlin: 'Kotlin', @@ -37,28 +40,162 @@ function escapeHtml(str: string): string { return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"') } -// marked v15 requires a plain object renderer — class instances extending Renderer are NOT dispatched +// --------------------------------------------------------------------------- +// Code block thresholds (must match `useMarkdownRenderer` doc comments) +// --------------------------------------------------------------------------- +/** Lines >= this trigger collapsible
wrap. */ +const COLLAPSE_LINE_THRESHOLD = 20 +/** JSON blob char count >= this triggers collapse even when line count is low. */ +const COLLAPSE_JSON_CHAR_THRESHOLD = 800 + +// --------------------------------------------------------------------------- +// Link safety +// --------------------------------------------------------------------------- +/** + * Scheme whitelist. Only http(s), mailto, fragment, and same-origin paths + * (absolute `/...`, relative `./...` / `../...`) are permitted. Everything + * else (javascript:, data:, vbscript:, file:, …) is degraded to plain text. + */ +const SAFE_LINK_RE = /^(https?:|mailto:|#|\/|\.\/|\.\.\/)/i + +// --------------------------------------------------------------------------- +// LaTeX pre-processor +// --------------------------------------------------------------------------- +// `$$ ... $$` (block) and `$ ... $` (inline) are extracted from raw markdown +// and replaced with HTML placeholders that survive marked + DOMPurify. The +// post-render KaTeX composable (useKatexRenderer) finds them by class + +// data-tex attribute and mounts the typeset output. +// +// We deliberately walk the source character-by-character rather than running +// a global regex, so that fenced/inline code blocks are skipped — otherwise +// dollar signs inside Bash snippets or JSON blobs would be misinterpreted. + +function preprocessLatex(text: string): string { + let out = '' + let i = 0 + let inFence = false + let fenceMarker = '' + while (i < text.length) { + // Detect fence open/close at line start. + if (i === 0 || text[i - 1] === '\n') { + const fenceMatch = /^(```+|~~~+)([^\n]*)/.exec(text.slice(i)) + if (fenceMatch) { + const marker = fenceMatch[1] + if (!inFence) { + inFence = true + fenceMarker = marker + } else if (marker.length >= fenceMarker.length && marker[0] === fenceMarker[0]) { + inFence = false + fenceMarker = '' + } + out += fenceMatch[0] + i += fenceMatch[0].length + continue + } + } + if (inFence) { + out += text[i++] + continue + } + + // Inline code: copy verbatim until the matching backtick run. + if (text[i] === '`') { + let n = 0 + while (text[i + n] === '`') n++ + const tickRun = '`'.repeat(n) + const close = text.indexOf(tickRun, i + n) + if (close < 0) { + // Unmatched — treat the rest as text but still advance past the ticks. + out += text[i++] + continue + } + out += text.slice(i, close + n) + i = close + n + continue + } + + // LaTeX-style block math: \[...\] — must be checked BEFORE marked sees + // the source, because CommonMark eats the backslash escape (`\[ → [`) + // and the marker would be lost. LLMs (DeepSeek, Qwen, Claude) emit this + // form heavily for display equations. + if (text[i] === '\\' && text[i + 1] === '[') { + const close = text.indexOf('\\]', i + 2) + // Bound length so a stray `\[` doesn't swallow the rest of the doc. + if (close > 0 && close - i < 800) { + const tex = text.slice(i + 2, close) + out += `\n\n
\n\n` + i = close + 2 + continue + } + } + // LaTeX-style inline math: \(...\) + if (text[i] === '\\' && text[i + 1] === '(') { + const close = text.indexOf('\\)', i + 2) + if (close > 0 && close - i < 400) { + const tex = text.slice(i + 2, close) + out += `` + i = close + 2 + continue + } + } + // Block math: $$...$$ + if (text[i] === '$' && text[i + 1] === '$') { + const close = text.indexOf('$$', i + 2) + if (close > 0) { + const tex = text.slice(i + 2, close) + // Wrap in newlines so marked treats the placeholder as its own block, + // not glued onto a surrounding paragraph (which would make
a + // direct child of

— invalid HTML the browser silently splits). + out += `\n\n

\n\n` + i = close + 2 + continue + } + } + // Inline math: $...$ — require non-whitespace adjacent to the dollars + // so that "$5.99" or "saved $10" are NOT treated as math. + if (text[i] === '$') { + const m = /^\$([^$\n]+?)\$(?!\d)/.exec(text.slice(i)) + if (m && !/^\s/.test(m[1]) && !/\s$/.test(m[1])) { + const tex = m[1] + out += `` + i += m[0].length + continue + } + } + + out += text[i++] + } + return out +} + +// --------------------------------------------------------------------------- +// Custom renderer (marked v15 requires a plain object — class instances are +// NOT dispatched). +// --------------------------------------------------------------------------- const customRenderer = { code({ text, lang }: { type: string; raw: string; text: string; lang?: string }): string { const rawCode = text || '' const infoStr = (lang || '').split(/\s/)[0] - // ECharts chart block: render as a placeholder div + // Mermaid: ship raw source through a placeholder div for the + // useMermaidRenderer post-mount step. Skip syntax highlighting entirely. + if (infoStr === 'mermaid') { + return `
` + } + + // ECharts: same pattern, mounted by useEChartsRenderer. if (infoStr === 'echarts') { - const encodedOption = encodeURIComponent(rawCode) - return `
` + return `
` } const detectedLang = extractLang(infoStr) - const hasLanguage = detectedLang && hljs.getLanguage(detectedLang) + const hasLanguage = !!detectedLang && !!hljs.getLanguage(detectedLang) let highlighted: string try { - if (hasLanguage) { - highlighted = hljs.highlight(rawCode, { language: detectedLang }).value - } else { - highlighted = hljs.highlightAuto(rawCode).value - } + highlighted = hasLanguage + ? hljs.highlight(rawCode, { language: detectedLang }).value + : hljs.highlightAuto(rawCode).value } catch { highlighted = escapeHtml(rawCode) } @@ -67,41 +204,149 @@ const customRenderer = { const encodedCode = encodeURIComponent(rawCode) const langClass = hasLanguage ? ` language-${detectedLang}` : '' - return `
` - + `
` + // Split into one
  • per source line so CSS counter renders the gutter. + // We trim a trailing empty line if highlight.js produced one (common when + // the user's fenced block ends with a newline), to avoid a blank tail row. + const rawLines = highlighted.split('\n') + if (rawLines.length && rawLines[rawLines.length - 1] === '') rawLines.pop() + const lineCount = rawLines.length || 1 + const linesHtml = `
      ${rawLines.map(l => `
    1. ${l || ' '}
    2. `).join('')}
    ` + + const isJson = detectedLang === 'json' + const isLongJson = isJson && rawCode.length >= COLLAPSE_JSON_CHAR_THRESHOLD + const shouldCollapse = lineCount >= COLLAPSE_LINE_THRESHOLD || isLongJson + // Default-open for normal long code (the user wants to see it; the + // collapsible header is just an opt-in fold). Default-closed only for + // giant JSON blobs, which are typically noisy tool-call output. + const openByDefault = !isLongJson + + const headerHtml = `
    ` + `${escapeHtml(langLabel)}` - + `
    ` - + `
    ${highlighted}
    ` - + `
  • ` + + const codeBody = `
    ${linesHtml}
    ` + + if (shouldCollapse) { + //
    / gives a native, no-JS collapse affordance. The + // summary holds the header (lang badge + line count + copy button); the + //
     sits in the 
    body and is hidden by CSS until expanded. + const openAttr = openByDefault ? ' open' : '' + return `
    ` + + `${headerHtml}` + + codeBody + + `
    ` + } + return `
    ${headerHtml}${codeBody}
    ` + }, + + link({ href, title, tokens }: Tokens.Link): string { + // marked v15 passes already-parsed inline tokens; render them ourselves so + // that the inner content keeps any bold/italic formatting from `[**x**](u)`. + const innerHtml = (this as unknown as { parser: { parseInline: (t: unknown[]) => string } }) + .parser.parseInline(tokens) + + if (!href || !SAFE_LINK_RE.test(href)) { + // Dangerous scheme — render the inner content as plain content (no anchor). + return innerHtml + } + let extra = '' + try { + const url = new URL(href, typeof window !== 'undefined' ? window.location.href : 'http://localhost/') + if (typeof window !== 'undefined' && url.origin !== window.location.origin) { + extra = ' target="_blank" rel="noopener noreferrer"' + } + } catch { + // Malformed URL — treat as same-origin (relative link path). + } + const titleAttr = title ? ` title="${escapeHtml(title)}"` : '' + return `${innerHtml}` }, } -// 创建 marked 实例 +// --------------------------------------------------------------------------- +// marked instance +// --------------------------------------------------------------------------- const markedInstance = new Marked({ gfm: true, breaks: true, renderer: customRenderer, }) -// 配置 DOMPurify — 允许 Markdown + 代码块复制按钮的标签和属性 +// --------------------------------------------------------------------------- +// DOMPurify config — allow Markdown + custom blocks (code-block, KaTeX/Mermaid +// placeholders) and the inline copy SVG button. +// --------------------------------------------------------------------------- const purifyConfig = { - ADD_ATTR: ['target', 'rel', 'class', 'data-code', 'data-echarts-option', 'data-wiki-title', 'data-slug', 'type', 'viewBox', 'fill', 'stroke', 'stroke-width', 'd', 'x', 'y', 'width', 'height', 'rx', 'ry', 'points'], - ADD_TAGS: ['input', 'button', 'svg', 'path', 'rect', 'polyline', 'circle', 'line', 'span'], + ADD_ATTR: [ + 'target', 'rel', 'class', + 'data-code', 'data-echarts-option', 'data-wiki-title', 'data-slug', + 'data-tex', 'data-mermaid', + 'aria-label', 'open', + 'type', 'viewBox', 'fill', 'stroke', 'stroke-width', 'd', + 'x', 'y', 'width', 'height', 'rx', 'ry', 'points', + ], + ADD_TAGS: [ + 'input', 'button', 'svg', 'path', 'rect', 'polyline', 'circle', 'line', + 'span', 'details', 'summary', + ], + // Defence in depth: even if a malicious href slips past our link() + // override, DOMPurify drops anything outside this whitelist. + ALLOWED_URI_REGEXP: /^(?:https?:|mailto:|#|\/|\.\/|\.\.\/)/i, } +// --------------------------------------------------------------------------- +// LRU render cache +// --------------------------------------------------------------------------- +// Streaming token-by-token defeats this (each delta produces a new key) but +// scrolling history and re-renders of completed messages are common, and the +// cost of marked + highlight.js + DOMPurify is non-trivial for long messages. +const RENDER_CACHE = new Map() +const RENDER_CACHE_CAP = 200 + +function cacheKey(text: string): string { + // Compact key — collisions on the order of 10^-6 in single-conversation + // scope, and a false hit only causes a "stale" render of unchanged content + // (no security implication since cached values are sanitized HTML). + return `${text.length}:${text.slice(0, 40)}:${text.slice(-40)}` +} + +// --------------------------------------------------------------------------- +// Public API +// --------------------------------------------------------------------------- export function useMarkdownRenderer() { function renderMarkdown(content: string): string { if (!content) return '' - // 将 [[Wiki Link]] 转换为可点击的 Wiki 引用链接 - const withWikiLinks = content.replace( + const k = cacheKey(content) + const cached = RENDER_CACHE.get(k) + if (cached !== undefined) { + // Refresh LRU position — re-insert at the tail. + RENDER_CACHE.delete(k) + RENDER_CACHE.set(k, cached) + return cached + } + + // 1. LaTeX placeholders (skips fenced/inline code). + const withLatex = preprocessLatex(content) + // 2. Wiki link substitution: [[Title]] → . + const withWikiLinks = withLatex.replace( /\[\[([^\]]+)\]\]/g, '$1' ) + // 3. Marked → 4. DOMPurify. const rawHtml = markedInstance.parse(withWikiLinks) as string - return DOMPurify.sanitize(rawHtml, purifyConfig) + const result = DOMPurify.sanitize(rawHtml, purifyConfig) + + // Evict oldest entry when at capacity (Map preserves insertion order). + if (RENDER_CACHE.size >= RENDER_CACHE_CAP) { + const oldestKey = RENDER_CACHE.keys().next().value + if (oldestKey !== undefined) RENDER_CACHE.delete(oldestKey) + } + RENDER_CACHE.set(k, result) + return result } function escapeText(text: string): string { @@ -115,6 +360,6 @@ export function useMarkdownRenderer() { } } -// 导出单例供直接使用 +// Direct singletons for tests / advanced callers. export { markedInstance, purifyConfig } export default markedInstance diff --git a/mateclaw-ui/src/composables/useMermaidRenderer.ts b/mateclaw-ui/src/composables/useMermaidRenderer.ts new file mode 100644 index 00000000..7573b709 --- /dev/null +++ b/mateclaw-ui/src/composables/useMermaidRenderer.ts @@ -0,0 +1,151 @@ +import { type Ref, watch, nextTick } from 'vue' +import { useThemeStore } from '@/stores/useThemeStore' + +// Lazy-load Mermaid (~600 KB minified) — only fetched when a chat message +// actually contains a ```mermaid block. +type MermaidLib = typeof import('mermaid').default +let mermaidModule: MermaidLib | null = null +let initializedTheme: 'dark' | 'default' | null = null + +async function getMermaid(theme: 'dark' | 'default'): Promise { + if (!mermaidModule) { + mermaidModule = (await import('mermaid')).default + } + if (initializedTheme !== theme) { + // securityLevel:'strict' disables click handlers and inline JS — important + // because Mermaid sources come from arbitrary LLM output. Coupled with + // our existing DOMPurify pass it provides defence in depth. + mermaidModule.initialize({ + startOnLoad: false, + theme, + securityLevel: 'strict', + flowchart: { useMaxWidth: true, htmlLabels: true }, + themeVariables: theme === 'dark' + ? { darkMode: true, background: '#1e293b' } + : {}, + }) + initializedTheme = theme + } + return mermaidModule +} + +let renderCounter = 0 + +/** + * Composable that observes a container for `.mermaid-block[data-mermaid]` + * placeholders (emitted by `useMarkdownRenderer.code()` for ```mermaid fenced + * blocks) and replaces them with rendered SVG diagrams. + * + * Mirrors `useEChartsRenderer` / `useKatexRenderer` so the three post-render + * augmentations behave identically: lazy-loaded module, MutationObserver for + * streaming inserts, theme reactivity via re-render on dark-mode toggle. + */ +export function useMermaidRenderer(containerRef: Ref) { + const themeStore = useThemeStore() + const rendered = new WeakSet() + const mounting = new Set() + const tracked = new Set() + let observer: MutationObserver | null = null + + async function mountBlock(el: HTMLElement) { + if (rendered.has(el) || mounting.has(el)) return + mounting.add(el) + const src = decodeURIComponent(el.getAttribute('data-mermaid') || '') + if (!src.trim()) { + mounting.delete(el) + return + } + try { + const theme = themeStore.isDark ? 'dark' : 'default' + const mermaid = await getMermaid(theme) + + // Pre-parse so we can fall back gracefully on bad input. Without this, + // mermaid 11.x's render() emits its own bomb-icon "Syntax error" SVG + // INSTEAD of throwing — which (1) looks ugly and (2) wouldn't trigger + // our catch block, so the same broken source would re-render on every + // streaming token mutation, flooding the console. + const parsed = await mermaid.parse(src, { suppressErrors: true }) + if (!parsed) { + throw new Error('mermaid parse failed') + } + const id = `mc-mermaid-${++renderCounter}` + const { svg } = await mermaid.render(id, src) + el.innerHTML = svg + rendered.add(el) + tracked.add(el) + } catch (e) { + // Streaming-aware: if the LLM is still emitting tokens the source may + // legitimately be incomplete on each pass. Mark this element done so + // we don't retry on every mutation; when streaming finishes Vue's v-html + // produces a fresh element (different identity) and we'll try again. + console.warn('[MermaidRenderer] failed to render — showing source:', e) + el.classList.add('mermaid-error') + el.textContent = src + rendered.add(el) + } finally { + mounting.delete(el) + } + } + + function scanAndMount() { + const container = containerRef.value + if (!container) return + const blocks = container.querySelectorAll( + '.mermaid-block[data-mermaid]:not(.mermaid-error)', + ) + blocks.forEach((el) => { + if (!rendered.has(el) && !mounting.has(el) && !el.querySelector('svg')) { + mountBlock(el) + } + }) + } + + function rebuildAll() { + // Theme switch: clear rendered SVGs and re-mount with the new theme. + tracked.forEach((el) => { + el.innerHTML = '' + rendered.delete(el) + }) + tracked.clear() + initializedTheme = null // force re-init with the new theme + scanAndMount() + } + + function attachObserver(container: HTMLElement) { + observer?.disconnect() + observer = new MutationObserver(() => { + nextTick(() => scanAndMount()) + }) + observer.observe(container, { childList: true, subtree: true }) + scanAndMount() + } + + function startObserving() { + const container = containerRef.value + if (container) attachObserver(container) + } + + const stopContainerWatch = watch( + () => containerRef.value, + (newContainer) => { + if (newContainer && !observer) attachObserver(newContainer) + }, + { immediate: false }, + ) + + // Re-render on theme change so diagrams pick up the new colour palette. + const stopThemeWatch = watch( + () => themeStore.isDark, + () => rebuildAll(), + ) + + function dispose() { + stopContainerWatch() + stopThemeWatch() + observer?.disconnect() + observer = null + tracked.clear() + } + + return { startObserving, dispose, scanAndMount } +} diff --git a/mateclaw-ui/src/i18n/locales/en-US.ts b/mateclaw-ui/src/i18n/locales/en-US.ts index 4ee983e7..c1e741d2 100644 --- a/mateclaw-ui/src/i18n/locales/en-US.ts +++ b/mateclaw-ui/src/i18n/locales/en-US.ts @@ -1238,6 +1238,7 @@ export default { addText: 'Add Text', noRawMaterials: 'No raw materials yet', reprocess: 'Reprocess', + resume: 'Resume', processAll: 'Process All Pending', materialTitle: 'Title', materialContent: 'Content', diff --git a/mateclaw-ui/src/i18n/locales/zh-CN.ts b/mateclaw-ui/src/i18n/locales/zh-CN.ts index c10a88cd..4138f148 100644 --- a/mateclaw-ui/src/i18n/locales/zh-CN.ts +++ b/mateclaw-ui/src/i18n/locales/zh-CN.ts @@ -1248,6 +1248,7 @@ export default { addText: '添加文本', noRawMaterials: '暂无原始材料', reprocess: '重新处理', + resume: '继续生成', processAll: '处理所有待处理材料', materialTitle: '标题', materialContent: '内容', diff --git a/mateclaw-ui/src/views/AgentContext.vue b/mateclaw-ui/src/views/AgentContext.vue index 214fcf2d..fa9a50bb 100644 --- a/mateclaw-ui/src/views/AgentContext.vue +++ b/mateclaw-ui/src/views/AgentContext.vue @@ -320,6 +320,11 @@ async function fetchPromptFiles() { function handlePreviewClick(e: MouseEvent) { const btn = (e.target as HTMLElement).closest('.code-block__copy') as HTMLElement | null if (!btn) return + // Same as ChatConsole: the copy button now lives inside
    + // for collapsible blocks. Without preventDefault the click toggles the + // details open state in addition to copying. + e.preventDefault() + e.stopPropagation() const encoded = btn.getAttribute('data-code') if (!encoded) return const code = decodeURIComponent(encoded) diff --git a/mateclaw-ui/src/views/ChatConsole.vue b/mateclaw-ui/src/views/ChatConsole.vue index 7120e658..39f708bb 100644 --- a/mateclaw-ui/src/views/ChatConsole.vue +++ b/mateclaw-ui/src/views/ChatConsole.vue @@ -291,6 +291,8 @@ import StreamLoadingBar from '@/components/chat/StreamLoadingBar.vue' import TalkMode from '@/components/chat/TalkMode.vue' import ModelSelector from '@/components/chat/ModelSelector.vue' import { useEChartsRenderer } from '@/composables/useEChartsRenderer' +import { useKatexRenderer } from '@/composables/useKatexRenderer' +import { useMermaidRenderer } from '@/composables/useMermaidRenderer' // ============ Talk Mode ============ const showTalkMode = ref(false) @@ -555,9 +557,13 @@ async function collectFilesFromEntries(dirEntries: FileSystemDirectoryEntry[]): const messageListRef = ref | null>(null) const chatInputRef = ref | null>(null) -// ECharts: extract DOM element from MessageList component ref +// Post-render augmentations (ECharts, KaTeX, Mermaid) all watch the same +// MessageList container — placeholders emitted by useMarkdownRenderer get +// upgraded in place after Vue paints the rendered Markdown HTML. const echartsContainerRef = computed(() => messageListRef.value?.$el as HTMLElement | null) const { startObserving: startECharts, dispose: disposeECharts } = useEChartsRenderer(echartsContainerRef) +const { startObserving: startKatex, dispose: disposeKatex } = useKatexRenderer(echartsContainerRef) +const { startObserving: startMermaid, dispose: disposeMermaid } = useMermaidRenderer(echartsContainerRef) // 使用 useChat composable const { @@ -767,6 +773,8 @@ onMounted(async () => { document.addEventListener('keydown', handleKeyboardShortcuts) document.addEventListener('click', handleCodeCopy) startECharts() + startKatex() + startMermaid() mobileQuery = window.matchMedia('(max-width: 768px)') handleMobileChange(mobileQuery) mobileQuery.addEventListener('change', handleMobileChange) @@ -782,6 +790,8 @@ onBeforeUnmount(() => { document.removeEventListener('keydown', handleKeyboardShortcuts) document.removeEventListener('click', handleCodeCopy) disposeECharts() + disposeKatex() + disposeMermaid() mobileQuery?.removeEventListener('change', handleMobileChange) mediumQuery?.removeEventListener('change', handleConvMediumChange) if (activityPollTimer !== null) { @@ -1442,6 +1452,12 @@ function formatConversationTime(time?: string) { function handleCodeCopy(e: MouseEvent) { const btn = (e.target as HTMLElement).closest('.code-block__copy') as HTMLElement | null if (!btn) return + // The copy button now sits inside
    for collapsible code + // blocks. Without preventDefault the click would also toggle the details + // open state — a regression introduced when we wrapped long blocks in + //
    . stopPropagation guards against any future ancestor handlers. + e.preventDefault() + e.stopPropagation() const encoded = btn.getAttribute('data-code') if (!encoded) return const code = decodeURIComponent(encoded) diff --git a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue index e3d9c2be..3936e41f 100644 --- a/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue +++ b/mateclaw-ui/src/views/Wiki/components/RawMaterialPanel.vue @@ -154,7 +154,16 @@
    +