Compare commits
5 Commits
ab7b7fb189
...
ed97b30d51
| Author | SHA1 | Date | |
|---|---|---|---|
| ed97b30d51 | |||
| 26ecaadb26 | |||
| 9c9afbd108 | |||
| 628b592577 | |||
| 29bf61f7a3 |
229
bun.lock
229
bun.lock
@@ -10,6 +10,8 @@
|
||||
"@ai-sdk/openai-compatible": "^2.0.48",
|
||||
"@ai-sdk/react": "^3.0.195",
|
||||
"@ant-design/icons": "^6.2.3",
|
||||
"@ant-design/x": "^2.7.0",
|
||||
"@ant-design/x-markdown": "^2.7.0",
|
||||
"@sinclair/typebox": "^0.34.49",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"ai": "^6.0.193",
|
||||
@@ -17,6 +19,8 @@
|
||||
"antd": "^6.4.3",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"es-toolkit": "^1.47.0",
|
||||
"overlayscrollbars": "^2.16.0",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"pino-roll": "^4.0.0",
|
||||
@@ -24,7 +28,6 @@
|
||||
"react-dom": "^19.2.6",
|
||||
"react-router": "^7.15.1",
|
||||
"recharts": "^3.8.1",
|
||||
"streamdown": "^2.5.0",
|
||||
"zod": "^4.4.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -86,6 +89,10 @@
|
||||
|
||||
"@ant-design/react-slick": ["@ant-design/react-slick@2.0.0", "https://registry.npmmirror.com/@ant-design/react-slick/-/react-slick-2.0.0.tgz", { "dependencies": { "@babel/runtime": "^7.28.4", "clsx": "^2.1.1", "json2mq": "^0.2.0", "throttle-debounce": "^5.0.0" }, "peerDependencies": { "react": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^0.14.0 || ^15.0.1 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-HMS9sRoEmZey8LsE/Yo6+klhlzU12PisjrVcydW3So7RdklyEd2qehyU6a7Yp+OYN72mgsYs3NFCyP2lCPFVqg=="],
|
||||
|
||||
"@ant-design/x": ["@ant-design/x@2.7.0", "", { "dependencies": { "@ant-design/colors": "^8.0.0", "@ant-design/cssinjs": "^2.0.1", "@ant-design/cssinjs-utils": "^2.0.2", "@ant-design/fast-color": "^3.0.0", "@ant-design/icons": "^6.0.0", "@babel/runtime": "^7.25.6", "@rc-component/motion": "^1.1.6", "@rc-component/resize-observer": "^1.0.1", "@rc-component/util": "^1.4.0", "clsx": "^2.1.1", "lodash.throttle": "^4.1.1", "mermaid": "^11.12.1", "react-syntax-highlighter": "^16.1.0" }, "peerDependencies": { "antd": "^6.1.1", "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-p5OtxQ9elbmeFRllGt1yj5wi6VHe41PIAmwrBU/OlaYydru5qIYsJzCS3DPRhkWkVdErU5oZwU74Z2oce2F5Uw=="],
|
||||
|
||||
"@ant-design/x-markdown": ["@ant-design/x-markdown@2.7.0", "", { "dependencies": { "clsx": "^2.1.1", "dompurify": "^3.2.6", "html-react-parser": "^5.2.13", "katex": "^0.16.22", "marked": "^15.0.12" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-tmuwbeulTD5nfO15VCb3mN13iCTT106626dVFxGjhj1tWnmLL+fIngyv3U8SOTq94+Baor6QdSWtPZluVKCIbw=="],
|
||||
|
||||
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
|
||||
|
||||
"@asamuzakjp/css-color": ["@asamuzakjp/css-color@5.1.11", "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", { "dependencies": { "@asamuzakjp/generational-cache": "^1.0.1", "@csstools/css-calc": "^3.2.0", "@csstools/css-color-parser": "^4.1.0", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg=="],
|
||||
@@ -516,14 +523,10 @@
|
||||
|
||||
"@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="],
|
||||
|
||||
"@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
|
||||
|
||||
"@types/esrecurse": ["@types/esrecurse@4.3.1", "https://registry.npmmirror.com/@types/esrecurse/-/esrecurse-4.3.1.tgz", {}, "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="],
|
||||
|
||||
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
@@ -534,12 +537,10 @@
|
||||
|
||||
"@types/json5": ["@types/json5@0.0.29", "https://registry.npmmirror.com/@types/json5/-/json5-0.0.29.tgz", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
|
||||
|
||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||
|
||||
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
|
||||
|
||||
"@types/node": ["@types/node@25.6.2", "https://registry.npmmirror.com/@types/node/-/node-25.6.2.tgz", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
|
||||
|
||||
"@types/prismjs": ["@types/prismjs@1.26.6", "", {}, "sha512-vqlvI7qlMvcCBbVe0AKAb4f97//Hy0EBTaiW8AalRnG/xAN5zOiWWyrNqNXeq8+KAuvRewjCVY1+IPxk4RdNYw=="],
|
||||
|
||||
"@types/react": ["@types/react@19.2.15", "https://registry.npmmirror.com/@types/react/-/react-19.2.15.tgz", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-eRwcGNHve+E8qtEQSSRl6urh+rFop4v8gm6O8rGv25CodbvFdLjA1vVQ1KkiFE0w0UPOnb8tDiFKL5lp0rtY5Q=="],
|
||||
|
||||
"@types/react-dom": ["@types/react-dom@19.2.3", "https://registry.npmmirror.com/@types/react-dom/-/react-dom-19.2.3.tgz", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||
@@ -572,8 +573,6 @@
|
||||
|
||||
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "https://registry.npmmirror.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm-eabi": ["@unrs/resolver-binding-android-arm-eabi@1.11.1", "https://registry.npmmirror.com/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", { "os": "android", "cpu": "arm" }, "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw=="],
|
||||
|
||||
"@unrs/resolver-binding-android-arm64": ["@unrs/resolver-binding-android-arm64@1.11.1", "https://registry.npmmirror.com/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", { "os": "android", "cpu": "arm64" }, "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g=="],
|
||||
@@ -658,8 +657,6 @@
|
||||
|
||||
"available-typed-arrays": ["available-typed-arrays@1.0.7", "https://registry.npmmirror.com/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
|
||||
|
||||
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
|
||||
|
||||
"balanced-match": ["balanced-match@4.0.4", "https://registry.npmmirror.com/balanced-match/-/balanced-match-4.0.4.tgz", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.28", "https://registry.npmmirror.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.28.tgz", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-Ic44hnOtFIgravCunj1ifSoQPSUrkNiJuH9Mf6jr2jjoA74icqV8wU0KuadXeOR8zuIJMOoTv0GuQjZ9ZYNMeA=="],
|
||||
@@ -684,12 +681,8 @@
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001792", "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
|
||||
|
||||
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
||||
|
||||
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||
|
||||
"character-reference-invalid": ["character-reference-invalid@2.0.1", "", {}, "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw=="],
|
||||
@@ -842,14 +835,20 @@
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"doctrine": ["doctrine@2.1.0", "https://registry.npmmirror.com/doctrine/-/doctrine-2.1.0.tgz", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
|
||||
|
||||
"dom-accessibility-api": ["dom-accessibility-api@0.5.16", "https://registry.npmmirror.com/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
|
||||
|
||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||
|
||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||
|
||||
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
|
||||
|
||||
"dompurify": ["dompurify@3.4.7", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA=="],
|
||||
|
||||
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
|
||||
|
||||
"dot-prop": ["dot-prop@5.3.0", "https://registry.npmmirror.com/dot-prop/-/dot-prop-5.3.0.tgz", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="],
|
||||
|
||||
"drizzle-kit": ["drizzle-kit@0.31.10", "https://registry.npmmirror.com/drizzle-kit/-/drizzle-kit-0.31.10.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="],
|
||||
@@ -928,16 +927,12 @@
|
||||
|
||||
"estraverse": ["estraverse@5.3.0", "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"estree-util-is-identifier-name": ["estree-util-is-identifier-name@3.0.0", "", {}, "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg=="],
|
||||
|
||||
"esutils": ["esutils@2.0.3", "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
|
||||
|
||||
"eventemitter3": ["eventemitter3@5.0.4", "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-5.0.4.tgz", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
|
||||
|
||||
"eventsource-parser": ["eventsource-parser@3.1.0", "https://registry.npmmirror.com/eventsource-parser/-/eventsource-parser-3.1.0.tgz", {}, "sha512-kJezFj9YFAMLeORyi7aCLxLbD5/qWMQnoMVlVPyHIll7lgRJCc3JVln9Vgl9nwQi0YkMnhdGTMNn7CkRRAptMg=="],
|
||||
|
||||
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
|
||||
|
||||
"fast-copy": ["fast-copy@4.0.3", "https://registry.npmmirror.com/fast-copy/-/fast-copy-4.0.3.tgz", {}, "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
@@ -952,6 +947,8 @@
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.2", "https://registry.npmmirror.com/fast-uri/-/fast-uri-3.1.2.tgz", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="],
|
||||
|
||||
"fault": ["fault@1.0.4", "", { "dependencies": { "format": "^0.2.0" } }, "sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"file-entry-cache": ["file-entry-cache@8.0.0", "https://registry.npmmirror.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
|
||||
@@ -964,6 +961,8 @@
|
||||
|
||||
"for-each": ["for-each@0.3.5", "https://registry.npmmirror.com/for-each/-/for-each-0.3.5.tgz", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="],
|
||||
|
||||
"format": ["format@0.2.2", "", {}, "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"function-bind": ["function-bind@1.1.2", "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
||||
@@ -1012,20 +1011,8 @@
|
||||
|
||||
"hasown": ["hasown@2.0.3", "https://registry.npmmirror.com/hasown/-/hasown-2.0.3.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="],
|
||||
|
||||
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
|
||||
|
||||
"hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
|
||||
|
||||
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
|
||||
|
||||
"hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="],
|
||||
|
||||
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
|
||||
|
||||
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="],
|
||||
|
||||
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||
|
||||
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
|
||||
|
||||
"help-me": ["help-me@5.0.0", "https://registry.npmmirror.com/help-me/-/help-me-5.0.0.tgz", {}, "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg=="],
|
||||
@@ -1034,11 +1021,17 @@
|
||||
|
||||
"hermes-parser": ["hermes-parser@0.25.1", "https://registry.npmmirror.com/hermes-parser/-/hermes-parser-0.25.1.tgz", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
|
||||
|
||||
"highlight.js": ["highlight.js@10.7.3", "", {}, "sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A=="],
|
||||
|
||||
"highlightjs-vue": ["highlightjs-vue@1.0.0", "", {}, "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA=="],
|
||||
|
||||
"html-dom-parser": ["html-dom-parser@5.1.8", "", { "dependencies": { "domhandler": "5.0.3", "htmlparser2": "10.1.0" } }, "sha512-MCIUng//mF2qTtGHXJWr6OLfHWmg3Pm8ezpfiltF83tizPWY17JxT4dRLE8lykJ5bChJELoY3onQKPbufJHxYA=="],
|
||||
|
||||
"html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="],
|
||||
|
||||
"html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="],
|
||||
"html-react-parser": ["html-react-parser@5.2.17", "", { "dependencies": { "domhandler": "5.0.3", "html-dom-parser": "5.1.8", "react-property": "2.0.2", "style-to-js": "1.1.21" }, "peerDependencies": { "@types/react": "0.14 || 15 || 16 || 17 || 18 || 19", "react": "0.14 || 15 || 16 || 17 || 18 || 19" }, "optionalPeers": ["@types/react"] }, "sha512-m+K/7Moq1jodAB4VL0RXSOmtwLUYoAsikZhwd+hGQe5Vtw2dbWfpFd60poxojMU0Tsh9w59mN1QLEcoHz0Dx9w=="],
|
||||
|
||||
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||
"htmlparser2": ["htmlparser2@10.1.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],
|
||||
|
||||
"husky": ["husky@9.1.7", "https://registry.npmmirror.com/husky/-/husky-9.1.7.tgz", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
|
||||
|
||||
@@ -1206,112 +1199,26 @@
|
||||
|
||||
"lodash-es": ["lodash-es@4.18.1", "", {}, "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A=="],
|
||||
|
||||
"lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
|
||||
|
||||
"log-update": ["log-update@6.1.0", "https://registry.npmmirror.com/log-update/-/log-update-6.1.0.tgz", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="],
|
||||
|
||||
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
|
||||
"lowlight": ["lowlight@1.20.0", "", { "dependencies": { "fault": "^1.0.0", "highlight.js": "~10.7.0" } }, "sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw=="],
|
||||
|
||||
"lru-cache": ["lru-cache@11.3.6", "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.3.6.tgz", {}, "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A=="],
|
||||
|
||||
"lz-string": ["lz-string@1.5.0", "https://registry.npmmirror.com/lz-string/-/lz-string-1.5.0.tgz", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
|
||||
|
||||
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
|
||||
|
||||
"marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="],
|
||||
"marked": ["marked@15.0.12", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA=="],
|
||||
|
||||
"math-intrinsics": ["math-intrinsics@1.1.0", "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
|
||||
|
||||
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
|
||||
|
||||
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="],
|
||||
|
||||
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
|
||||
|
||||
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
|
||||
|
||||
"mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
|
||||
|
||||
"mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
|
||||
|
||||
"mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
|
||||
|
||||
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
|
||||
|
||||
"mdast-util-mdx-expression": ["mdast-util-mdx-expression@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ=="],
|
||||
|
||||
"mdast-util-mdx-jsx": ["mdast-util-mdx-jsx@3.2.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "parse-entities": "^4.0.0", "stringify-entities": "^4.0.0", "unist-util-stringify-position": "^4.0.0", "vfile-message": "^4.0.0" } }, "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q=="],
|
||||
|
||||
"mdast-util-mdxjs-esm": ["mdast-util-mdxjs-esm@2.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg=="],
|
||||
|
||||
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
|
||||
|
||||
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
|
||||
|
||||
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
|
||||
|
||||
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
|
||||
|
||||
"mdn-data": ["mdn-data@2.27.1", "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.27.1.tgz", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
|
||||
|
||||
"meow": ["meow@13.2.0", "https://registry.npmmirror.com/meow/-/meow-13.2.0.tgz", {}, "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA=="],
|
||||
|
||||
"mermaid": ["mermaid@11.15.0", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.2", "@mermaid-js/parser": "^1.1.1", "@types/d3": "^7.4.3", "@upsetjs/venn.js": "^2.0.0", "cytoscape": "^3.33.1", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.14", "dayjs": "^1.11.19", "dompurify": "^3.3.1", "es-toolkit": "^1.45.1", "katex": "^0.16.25", "khroma": "^2.1.0", "marked": "^16.3.0", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0 || ^12 || ^13 || ^14.0.0" } }, "sha512-pTMbcf3rWdtLiYGpmoTjHEpeY8seiy6sR+9nD7LOs8KfUbHE4lOUAprTRqRAcWSQ6MQpdX+YEsxShtGsINtPtw=="],
|
||||
|
||||
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
|
||||
|
||||
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
|
||||
|
||||
"micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
|
||||
|
||||
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
|
||||
|
||||
"micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
|
||||
|
||||
"micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
|
||||
|
||||
"micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
|
||||
|
||||
"micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
|
||||
|
||||
"micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
|
||||
|
||||
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
|
||||
|
||||
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
|
||||
|
||||
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
|
||||
|
||||
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
|
||||
|
||||
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
|
||||
|
||||
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
||||
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
|
||||
|
||||
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
|
||||
|
||||
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
|
||||
|
||||
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
|
||||
|
||||
"micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
|
||||
|
||||
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||
|
||||
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
|
||||
|
||||
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
|
||||
|
||||
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
|
||||
|
||||
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
||||
|
||||
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
|
||||
|
||||
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
||||
|
||||
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
|
||||
|
||||
"mimic-function": ["mimic-function@5.0.1", "https://registry.npmmirror.com/mimic-function/-/mimic-function-5.0.1.tgz", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="],
|
||||
|
||||
"minimatch": ["minimatch@10.2.5", "https://registry.npmmirror.com/minimatch/-/minimatch-10.2.5.tgz", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
|
||||
@@ -1354,6 +1261,10 @@
|
||||
|
||||
"optionator": ["optionator@0.9.4", "https://registry.npmmirror.com/optionator/-/optionator-0.9.4.tgz", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
|
||||
|
||||
"overlayscrollbars": ["overlayscrollbars@2.16.0", "", {}, "sha512-N03oje/q7j93D0aLZtoCdsDSYLmhheSsv8H7oSLE7HhdV9P/bmCURtLV/KbPye7P/bpfyt/obSfDpGUYoJ0OWg=="],
|
||||
|
||||
"overlayscrollbars-react": ["overlayscrollbars-react@0.5.6", "", { "peerDependencies": { "overlayscrollbars": "^2.0.0", "react": ">=16.8.0" } }, "sha512-E5To04bL5brn9GVCZ36SnfGanxa2I2MDkWoa4Cjo5wol7l+diAgi4DBc983V7l2nOk/OLJ6Feg4kySspQEGDBw=="],
|
||||
|
||||
"own-keys": ["own-keys@1.0.1", "https://registry.npmmirror.com/own-keys/-/own-keys-1.0.1.tgz", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
|
||||
|
||||
"p-limit": ["p-limit@3.1.0", "https://registry.npmmirror.com/p-limit/-/p-limit-3.1.0.tgz", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
|
||||
@@ -1408,6 +1319,8 @@
|
||||
|
||||
"pretty-format": ["pretty-format@27.5.1", "https://registry.npmmirror.com/pretty-format/-/pretty-format-27.5.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
|
||||
|
||||
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||
|
||||
"process-warning": ["process-warning@5.0.0", "https://registry.npmmirror.com/process-warning/-/process-warning-5.0.0.tgz", {}, "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA=="],
|
||||
|
||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
@@ -1424,10 +1337,14 @@
|
||||
|
||||
"react-is": ["react-is@18.3.1", "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
|
||||
|
||||
"react-property": ["react-property@2.0.2", "", {}, "sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug=="],
|
||||
|
||||
"react-redux": ["react-redux@9.2.0", "https://registry.npmmirror.com/react-redux/-/react-redux-9.2.0.tgz", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" }, "peerDependencies": { "@types/react": "^18.2.25 || ^19", "react": "^18.0 || ^19", "redux": "^5.0.0" }, "optionalPeers": ["@types/react", "redux"] }, "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g=="],
|
||||
|
||||
"react-router": ["react-router@7.15.1", "https://registry.npmmirror.com/react-router/-/react-router-7.15.1.tgz", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-R8rl9HhgikFYoPJymnUtPXWbnDb3oget6lQnfIoupbt61aT9aOhRkDsY2XRhZRyX1Z/8a5sL74fXmFNm3NRK5A=="],
|
||||
|
||||
"react-syntax-highlighter": ["react-syntax-highlighter@16.1.1", "", { "dependencies": { "@babel/runtime": "^7.28.4", "highlight.js": "^10.4.1", "highlightjs-vue": "^1.0.0", "lowlight": "^1.17.0", "prismjs": "^1.30.0", "refractor": "^5.0.0" }, "peerDependencies": { "react": ">= 0.14.0" } }, "sha512-PjVawBGy80C6YbC5DDZJeUjBmC7skaoEUdvfFQediQHgCL7aKyVHe57SaJGfQsloGDac+gCpTfRdtxzWWKmCXA=="],
|
||||
|
||||
"real-require": ["real-require@0.2.0", "https://registry.npmmirror.com/real-require/-/real-require-0.2.0.tgz", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="],
|
||||
|
||||
"recharts": ["recharts@3.8.1", "https://registry.npmmirror.com/recharts/-/recharts-3.8.1.tgz", { "dependencies": { "@reduxjs/toolkit": "^1.9.0 || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg=="],
|
||||
@@ -1438,24 +1355,10 @@
|
||||
|
||||
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "https://registry.npmmirror.com/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
|
||||
|
||||
"refractor": ["refractor@5.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/prismjs": "^1.0.0", "hastscript": "^9.0.0", "parse-entities": "^4.0.0" } }, "sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw=="],
|
||||
|
||||
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "https://registry.npmmirror.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
|
||||
|
||||
"rehype-harden": ["rehype-harden@1.1.8", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-Qn7vR1xrf6fZCrkm9TDWi/AB4ylrHy+jqsNm1EHOAmbARYA6gsnVJBq/sdBh6kmT4NEZxH5vgIjrscefJAOXcw=="],
|
||||
|
||||
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
|
||||
|
||||
"rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="],
|
||||
|
||||
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
|
||||
|
||||
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
|
||||
|
||||
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
|
||||
|
||||
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
|
||||
|
||||
"remend": ["remend@1.3.0", "", {}, "sha512-iIhggPkhW3hFImKtB10w0dz4EZbs28mV/dmbcYVonWEJ6UGHHpP+bFZnTh6GNWJONg5m+U56JrL+8IxZRdgWjw=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"reselect": ["reselect@5.1.1", "https://registry.npmmirror.com/reselect/-/reselect-5.1.1.tgz", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="],
|
||||
@@ -1538,8 +1441,6 @@
|
||||
|
||||
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
|
||||
|
||||
"streamdown": ["streamdown@2.5.0", "", { "dependencies": { "clsx": "^2.1.1", "hast-util-to-jsx-runtime": "^2.3.6", "html-url-attributes": "^3.0.1", "marked": "^17.0.1", "mermaid": "^11.12.2", "rehype-harden": "^1.1.8", "rehype-raw": "^7.0.0", "rehype-sanitize": "^6.0.0", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remend": "1.3.0", "tailwind-merge": "^3.4.0", "unified": "^11.0.5", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.0" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-/tTnURfIOxZK/pqJAxsfCvETG/XCJHoWnk3jq9xLcuz6CSpnjjuxSRBTTL4PKGhxiZQf0lqPxGhImdpwcZ2XwA=="],
|
||||
|
||||
"string-argv": ["string-argv@0.3.2", "https://registry.npmmirror.com/string-argv/-/string-argv-0.3.2.tgz", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="],
|
||||
|
||||
"string-convert": ["string-convert@0.2.1", "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz", {}, "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A=="],
|
||||
@@ -1552,8 +1453,6 @@
|
||||
|
||||
"string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "https://registry.npmmirror.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.2.0", "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.2.0.tgz", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"strip-bom": ["strip-bom@3.0.0", "https://registry.npmmirror.com/strip-bom/-/strip-bom-3.0.0.tgz", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
|
||||
@@ -1574,8 +1473,6 @@
|
||||
|
||||
"synckit": ["synckit@0.11.12", "https://registry.npmmirror.com/synckit/-/synckit-0.11.12.tgz", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ=="],
|
||||
|
||||
"tailwind-merge": ["tailwind-merge@3.6.0", "", {}, "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w=="],
|
||||
|
||||
"thread-stream": ["thread-stream@4.2.0", "https://registry.npmmirror.com/thread-stream/-/thread-stream-4.2.0.tgz", { "dependencies": { "real-require": "^1.0.0" } }, "sha512-e2zZ96wSChazBsbENf/Pcm/4swHt2cEKQ92rhUjkL9GCKiTDJIaTBenjE/m9DXi0QBmTMDkFDdOomUy20A1tDQ=="],
|
||||
|
||||
"throttle-debounce": ["throttle-debounce@5.0.2", "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", {}, "sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A=="],
|
||||
@@ -1596,10 +1493,6 @@
|
||||
|
||||
"tr46": ["tr46@6.0.0", "https://registry.npmmirror.com/tr46/-/tr46-6.0.0.tgz", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
|
||||
|
||||
"ts-api-utils": ["ts-api-utils@2.5.0", "https://registry.npmmirror.com/ts-api-utils/-/ts-api-utils-2.5.0.tgz", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="],
|
||||
|
||||
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
|
||||
@@ -1630,18 +1523,6 @@
|
||||
|
||||
"undici-types": ["undici-types@7.25.0", "https://registry.npmmirror.com/undici-types/-/undici-types-7.25.0.tgz", {}, "sha512-AXNgS1Byr27fTI+2bsPEkV9CxkT8H6xNyRI68b3TatlZo3RkzlqQBLL+w7SmGPVpokjHbcuNVQUWE7FRTg+LRA=="],
|
||||
|
||||
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||
|
||||
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||
|
||||
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||
|
||||
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||
|
||||
"unrs-resolver": ["unrs-resolver@1.11.1", "https://registry.npmmirror.com/unrs-resolver/-/unrs-resolver-1.11.1.tgz", { "dependencies": { "napi-postinstall": "^0.3.0" }, "optionalDependencies": { "@unrs/resolver-binding-android-arm-eabi": "1.11.1", "@unrs/resolver-binding-android-arm64": "1.11.1", "@unrs/resolver-binding-darwin-arm64": "1.11.1", "@unrs/resolver-binding-darwin-x64": "1.11.1", "@unrs/resolver-binding-freebsd-x64": "1.11.1", "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", "@unrs/resolver-binding-linux-x64-musl": "1.11.1", "@unrs/resolver-binding-wasm32-wasi": "1.11.1", "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
@@ -1652,20 +1533,12 @@
|
||||
|
||||
"uuid": ["uuid@14.0.0", "", { "bin": { "uuid": "dist-node/bin/uuid" } }, "sha512-Qo+uWgilfSmAhXCMav1uYFynlQO7fMFiMVZsQqZRMIXp0O7rR7qjkj+cPvBHLgBqi960QCoo/PH2/6ZtVqKvrg=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
|
||||
"vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"victory-vendor": ["victory-vendor@37.3.6", "https://registry.npmmirror.com/victory-vendor/-/victory-vendor-37.3.6.tgz", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
|
||||
|
||||
"vite": ["vite@8.0.14", "https://registry.npmmirror.com/vite/-/vite-8.0.14.tgz", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.15", "rolldown": "1.0.2", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.18", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw=="],
|
||||
|
||||
"w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="],
|
||||
|
||||
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
|
||||
|
||||
"webidl-conversions": ["webidl-conversions@8.0.1", "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-8.0.1.tgz", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="],
|
||||
|
||||
"whatwg-mimetype": ["whatwg-mimetype@5.0.0", "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="],
|
||||
@@ -1708,8 +1581,6 @@
|
||||
|
||||
"zod-validation-error": ["zod-validation-error@4.0.2", "https://registry.npmmirror.com/zod-validation-error/-/zod-validation-error-4.0.2.tgz", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"@babel/core/json5": ["json5@2.2.3", "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
|
||||
|
||||
"@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
|
||||
@@ -1772,6 +1643,8 @@
|
||||
|
||||
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
|
||||
|
||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||
|
||||
"eslint/ajv": ["ajv@6.15.0", "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw=="],
|
||||
|
||||
"eslint-import-resolver-node/debug": ["debug@3.2.7", "https://registry.npmmirror.com/debug/-/debug-3.2.7.tgz", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
|
||||
@@ -1782,7 +1655,7 @@
|
||||
|
||||
"eslint-plugin-import/minimatch": ["minimatch@3.1.5", "https://registry.npmmirror.com/minimatch/-/minimatch-3.1.5.tgz", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
|
||||
|
||||
"hast-util-raw/parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
|
||||
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
|
||||
"import-fresh/resolve-from": ["resolve-from@4.0.0", "https://registry.npmmirror.com/resolve-from/-/resolve-from-4.0.0.tgz", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
|
||||
|
||||
@@ -1792,8 +1665,6 @@
|
||||
|
||||
"log-update/wrap-ansi": ["wrap-ansi@9.0.2", "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-9.0.2.tgz", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
|
||||
|
||||
"mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
|
||||
|
||||
"mermaid/marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
|
||||
|
||||
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
|
||||
@@ -1886,8 +1757,6 @@
|
||||
|
||||
"eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "https://registry.npmmirror.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
||||
"hast-util-raw/parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
|
||||
|
||||
"log-update/slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
"log-update/wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "https://registry.npmmirror.com/ansi-styles/-/ansi-styles-6.2.3.tgz", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="],
|
||||
|
||||
@@ -42,6 +42,7 @@ AI 工具必须严格遵守以下全部约束。
|
||||
| `src/server/` | 后端,禁止 import src/web/ |
|
||||
| `src/server/db/` | 数据库层:schema、connection、migration、DAO |
|
||||
| `src/server/ai/` | AI Provider Registry + Agent + 工具 |
|
||||
| `src/server/config/` | 配置子系统:types、variables、issues、schema |
|
||||
| `src/server/helpers/` | 跨路由工具:响应格式化、URL 拼接 |
|
||||
| `src/server/middleware/` | 参数校验 + 错误处理中间件 |
|
||||
| `src/web/` | 前端,禁止 import src/server/ 运行时实现 |
|
||||
@@ -54,7 +55,8 @@ AI 工具必须严格遵守以下全部约束。
|
||||
### 类型与配置
|
||||
|
||||
- 共享类型唯一源头:`src/shared/api.ts`;应用常量唯一源头:`src/shared/app.ts`;版本号唯一源头:`package.json`。
|
||||
- 配置加载流程:unknown → AuthoringConfig → NormalizedConfig → ValidatedConfig → ServerConfig。
|
||||
- 配置加载流程:unknown → AuthoringConfig → NormalizedConfig → ValidatedConfig → ResolvedConfig。
|
||||
- 配置系统入口:`src/server/config.ts`(统一配置加载、运行时校验、默认值解析)。
|
||||
- Ajv 严格拒绝模式:不类型转换、不注入默认值、不删除未知字段。
|
||||
- 新增/修改配置字段必须同步更新 TypeBox schema、`config.schema.json`、测试和用户文档。
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
dev.ts / main.ts
|
||||
-> parseRuntimeArgs(cli args) — 必须指定 config.yaml
|
||||
-> bootstrap({ configPath, mode })
|
||||
-> loadServerConfig(configPath)
|
||||
-> createRuntimeLogger(config.logging)
|
||||
-> loadServerConfig(configPath) — 加载并校验配置(config.ts)
|
||||
-> createRuntimeLogger(config.logging) — 创建 Logger 实例
|
||||
-> mkdirSync(dataDir)
|
||||
-> 加载 migrations(生产:嵌入 bytes;开发:磁盘 drizzle/)
|
||||
-> createDatabase(dataDir)
|
||||
@@ -33,18 +33,20 @@ Request -> Bun.serve routes 声明式匹配 -> routes/*.ts handler -> helpers/
|
||||
|
||||
## 主要模块
|
||||
|
||||
| 模块 | 职责 |
|
||||
| ------------------------- | ------------------------------------------------ |
|
||||
| `src/server/bootstrap.ts` | 统一启动引导、DB 初始化、shutdown 编排 |
|
||||
| `src/server/server.ts` | Bun HTTP server + routes 注册 |
|
||||
| `src/server/routes/` | API handler,按资源端点拆分 |
|
||||
| `src/server/db/` | SQLite 连接、schema、migration、data access |
|
||||
| `src/server/ai/` | AI Provider Registry + Agent 流式调用 |
|
||||
| `src/server/config/` | 配置解析(types、variables、normalizer、schema) |
|
||||
| `src/server/helpers/` | 响应格式化、URL 工具 |
|
||||
| `src/server/middleware/` | 参数校验 + 错误处理 |
|
||||
| `src/shared/api.ts` | 前后端共享 API 类型 |
|
||||
| `src/shared/app.ts` | 应用全局常量 |
|
||||
| 模块 | 职责 |
|
||||
| ------------------------- | -------------------------------------------------------- |
|
||||
| `src/server/bootstrap.ts` | 统一启动引导、DB 初始化、shutdown 编排 |
|
||||
| `src/server/config.ts` | 配置加载入口:YAML 解析、规范化、契约校验、运行时校验 |
|
||||
| `src/server/config/` | 配置子系统:types、variables、issues、normalizer、schema |
|
||||
| `src/server/logger.ts` | Logger 接口 + Pino 实现 + 测试替身 |
|
||||
| `src/server/server.ts` | Bun HTTP server + routes 注册 |
|
||||
| `src/server/routes/` | API handler,按资源端点拆分 |
|
||||
| `src/server/db/` | SQLite 连接、schema、migration、data access |
|
||||
| `src/server/ai/` | AI Provider Registry + Agent + 工具 |
|
||||
| `src/server/helpers/` | 响应格式化、URL 工具 |
|
||||
| `src/server/middleware/` | 参数校验 + 错误处理 |
|
||||
| `src/shared/api.ts` | 前后端共享 API 类型 |
|
||||
| `src/shared/app.ts` | 应用全局常量 |
|
||||
|
||||
## 路由分组
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ SQLite + bun:sqlite + Drizzle ORM。
|
||||
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `projects.ts` | createProject、getProject、listProjects、updateProject、deleteProject、archiveProject、restoreProject |
|
||||
| `providers.ts` | createProvider、getProvider、listProviders、listProviderOptions、updateProvider、deleteProvider |
|
||||
| `models.ts` | createModel、getModel、listModels、getModelsByProviderId、updateModel、deleteModel |
|
||||
| `models.ts` | createModel、getModel、listModels、getModelWithProvider、getModelsByProviderId、updateModel、deleteModel |
|
||||
| `conversations.ts` | createConversation、getConversation、listConversations、updateConversation、updateConversationTimestamp、deleteConversation、createMessage、createMessages、listMessages |
|
||||
|
||||
输入输出类型来自 `src/shared/api.ts`。
|
||||
@@ -38,9 +38,8 @@ SQLite + bun:sqlite + Drizzle ORM。
|
||||
- `src/server/ai/types.ts`:`AIProviderConfig`(name、type、baseUrl、apiKey)、`AIModelConfig`(providerId、modelId、capabilities)。
|
||||
- `src/server/ai/registry.ts`:
|
||||
- `buildProviderRegistry(db)` — 从 DB 查询供应商构建 AI SDK Provider Registry,每次调用重建,不缓存。通过 `registry.languageModel('providerId:modelId')` 获取模型实例。
|
||||
- `testProviderConnection(config)` — 测试 Base URL 可达性 + `/models` 接口
|
||||
- `testModelConnection(config)` — 测试模型连通性
|
||||
- `countModels(db)` — 统计已配置模型数
|
||||
- `testProviderConnection(config, logger)` — 测试 Base URL 可达性 + `/models` 接口
|
||||
- `testModelConnection(config, logger)` — 测试模型连通性(需传入含 modelId 的合并配置)
|
||||
- `src/server/ai/agents/alfred-agent.ts`:`createAlfredAgent(model)` — ToolLoopAgent + `stepCountIs(20)` + `getCurrentTime` 工具。
|
||||
- `src/server/ai/tools/`:AI 工具定义。
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
- **Admin**(`src/web/consoles/admin/`):路由 `/`(总览)、`/projects`、`/models`。
|
||||
- **Workbench**(`src/web/consoles/workbench/`):路由 `/workbench/:projectId`、`/workbench/:projectId/chat`。`WorkbenchProjectGate` 从 URL 读 projectId,通过 `ProjectContext` 提供项目上下文,仅 active 项目渲染。
|
||||
|
||||
ConsoleShell 包含:`ConfigProvider(zhCN)` + `AntApp` + `Layout`(Header/Sider/Content) + 主题切换(明亮/黑暗/系统)+ 侧边栏折叠。Header 显示品牌名、版本号和控制台标题。
|
||||
ConsoleShell 包含:`XProvider(zhCN + zhCN_X)` + `AntApp` + `Layout`(Header/Sider/Content) + 主题切换(明亮/黑暗/系统)+ 侧边栏折叠。Header 显示品牌名、版本号和控制台标题。
|
||||
|
||||
`Sidebar`(`src/web/components/Sidebar/`)纯展示组件,通过 `menuItems` props 接收配置。
|
||||
|
||||
@@ -25,11 +25,11 @@ ConsoleShell 包含:`ConfigProvider(zhCN)` + `AntApp` + `Layout`(Header/Sider/
|
||||
|
||||
### 聊天页面
|
||||
|
||||
`ChatPage` = `ChatSidebar` + `ChatPanel`。
|
||||
`ChatPage` = `Conversations`(@ant-design/x)+ `ChatPanel`。
|
||||
|
||||
- **ChatSidebar**:TanStack Query 管理会话列表,创建/删除操作。
|
||||
- **ChatPanel**:`useChat`(@ai-sdk/react)+ `DefaultChatTransport`(ai 包)与后端 SSE 通信。按 `part.type` 分派渲染:TextPart(streamdown Markdown)、ReasoningPart、ToolPart(四态)。支持编辑重发、重新生成、复制。
|
||||
- **ChatInputArea**:`Input.TextArea` + `Button` + `Select`(模型切换)。
|
||||
- **Conversations**:会话侧边栏,TanStack Query 管理会话列表,支持创建/选中/删除(menu dropdown)。
|
||||
- **ChatPanel**:`useChat`(@ai-sdk/react)+ `DefaultChatTransport`(ai 包)与后端 SSE 通信。按 `part.type` 分派渲染:TextPart(XMarkdown)、ReasoningPart、ToolPart(四态)。支持编辑重发、重新生成、复制。
|
||||
- **Sender**(@ant-design/x):输入框 + 发送/停止按钮 + 模型 Select(footer slot)。
|
||||
|
||||
## Hooks
|
||||
|
||||
@@ -73,12 +73,12 @@ export interface Logger {
|
||||
|
||||
### 实现
|
||||
|
||||
| 实现 | 工厂函数 | 用途 |
|
||||
| ----------------------- | --------------------------------------- | ------------------------------------------------------- |
|
||||
| `DefaultLogger` + Sinks | `useLogger()` / `createDefaultLogger()` | 组件内使用,ConsoleSink + AntdMessageSink 双流 |
|
||||
| `ConsoleLogger` | `createConsoleLogger()` | 非组件纯函数(ErrorBoundary、工具函数),仅 ConsoleSink |
|
||||
| `NoopLogger` | `createNoopLogger()` | 测试中不需要日志的场景 |
|
||||
| `MemoryLogger` | `createMemoryLogger()` | 测试断言日志条目 |
|
||||
| 实现 | 工厂函数 | 用途 |
|
||||
| ----------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------- |
|
||||
| `DefaultLogger` + Sinks | `useLogger(bindings?)` / `createDefaultLogger()` | 组件内使用,ConsoleSink + AntdMessageSink 双流;传入 bindings 自动创建带作用域的子 Logger |
|
||||
| `ConsoleLogger` | `createConsoleLogger()` | 非组件纯函数(ErrorBoundary、工具函数),仅 ConsoleSink |
|
||||
| `NoopLogger` | `createNoopLogger()` | 测试中不需要日志的场景 |
|
||||
| `MemoryLogger` | `createMemoryLogger()` | 测试断言日志条目 |
|
||||
|
||||
### 使用方式
|
||||
|
||||
@@ -88,7 +88,7 @@ export interface Logger {
|
||||
import { useLogger } from "../hooks/use-logger";
|
||||
|
||||
function MyComponent() {
|
||||
const logger = useLogger();
|
||||
const logger = useLogger({ component: "MyComponent" });
|
||||
logger.info("数据加载完成", { count: 42 });
|
||||
logger.warn("即将超时");
|
||||
logger.error("操作失败", { error: new Error("...") });
|
||||
@@ -106,6 +106,15 @@ logger.debug("调试信息");
|
||||
|
||||
**作用域绑定:**
|
||||
|
||||
组件内直接通过 `useLogger` 的 `bindings` 参数传入,hook 内部保证引用稳定(值不变时多次渲染返回同一 Logger):
|
||||
|
||||
```typescript
|
||||
const logger = useLogger({ component: "ChatPanel", page: "workbench" });
|
||||
logger.info("页面加载"); // [Alfred:INFO] 页面加载 [component=ChatPanel][page=workbench]
|
||||
```
|
||||
|
||||
非组件场景仍可使用 `logger.child()`:
|
||||
|
||||
```typescript
|
||||
const pageLogger = logger.child({ page: "projects" });
|
||||
pageLogger.info("页面加载"); // [Alfred:INFO] 页面加载 [page=projects]
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
"@ai-sdk/openai-compatible": "^2.0.48",
|
||||
"@ai-sdk/react": "^3.0.195",
|
||||
"@ant-design/icons": "^6.2.3",
|
||||
"@ant-design/x": "^2.7.0",
|
||||
"@ant-design/x-markdown": "^2.7.0",
|
||||
"@sinclair/typebox": "^0.34.49",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"ai": "^6.0.193",
|
||||
@@ -65,6 +67,8 @@
|
||||
"antd": "^6.4.3",
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"es-toolkit": "^1.47.0",
|
||||
"overlayscrollbars": "^2.16.0",
|
||||
"overlayscrollbars-react": "^0.5.6",
|
||||
"pino": "^10.3.1",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"pino-roll": "^4.0.0",
|
||||
@@ -72,7 +76,6 @@
|
||||
"react-dom": "^19.2.6",
|
||||
"react-router": "^7.15.1",
|
||||
"recharts": "^3.8.1",
|
||||
"streamdown": "^2.5.0",
|
||||
"zod": "^4.4.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,20 @@
|
||||
"sourceType": "github",
|
||||
"skillPath": "skills/antd/SKILL.md",
|
||||
"computedHash": "5e26c8042060bb811118927b5daf637af7929a00fa973dd8f5f804f3ba6e2bf2"
|
||||
},
|
||||
"x-components": {
|
||||
"source": "ant-design/x",
|
||||
"ref": "main",
|
||||
"sourceType": "github",
|
||||
"skillPath": "packages/x-skill/skills/x-components/SKILL.md",
|
||||
"computedHash": "ebc195a3a5020b6d4f4533adf2e0af33253919f0c704947e727f877aba23a4c2"
|
||||
},
|
||||
"x-markdown": {
|
||||
"source": "ant-design/x",
|
||||
"ref": "main",
|
||||
"sourceType": "github",
|
||||
"skillPath": "packages/x-skill/skills/x-markdown/SKILL.md",
|
||||
"computedHash": "2d26b8eda1692929e99a8b6163ef8b206f1f096a4a84507b50dbe836a7ec041e"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ export function createGetCurrentTime(logger?: Logger) {
|
||||
inputSchema: z.object({
|
||||
timezone: z.string().optional().describe("IANA 时区名称,如 'Asia/Shanghai'、'America/New_York'"),
|
||||
}),
|
||||
metadata: { displayName: "获取当前时间" },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { MenuFoldOutlined, MenuUnfoldOutlined } from "@ant-design/icons";
|
||||
import { App as AntApp, ConfigProvider, Layout, Segmented, theme } from "antd";
|
||||
import { XProvider } from "@ant-design/x";
|
||||
import zhCN_X from "@ant-design/x/locale/zh_CN";
|
||||
import { App as AntApp, Layout, Segmented, theme } from "antd";
|
||||
import zhCN from "antd/locale/zh_CN";
|
||||
|
||||
import type { ConsoleShellProps } from "./types";
|
||||
@@ -28,7 +30,7 @@ export function ConsoleShell({ headerExtra, menuItems, title }: ConsoleShellProp
|
||||
const themeAlgorithm = effectiveTheme === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm;
|
||||
|
||||
return (
|
||||
<ConfigProvider locale={zhCN} theme={{ algorithm: themeAlgorithm }}>
|
||||
<XProvider locale={{ ...zhCN, ...zhCN_X }} theme={{ algorithm: themeAlgorithm }}>
|
||||
<AntApp>
|
||||
<Layout className="app-layout">
|
||||
<Header className="app-header">
|
||||
@@ -68,6 +70,6 @@ export function ConsoleShell({ headerExtra, menuItems, title }: ConsoleShellProp
|
||||
</Layout>
|
||||
</Layout>
|
||||
</AntApp>
|
||||
</ConfigProvider>
|
||||
</XProvider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
import { Button, Flex, Input, Select } from "antd";
|
||||
|
||||
interface ChatInputAreaProps {
|
||||
displayModelId: null | string;
|
||||
input: string;
|
||||
isLoading: boolean;
|
||||
modelOptions: Array<{ label: string; value: string }>;
|
||||
onInputChange: (value: string) => void;
|
||||
onModelChange: (value: string) => void;
|
||||
onSend: () => void;
|
||||
onStop: () => void;
|
||||
}
|
||||
|
||||
export function ChatInputArea({
|
||||
displayModelId,
|
||||
input,
|
||||
isLoading,
|
||||
modelOptions,
|
||||
onInputChange,
|
||||
onModelChange,
|
||||
onSend,
|
||||
onStop,
|
||||
}: ChatInputAreaProps) {
|
||||
return (
|
||||
<div className="chat-input-area">
|
||||
<Input.TextArea
|
||||
autoSize={{ maxRows: 6, minRows: 1 }}
|
||||
className="chat-textarea"
|
||||
disabled={isLoading}
|
||||
onChange={(e) => onInputChange(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter" && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
void onSend();
|
||||
}
|
||||
}}
|
||||
placeholder="输入消息..."
|
||||
value={input}
|
||||
/>
|
||||
<Flex align="center" gap={8} justify="space-between">
|
||||
<Select
|
||||
className="chat-model-select"
|
||||
disabled={isLoading}
|
||||
onChange={onModelChange}
|
||||
options={modelOptions}
|
||||
placeholder="选择模型"
|
||||
value={displayModelId}
|
||||
/>
|
||||
<Flex align="center" gap={8}>
|
||||
{isLoading ? (
|
||||
<Button
|
||||
danger
|
||||
onClick={() => {
|
||||
onStop?.();
|
||||
}}
|
||||
>
|
||||
停止
|
||||
</Button>
|
||||
) : (
|
||||
<Button onClick={() => void onSend()} type="primary">
|
||||
发送
|
||||
</Button>
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useChat } from "@ai-sdk/react";
|
||||
import { CopyOutlined, EditOutlined, RedoOutlined, RobotOutlined } from "@ant-design/icons";
|
||||
import { Sender } from "@ant-design/x";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { DefaultChatTransport, type UIMessage } from "ai";
|
||||
import { App, Button, Card, Flex, Input, Spin, Typography } from "antd";
|
||||
import { App, Button, Card, Divider, Flex, Input, Select, Spin, Typography } from "antd";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
|
||||
import {
|
||||
@@ -13,7 +14,7 @@ import {
|
||||
} from "../../../../hooks/use-conversations";
|
||||
import { useLogger } from "../../../../hooks/use-logger";
|
||||
import { useModelList } from "../../../../hooks/use-models";
|
||||
import { ChatInputArea } from "./ChatInputArea";
|
||||
import { ChatScrollArea } from "./ChatScrollArea";
|
||||
import { ReasoningPart } from "./parts/ReasoningPart";
|
||||
import { TextPart } from "./parts/TextPart";
|
||||
import { ToolPart } from "./parts/ToolPart";
|
||||
@@ -26,7 +27,7 @@ interface ChatPanelProps {
|
||||
|
||||
export function ChatPanel({ conversationId, onConversationCreated, projectId }: ChatPanelProps) {
|
||||
const { message } = App.useApp();
|
||||
const logger = useLogger().child({ component: "ChatPanel", page: "workbench" });
|
||||
const logger = useLogger({ component: "ChatPanel", page: "workbench" });
|
||||
const queryClient = useQueryClient();
|
||||
const [input, setInput] = useState("");
|
||||
const [editingMessageId, setEditingMessageId] = useState<null | string>(null);
|
||||
@@ -34,7 +35,6 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
||||
const [loadingHistory, setLoadingHistory] = useState(false);
|
||||
const [selectedModelId, setSelectedModelId] = useState<null | string>(null);
|
||||
const fetchRef = useRef(fetchMessages);
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const skipHistoryLoadRef = useRef<null | string>(null);
|
||||
|
||||
const { data: modelsData } = useModelList({ pageSize: 200 });
|
||||
@@ -125,10 +125,6 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
||||
});
|
||||
}, [conversationId, textModels, projectId, logger]);
|
||||
|
||||
useEffect(() => {
|
||||
scrollRef.current?.scrollTo({ behavior: "smooth", top: scrollRef.current.scrollHeight });
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status === "ready" && conversationId) {
|
||||
void queryClient.invalidateQueries({ queryKey: ["conversations", projectId] });
|
||||
@@ -150,42 +146,37 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
||||
[projectId, conversationId, logger],
|
||||
);
|
||||
|
||||
const handleSend = useCallback(async () => {
|
||||
if (!input.trim()) return;
|
||||
const text = input;
|
||||
setInput("");
|
||||
const handleSenderSubmit = useCallback(
|
||||
(msg: string) => {
|
||||
if (!msg.trim()) return;
|
||||
|
||||
if (!conversationId) {
|
||||
try {
|
||||
const conv = await createConversation(projectId, displayModelId ?? undefined);
|
||||
skipHistoryLoadRef.current = conv.id;
|
||||
void queryClient.invalidateQueries({ queryKey: ["conversations", projectId] });
|
||||
void sendMessage({ text }, { body: { conversationId: conv.id } });
|
||||
onConversationCreated(conv.id);
|
||||
} catch (err: unknown) {
|
||||
setInput(text);
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.error("创建会话失败", { error: msg, projectId });
|
||||
void message.error(`创建会话失败:${msg}`);
|
||||
if (!conversationId) {
|
||||
void (async () => {
|
||||
try {
|
||||
const conv = await createConversation(projectId, displayModelId ?? undefined);
|
||||
skipHistoryLoadRef.current = conv.id;
|
||||
void queryClient.invalidateQueries({ queryKey: ["conversations", projectId] });
|
||||
void sendMessage({ text: msg }, { body: { conversationId: conv.id } });
|
||||
setInput("");
|
||||
onConversationCreated(conv.id);
|
||||
} catch (err: unknown) {
|
||||
setInput(msg);
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
logger.error("创建会话失败", { error: errMsg, projectId });
|
||||
void message.error(`创建会话失败:${errMsg}`);
|
||||
}
|
||||
})();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void sendMessage({ text }, { body: { conversationId } }).catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.error("发送消息失败", { conversationId, error: msg, projectId });
|
||||
});
|
||||
}, [
|
||||
input,
|
||||
sendMessage,
|
||||
conversationId,
|
||||
projectId,
|
||||
onConversationCreated,
|
||||
message,
|
||||
queryClient,
|
||||
displayModelId,
|
||||
logger,
|
||||
]);
|
||||
setInput("");
|
||||
void sendMessage({ text: msg }, { body: { conversationId } }).catch((err: unknown) => {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
logger.error("发送消息失败", { conversationId, error: errMsg, projectId });
|
||||
});
|
||||
},
|
||||
[sendMessage, conversationId, projectId, onConversationCreated, message, queryClient, displayModelId, logger],
|
||||
);
|
||||
|
||||
const extractText = useCallback((msg: UIMessage) => {
|
||||
return msg.parts
|
||||
@@ -309,23 +300,40 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
||||
<Typography.Text type="secondary">有什么我可以帮助你的吗?</Typography.Text>
|
||||
</Flex>
|
||||
</div>
|
||||
<ChatInputArea
|
||||
displayModelId={displayModelId}
|
||||
input={input}
|
||||
isLoading={isLoading}
|
||||
modelOptions={modelOptions}
|
||||
onInputChange={setInput}
|
||||
onModelChange={handleModelChange}
|
||||
onSend={() => {
|
||||
void handleSend();
|
||||
}}
|
||||
onStop={() => {
|
||||
void stop().catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.warn("停止聊天失败", { error: msg });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="chat-sender-area">
|
||||
<Sender
|
||||
autoSize={{ maxRows: 3, minRows: 1 }}
|
||||
classNames={{ root: "chat-sender-box" }}
|
||||
footer={(actionNode) => (
|
||||
<Flex align="center" justify="space-between">
|
||||
<Select
|
||||
className="chat-model-select"
|
||||
disabled={isLoading}
|
||||
onChange={handleModelChange}
|
||||
options={modelOptions}
|
||||
placeholder="选择模型"
|
||||
value={displayModelId}
|
||||
/>
|
||||
<Flex align="center">
|
||||
<Divider orientation="vertical" />
|
||||
{actionNode}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
loading={isLoading}
|
||||
onCancel={() => {
|
||||
void stop().catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.warn("停止聊天失败", { error: msg });
|
||||
});
|
||||
}}
|
||||
onChange={setInput}
|
||||
onSubmit={handleSenderSubmit}
|
||||
placeholder="输入消息..."
|
||||
suffix={false}
|
||||
value={input}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -337,7 +345,7 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
||||
<Spin />
|
||||
</div>
|
||||
) : (
|
||||
<div className="chat-scroll-area" ref={scrollRef}>
|
||||
<ChatScrollArea messages={messages} status={status}>
|
||||
<Flex gap={8} vertical>
|
||||
{messages.map((msg, idx) => (
|
||||
<Card
|
||||
@@ -366,7 +374,12 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
||||
</Flex>
|
||||
) : (
|
||||
msg.parts.map((part: Record<string, unknown>, i: number) => (
|
||||
<PartRenderer key={i} part={part} role={msg.role} />
|
||||
<PartRenderer
|
||||
isStreaming={isLoading && idx === messages.length - 1 && msg.role === "assistant"}
|
||||
key={i}
|
||||
part={part}
|
||||
role={msg.role}
|
||||
/>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
@@ -378,34 +391,59 @@ export function ChatPanel({ conversationId, onConversationCreated, projectId }:
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</div>
|
||||
</ChatScrollArea>
|
||||
)}
|
||||
<ChatInputArea
|
||||
displayModelId={displayModelId}
|
||||
input={input}
|
||||
isLoading={isLoading}
|
||||
modelOptions={modelOptions}
|
||||
onInputChange={setInput}
|
||||
onModelChange={handleModelChange}
|
||||
onSend={() => {
|
||||
void handleSend();
|
||||
}}
|
||||
onStop={() => {
|
||||
void stop().catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.warn("停止聊天失败", { error: msg });
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<div className="chat-sender-area">
|
||||
<Sender
|
||||
autoSize={{ maxRows: 3, minRows: 1 }}
|
||||
classNames={{ root: "chat-sender-box" }}
|
||||
footer={(actionNode) => (
|
||||
<Flex align="center" justify="space-between">
|
||||
<Select
|
||||
className="chat-model-select"
|
||||
disabled={isLoading}
|
||||
onChange={handleModelChange}
|
||||
options={modelOptions}
|
||||
placeholder="选择模型"
|
||||
value={displayModelId}
|
||||
/>
|
||||
<Flex align="center">
|
||||
<Divider orientation="vertical" />
|
||||
{actionNode}
|
||||
</Flex>
|
||||
</Flex>
|
||||
)}
|
||||
loading={isLoading}
|
||||
onCancel={() => {
|
||||
void stop().catch((err: unknown) => {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
logger.warn("停止聊天失败", { error: msg });
|
||||
});
|
||||
}}
|
||||
onChange={setInput}
|
||||
onSubmit={handleSenderSubmit}
|
||||
placeholder="输入消息..."
|
||||
suffix={false}
|
||||
value={input}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PartRenderer({ part, role }: { part: Record<string, unknown>; role: string }) {
|
||||
function PartRenderer({
|
||||
isStreaming,
|
||||
part,
|
||||
role,
|
||||
}: {
|
||||
isStreaming: boolean;
|
||||
part: Record<string, unknown>;
|
||||
role: string;
|
||||
}) {
|
||||
const partType = typeof part["type"] === "string" ? part["type"] : "";
|
||||
|
||||
if (partType === "text") {
|
||||
return <TextPart part={part} role={role} />;
|
||||
return <TextPart isStreaming={isStreaming} part={part} role={role} />;
|
||||
}
|
||||
if (partType.startsWith("tool-")) {
|
||||
return <ToolPart part={part} />;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import type { UIMessage } from "ai";
|
||||
|
||||
import { ArrowDownOutlined } from "@ant-design/icons";
|
||||
import { Button } from "antd";
|
||||
import "overlayscrollbars/styles/overlayscrollbars.css";
|
||||
import { OverlayScrollbarsComponent, type OverlayScrollbarsComponentRef } from "overlayscrollbars-react";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { useChatScroll } from "./use-chat-scroll";
|
||||
|
||||
interface ChatScrollAreaProps {
|
||||
children: React.ReactNode;
|
||||
messages: UIMessage[];
|
||||
status: string;
|
||||
}
|
||||
|
||||
export function ChatScrollArea({ children, messages, status }: ChatScrollAreaProps) {
|
||||
const scrollRef = useRef<HTMLDivElement>(null);
|
||||
const osRef = useRef<OverlayScrollbarsComponentRef>(null);
|
||||
const [viewportElement, setViewportElement] = useState<HTMLDivElement | null>(null);
|
||||
|
||||
const handleOsInitialized = useCallback(() => {
|
||||
const os = osRef.current;
|
||||
if (!os) return;
|
||||
const instance = os.osInstance();
|
||||
if (!instance) return;
|
||||
const viewport = instance.elements().viewport as HTMLDivElement;
|
||||
scrollRef.current = viewport;
|
||||
setViewportElement(viewport);
|
||||
}, []);
|
||||
|
||||
const { isAtBottom, scrollToBottom } = useChatScroll({ messages, scrollRef, status, viewportElement });
|
||||
|
||||
return (
|
||||
<>
|
||||
<OverlayScrollbarsComponent
|
||||
className="chat-scroll-area"
|
||||
events={{ initialized: handleOsInitialized }}
|
||||
options={{
|
||||
overflow: { x: "hidden", y: "scroll" },
|
||||
scrollbars: { autoHide: "move", theme: "os-theme-custom" },
|
||||
}}
|
||||
ref={osRef}
|
||||
>
|
||||
{children}
|
||||
</OverlayScrollbarsComponent>
|
||||
{!isAtBottom && (
|
||||
<Button
|
||||
className="chat-scroll-bottom-btn"
|
||||
icon={<ArrowDownOutlined />}
|
||||
onClick={scrollToBottom}
|
||||
shape="circle"
|
||||
size="small"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { DeleteOutlined, PlusOutlined } from "@ant-design/icons";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { App, Button, Flex, Popconfirm, Spin, Typography } from "antd";
|
||||
|
||||
import type { Conversation } from "../../../../../shared/api";
|
||||
|
||||
import { createConversation, deleteConversation, fetchConversations } from "../../../../hooks/use-conversations";
|
||||
import { useModelList } from "../../../../hooks/use-models";
|
||||
|
||||
interface ChatSidebarProps {
|
||||
activeId: null | string;
|
||||
onSelect: (id: null | string) => void;
|
||||
projectId: string;
|
||||
}
|
||||
|
||||
export function ChatSidebar({ activeId, onSelect, projectId }: ChatSidebarProps) {
|
||||
const queryClient = useQueryClient();
|
||||
const { message } = App.useApp();
|
||||
|
||||
const CONVERSATIONS_KEY = ["conversations", projectId] as const;
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryFn: () => fetchConversations(projectId),
|
||||
queryKey: CONVERSATIONS_KEY,
|
||||
});
|
||||
|
||||
const { data: modelsData } = useModelList({ pageSize: 200 });
|
||||
const textModels = (modelsData?.items ?? []).filter((m) => m.capabilities.includes("text"));
|
||||
const defaultModelId = textModels[0]?.id;
|
||||
|
||||
const createMutation = useMutation({
|
||||
mutationFn: () => createConversation(projectId, defaultModelId),
|
||||
onError: (err: Error) => {
|
||||
void message.error(`创建会话失败:${err.message}`);
|
||||
},
|
||||
onSuccess: (conversation: Conversation) => {
|
||||
void queryClient.invalidateQueries({ queryKey: CONVERSATIONS_KEY });
|
||||
onSelect(conversation.id);
|
||||
},
|
||||
});
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: string) => deleteConversation(projectId, id),
|
||||
onError: (err: Error) => {
|
||||
void message.error(`删除会话失败:${err.message}`);
|
||||
},
|
||||
onSuccess: (_data: void, id: string) => {
|
||||
void queryClient.invalidateQueries({ queryKey: CONVERSATIONS_KEY });
|
||||
if (activeId === id) onSelect(null);
|
||||
},
|
||||
});
|
||||
|
||||
const conversations = data?.items ?? [];
|
||||
|
||||
return (
|
||||
<div className="app-chat-sidebar">
|
||||
<div className="app-chat-sidebar-header">
|
||||
<Button
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
loading={createMutation.isPending}
|
||||
onClick={() => createMutation.mutate()}
|
||||
type="primary"
|
||||
>
|
||||
新会话
|
||||
</Button>
|
||||
</div>
|
||||
<div className="app-chat-sidebar-list">
|
||||
{isLoading ? (
|
||||
<div className="app-chat-sidebar-loading">
|
||||
<Spin />
|
||||
</div>
|
||||
) : (
|
||||
conversations.map((item: Conversation) => (
|
||||
<Flex
|
||||
align="center"
|
||||
className={`app-chat-sidebar-item ${activeId === item.id ? "app-chat-sidebar-item-active" : ""}`}
|
||||
gap="small"
|
||||
justify="space-between"
|
||||
key={item.id}
|
||||
onClick={() => onSelect(item.id)}
|
||||
>
|
||||
<Typography.Text className="app-chat-sidebar-item-title" ellipsis>
|
||||
{item.title}
|
||||
</Typography.Text>
|
||||
<Popconfirm onConfirm={() => deleteMutation.mutate(item.id)} title="确定删除此会话?">
|
||||
<Button
|
||||
className="app-chat-sidebar-item-action"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
size="small"
|
||||
type="text"
|
||||
/>
|
||||
</Popconfirm>
|
||||
</Flex>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { CopyOutlined } from "@ant-design/icons";
|
||||
import { CodeHighlighter } from "@ant-design/x";
|
||||
import { App, Button, Flex, Typography } from "antd";
|
||||
import React from "react";
|
||||
|
||||
interface CodeBlockWithCopyProps {
|
||||
block?: boolean;
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
lang?: string;
|
||||
streamStatus?: "done" | "loading";
|
||||
}
|
||||
|
||||
export function CodeBlockWithCopy({ block, children, className, lang }: CodeBlockWithCopyProps) {
|
||||
const { message } = App.useApp();
|
||||
|
||||
if (!block) {
|
||||
return <code className={className}>{children}</code>;
|
||||
}
|
||||
|
||||
const codeText = extractText(children);
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
const displayLang = lang || "plaintext";
|
||||
|
||||
const handleCopy = () => {
|
||||
void navigator.clipboard.writeText(codeText).then(() => {
|
||||
void message.success("已复制");
|
||||
});
|
||||
};
|
||||
|
||||
const header = (
|
||||
<Flex align="center" justify="space-between" style={{ padding: "0 4px" }}>
|
||||
<Typography.Text style={{ color: "var(--ant-color-text-quaternary)", fontSize: 12 }}>
|
||||
{displayLang}
|
||||
</Typography.Text>
|
||||
<Button
|
||||
icon={<CopyOutlined />}
|
||||
onClick={handleCopy}
|
||||
size="small"
|
||||
style={{ color: "var(--ant-color-text-quaternary)" }}
|
||||
type="text"
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
return (
|
||||
<CodeHighlighter header={header} lang={displayLang}>
|
||||
{codeText}
|
||||
</CodeHighlighter>
|
||||
);
|
||||
}
|
||||
|
||||
function extractText(children: React.ReactNode): string {
|
||||
return React.Children.toArray(children)
|
||||
.map((child) => (typeof child === "string" ? child : ""))
|
||||
.join("");
|
||||
}
|
||||
@@ -1,20 +1,40 @@
|
||||
import { CheckCircleFilled, LoadingOutlined } from "@ant-design/icons";
|
||||
import { Collapse, Flex, Typography } from "antd";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
|
||||
import type { PartProps } from "./types";
|
||||
|
||||
const REASONING_KEY = "reasoning";
|
||||
|
||||
export function ReasoningPart({ part }: PartProps) {
|
||||
const text = typeof part["text"] === "string" ? part["text"] : "";
|
||||
const state = typeof part["state"] === "string" ? part["state"] : "";
|
||||
const isStreaming = state === "streaming";
|
||||
|
||||
const [userToggled, setUserToggled] = useState(false);
|
||||
const [userActiveKey, setUserActiveKey] = useState<string[]>([]);
|
||||
|
||||
const autoActiveKey = useMemo(() => {
|
||||
if (isStreaming) return [REASONING_KEY];
|
||||
if (state === "complete" || text.length > 0) return [];
|
||||
return [];
|
||||
}, [isStreaming, state, text]);
|
||||
|
||||
const activeKey = userToggled ? userActiveKey : autoActiveKey;
|
||||
|
||||
const handleChange = useCallback((keys: string | string[]) => {
|
||||
setUserToggled(true);
|
||||
setUserActiveKey(keys as string[]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
activeKey={activeKey}
|
||||
ghost
|
||||
items={[
|
||||
{
|
||||
children: <Typography.Text type="secondary">{text}</Typography.Text>,
|
||||
key: "reasoning",
|
||||
key: REASONING_KEY,
|
||||
label: isStreaming ? (
|
||||
<Flex align="center" component="span" gap={4}>
|
||||
<LoadingOutlined className="icon-primary" />
|
||||
@@ -28,6 +48,7 @@ export function ReasoningPart({ part }: PartProps) {
|
||||
),
|
||||
},
|
||||
]}
|
||||
onChange={handleChange}
|
||||
size="small"
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
import { XMarkdown } from "@ant-design/x-markdown";
|
||||
import "@ant-design/x-markdown/themes/light.css";
|
||||
import { Typography } from "antd";
|
||||
import { Streamdown } from "streamdown";
|
||||
|
||||
import type { PartProps } from "./types";
|
||||
|
||||
import { CodeBlockWithCopy } from "./CodeBlockWithCopy";
|
||||
|
||||
interface TextPartProps extends PartProps {
|
||||
isStreaming: boolean;
|
||||
role: string;
|
||||
}
|
||||
|
||||
export function TextPart({ part, role }: TextPartProps) {
|
||||
const xmarkdownComponents = {
|
||||
code: CodeBlockWithCopy,
|
||||
pre: ({ children }: { children?: React.ReactNode }) => <>{children}</>,
|
||||
};
|
||||
|
||||
export function TextPart({ isStreaming, part, role }: TextPartProps) {
|
||||
const text = typeof part["text"] === "string" ? part["text"] : "";
|
||||
|
||||
return (
|
||||
@@ -15,7 +24,12 @@ export function TextPart({ part, role }: TextPartProps) {
|
||||
{role === "user" ? (
|
||||
<Typography.Paragraph className="message-body-text">{text}</Typography.Paragraph>
|
||||
) : (
|
||||
<Streamdown parseIncompleteMarkdown>{text}</Streamdown>
|
||||
<XMarkdown
|
||||
className="x-markdown-light"
|
||||
components={xmarkdownComponents}
|
||||
content={text}
|
||||
streaming={{ hasNextChunk: isStreaming }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ interface ToolPartData {
|
||||
input?: unknown;
|
||||
output?: unknown;
|
||||
toolCallId?: string;
|
||||
toolMetadata?: Record<string, unknown>;
|
||||
toolName?: string;
|
||||
type?: string;
|
||||
}
|
||||
@@ -24,7 +25,9 @@ const FORMAT_JSON = (v: unknown) => JSON.stringify(v, null, 2);
|
||||
export function ToolPart({ part }: PartProps) {
|
||||
const toolPart = part as unknown as ToolPartData;
|
||||
const state = getToolState(toolPart);
|
||||
const toolName = toolPart.toolName ?? (toolPart.type ?? "unknown").replace(/^tool-/, "");
|
||||
const rawToolName = toolPart.toolName ?? (toolPart.type ?? "unknown").replace(/^tool-/, "");
|
||||
const toolName =
|
||||
typeof toolPart.toolMetadata?.["displayName"] === "string" ? toolPart.toolMetadata["displayName"] : rawToolName;
|
||||
|
||||
const isStreaming = state === "input-streaming" || state === "input-available";
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import type { UIMessage } from "ai";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
const NEAR_BOTTOM_THRESHOLD = 80;
|
||||
|
||||
interface UseChatScrollOptions {
|
||||
loadingHistory?: boolean;
|
||||
messages: UIMessage[];
|
||||
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
status: string;
|
||||
viewportElement: HTMLDivElement | null;
|
||||
}
|
||||
|
||||
export function useChatScroll({ loadingHistory, messages, scrollRef, status, viewportElement }: UseChatScrollOptions) {
|
||||
const [isAtBottom, setIsAtBottom] = useState(true);
|
||||
const isStreaming = status === "streaming";
|
||||
const prevLoadingRef = useRef(loadingHistory ?? false);
|
||||
|
||||
const checkNearBottom = useCallback(() => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return true;
|
||||
return el.scrollHeight - el.scrollTop - el.clientHeight < NEAR_BOTTOM_THRESHOLD;
|
||||
}, [scrollRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = viewportElement;
|
||||
if (!el) return;
|
||||
|
||||
const handleScroll = () => {
|
||||
setIsAtBottom(checkNearBottom());
|
||||
};
|
||||
|
||||
el.addEventListener("scroll", handleScroll, { passive: true });
|
||||
return () => el.removeEventListener("scroll", handleScroll);
|
||||
}, [viewportElement, checkNearBottom]);
|
||||
|
||||
useEffect(() => {
|
||||
const wasLoading = prevLoadingRef.current;
|
||||
prevLoadingRef.current = loadingHistory ?? false;
|
||||
|
||||
if (wasLoading && !loadingHistory) {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
requestAnimationFrame(() => {
|
||||
const target = scrollRef.current;
|
||||
if (!target) return;
|
||||
target.scrollTo({ behavior: "instant", top: target.scrollHeight });
|
||||
setIsAtBottom(true);
|
||||
});
|
||||
return;
|
||||
}
|
||||
}, [loadingHistory, scrollRef]);
|
||||
|
||||
useEffect(() => {
|
||||
const el = scrollRef.current;
|
||||
if (!el || !isAtBottom) return;
|
||||
|
||||
el.scrollTo({ behavior: "instant", top: el.scrollHeight });
|
||||
}, [messages, isStreaming, isAtBottom, scrollRef]);
|
||||
|
||||
const scrollToBottom = useCallback(() => {
|
||||
const el = scrollRef.current;
|
||||
if (!el) return;
|
||||
el.scrollTo({ behavior: "instant", top: el.scrollHeight });
|
||||
setIsAtBottom(true);
|
||||
}, [scrollRef]);
|
||||
|
||||
return { isAtBottom, scrollToBottom };
|
||||
}
|
||||
@@ -1,22 +1,93 @@
|
||||
import { Flex } from "antd";
|
||||
import { DeleteOutlined, MoreOutlined } from "@ant-design/icons";
|
||||
import { Conversations } from "@ant-design/x";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { App, Spin } from "antd";
|
||||
import { useState } from "react";
|
||||
|
||||
import type { Conversation } from "../../../../shared/api";
|
||||
|
||||
import { createConversation, deleteConversation, fetchConversations } from "../../../hooks/use-conversations";
|
||||
import { useModelList } from "../../../hooks/use-models";
|
||||
import { ChatPanel } from "../components/chat/ChatPanel";
|
||||
import { ChatSidebar } from "../components/chat/ChatSidebar";
|
||||
import { useCurrentProject } from "../useCurrentProject";
|
||||
|
||||
export function ChatPage() {
|
||||
const project = useCurrentProject();
|
||||
const [activeConversationId, setActiveConversationId] = useState<null | string>(null);
|
||||
const queryClient = useQueryClient();
|
||||
const { message } = App.useApp();
|
||||
|
||||
const CONVERSATIONS_KEY = ["conversations", project.id] as const;
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryFn: () => fetchConversations(project.id),
|
||||
queryKey: CONVERSATIONS_KEY,
|
||||
});
|
||||
|
||||
const { data: modelsData } = useModelList({ pageSize: 200 });
|
||||
const textModels = (modelsData?.items ?? []).filter((m) => m.capabilities.includes("text"));
|
||||
const defaultModelId = textModels[0]?.id;
|
||||
|
||||
const deleteMutation = useMutation({
|
||||
mutationFn: (id: string) => deleteConversation(project.id, id),
|
||||
onError: (err: Error) => {
|
||||
void message.error(`删除会话失败:${err.message}`);
|
||||
},
|
||||
onSuccess: (_data: void, id: string) => {
|
||||
void queryClient.invalidateQueries({ queryKey: CONVERSATIONS_KEY });
|
||||
if (activeConversationId === id) setActiveConversationId(null);
|
||||
},
|
||||
});
|
||||
|
||||
const conversations = (data?.items ?? []).map((c: Conversation) => ({
|
||||
key: c.id,
|
||||
label: c.title,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Flex className="app-chat-page" gap={0} vertical={false}>
|
||||
<ChatSidebar activeId={activeConversationId} onSelect={setActiveConversationId} projectId={project.id} />
|
||||
<div className="app-chat-page">
|
||||
<div className="app-chat-conversations">
|
||||
{isLoading ? (
|
||||
<Spin />
|
||||
) : (
|
||||
<Conversations
|
||||
activeKey={activeConversationId ?? ""}
|
||||
creation={{
|
||||
onClick: () => {
|
||||
void createConversation(project.id, defaultModelId)
|
||||
.then((conv) => {
|
||||
void queryClient.invalidateQueries({ queryKey: CONVERSATIONS_KEY });
|
||||
setActiveConversationId(conv.id);
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
void message.error(`创建会话失败:${err.message}`);
|
||||
});
|
||||
},
|
||||
}}
|
||||
items={conversations}
|
||||
menu={(conv) => ({
|
||||
items: [
|
||||
{
|
||||
danger: true,
|
||||
icon: <DeleteOutlined />,
|
||||
key: "delete",
|
||||
label: "删除",
|
||||
onClick: () => {
|
||||
deleteMutation.mutate(conv.key);
|
||||
},
|
||||
},
|
||||
],
|
||||
trigger: <MoreOutlined />,
|
||||
})}
|
||||
onActiveChange={(key) => setActiveConversationId(key)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<ChatPanel
|
||||
conversationId={activeConversationId}
|
||||
onConversationCreated={setActiveConversationId}
|
||||
projectId={project.id}
|
||||
/>
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,25 @@
|
||||
import { App } from "antd";
|
||||
import { useMemo } from "react";
|
||||
import { useMemo, useState } from "react";
|
||||
|
||||
import type { Logger } from "../utils/logger";
|
||||
|
||||
import { AntdMessageSink, ConsoleSink, createDefaultLogger } from "../utils/logger";
|
||||
|
||||
export function useLogger(): Logger {
|
||||
export function useLogger(bindings?: Record<string, unknown>): Logger {
|
||||
const { message } = App.useApp();
|
||||
const [stableJson, setStableJson] = useState(() => JSON.stringify(bindings ?? {}));
|
||||
const [stableBindings, setStableBindings] = useState(() => bindings);
|
||||
const currentJson = JSON.stringify(bindings ?? {});
|
||||
|
||||
if (currentJson !== stableJson) {
|
||||
setStableJson(currentJson);
|
||||
setStableBindings(bindings);
|
||||
}
|
||||
|
||||
return useMemo(() => {
|
||||
const isProduction = !!import.meta.env["PROD"];
|
||||
return createDefaultLogger([new ConsoleSink(isProduction), new AntdMessageSink(message)], isProduction);
|
||||
}, [message]);
|
||||
const base = createDefaultLogger([new ConsoleSink(isProduction), new AntdMessageSink(message)], isProduction);
|
||||
if (!stableBindings || Object.keys(stableBindings).length === 0) return base;
|
||||
return base.child(stableBindings);
|
||||
}, [message, stableBindings]);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ body {
|
||||
}
|
||||
|
||||
.app-chat-page {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -80,67 +81,22 @@ body {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
.app-chat-sidebar {
|
||||
.app-chat-conversations {
|
||||
display: flex;
|
||||
width: 260px;
|
||||
flex-direction: column;
|
||||
border-right: 1px solid var(--ant-color-border-secondary);
|
||||
border-radius: var(--ant-border-radius-lg);
|
||||
background: var(--ant-color-bg-container);
|
||||
}
|
||||
|
||||
.app-chat-sidebar-header {
|
||||
padding: var(--ant-padding-sm);
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary);
|
||||
}
|
||||
|
||||
.app-chat-sidebar-list {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.app-chat-sidebar-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--ant-padding-xl);
|
||||
}
|
||||
|
||||
.app-chat-sidebar-item {
|
||||
cursor: pointer;
|
||||
padding: var(--ant-padding-xs) var(--ant-padding-sm);
|
||||
border-bottom: 1px solid var(--ant-color-border-secondary);
|
||||
}
|
||||
|
||||
.app-chat-sidebar-item:hover {
|
||||
background: var(--ant-color-bg-text-hover);
|
||||
}
|
||||
|
||||
.app-chat-sidebar-item-active {
|
||||
background: var(--ant-color-bg-text-hover);
|
||||
}
|
||||
|
||||
.app-chat-sidebar-item-title {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-chat-sidebar-item-action {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.app-chat-sidebar-item:hover .app-chat-sidebar-item-action,
|
||||
.app-chat-sidebar-item-active .app-chat-sidebar-item-action {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.app-chat-panel {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-welcome-area {
|
||||
@@ -158,45 +114,29 @@ body {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat-input-area {
|
||||
.chat-sender-area {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding-left: var(--ant-padding-sm);
|
||||
padding-top: var(--ant-padding-sm);
|
||||
border-top: 1px solid var(--ant-color-border-secondary);
|
||||
margin-left: var(--ant-padding-sm);
|
||||
margin-top: var(--ant-padding-sm);
|
||||
}
|
||||
|
||||
.message-body {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.message-body pre {
|
||||
background: var(--ant-color-bg-layout);
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.chat-scroll-area {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
overflow-anchor: auto;
|
||||
padding-left: var(--ant-padding-sm);
|
||||
scroll-behavior: smooth;
|
||||
margin-left: var(--ant-padding-sm);
|
||||
border-radius: var(--ant-border-radius-lg);
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-loading-indicator {
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.chat-textarea {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.message-body-text {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -243,6 +183,10 @@ body {
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.chat-sender-box {
|
||||
background: var(--ant-color-bg-container);
|
||||
}
|
||||
|
||||
.card-extra-actions .btn-dimmed {
|
||||
color: var(--ant-color-text-quaternary);
|
||||
}
|
||||
@@ -251,6 +195,53 @@ body {
|
||||
color: var(--ant-color-text-secondary);
|
||||
}
|
||||
|
||||
.chat-scroll-bottom-btn {
|
||||
position: absolute;
|
||||
bottom: 115px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.app-page-flex {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.os-theme-custom {
|
||||
--os-size: 8px;
|
||||
--os-padding-perpendicular: 2px;
|
||||
--os-padding-axis: 2px;
|
||||
--os-track-border-radius: 10px;
|
||||
--os-handle-border-radius: 10px;
|
||||
--os-handle-bg: rgba(0, 0, 0, 0.15);
|
||||
--os-handle-bg-hover: rgba(0, 0, 0, 0.25);
|
||||
--os-handle-bg-active: rgba(0, 0, 0, 0.35);
|
||||
--os-handle-min-size: 33px;
|
||||
--os-handle-max-size: none;
|
||||
--os-handle-interactive-area-offset: 4px;
|
||||
}
|
||||
|
||||
.x-markdown-light table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.x-markdown-light th,
|
||||
.x-markdown-light td {
|
||||
border: 1px solid var(--ant-color-border);
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.x-markdown-light th {
|
||||
background: var(--ant-color-fill-quaternary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.x-markdown-light .x-md-table-wrap {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ void mock.module("ai", () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
function getSendButton() {
|
||||
return screen.getByRole("button", { name: /发.*送/ });
|
||||
function getSenderTextarea() {
|
||||
return screen.getByPlaceholderText("输入消息...");
|
||||
}
|
||||
|
||||
function setupFetchMock() {
|
||||
@@ -90,8 +90,7 @@ describe("ChatPanel", () => {
|
||||
|
||||
expect(screen.getByText("你好,我是阿福")).toBeTruthy();
|
||||
expect(screen.getByText("有什么我可以帮助你的吗?")).toBeTruthy();
|
||||
expect(screen.getByPlaceholderText("输入消息...")).toBeTruthy();
|
||||
expect(getSendButton()).toBeTruthy();
|
||||
expect(getSenderTextarea()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -108,9 +107,9 @@ describe("ChatPanel", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const input = screen.getByPlaceholderText("输入消息...");
|
||||
fireEvent.change(input, { target: { value: "你好" } });
|
||||
fireEvent.click(getSendButton());
|
||||
const textarea = getSenderTextarea();
|
||||
fireEvent.change(textarea, { target: { value: "你好" } });
|
||||
fireEvent.keyDown(textarea, { key: "Enter" });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onCreated).toHaveBeenCalledWith("conv-new");
|
||||
@@ -139,12 +138,12 @@ describe("ChatPanel", () => {
|
||||
}),
|
||||
);
|
||||
|
||||
const input = screen.getByPlaceholderText("输入消息...");
|
||||
fireEvent.change(input, { target: { value: "测试输入" } });
|
||||
fireEvent.click(getSendButton());
|
||||
const textarea = getSenderTextarea();
|
||||
fireEvent.change(textarea, { target: { value: "测试输入" } });
|
||||
fireEvent.keyDown(textarea, { key: "Enter" });
|
||||
|
||||
await waitFor(() => {
|
||||
expect((input as HTMLTextAreaElement).value).toBe("测试输入");
|
||||
expect((textarea as HTMLTextAreaElement).value).toBe("测试输入");
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -161,7 +160,7 @@ describe("ChatPanel", () => {
|
||||
);
|
||||
|
||||
expect(screen.queryByText("你好,我是阿福")).toBeNull();
|
||||
expect(screen.getByPlaceholderText("输入消息...")).toBeTruthy();
|
||||
expect(getSenderTextarea()).toBeTruthy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
99
tests/web/components/chat/CodeBlockWithCopy.test.tsx
Normal file
99
tests/web/components/chat/CodeBlockWithCopy.test.tsx
Normal file
@@ -0,0 +1,99 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import { describe, expect, mock, test } from "bun:test";
|
||||
import { createElement } from "react";
|
||||
|
||||
import { CodeBlockWithCopy } from "../../../../src/web/consoles/workbench/components/chat/parts/CodeBlockWithCopy";
|
||||
import { renderWithProviders } from "../../test-utils";
|
||||
|
||||
const mockWriteText = mock(() => Promise.resolve());
|
||||
|
||||
Object.defineProperty(navigator, "clipboard", {
|
||||
configurable: true,
|
||||
get: () => ({ writeText: mockWriteText }),
|
||||
});
|
||||
|
||||
describe("CodeBlockWithCopy", () => {
|
||||
test("block 模式渲染 CodeHighlighter 和语言标签", () => {
|
||||
renderWithProviders(
|
||||
createElement(CodeBlockWithCopy, {
|
||||
block: true,
|
||||
children: "const x = 1;",
|
||||
lang: "typescript",
|
||||
streamStatus: "done",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(screen.getByText("typescript")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("block 模式渲染复制按钮", () => {
|
||||
renderWithProviders(
|
||||
createElement(CodeBlockWithCopy, {
|
||||
block: true,
|
||||
children: "hello world",
|
||||
lang: "python",
|
||||
streamStatus: "done",
|
||||
}),
|
||||
);
|
||||
|
||||
const copyBtn = screen.getByRole("button");
|
||||
expect(copyBtn).toBeTruthy();
|
||||
});
|
||||
|
||||
test("block 模式语言为空时显示 plaintext", () => {
|
||||
renderWithProviders(
|
||||
createElement(CodeBlockWithCopy, {
|
||||
block: true,
|
||||
children: "some code",
|
||||
lang: "",
|
||||
streamStatus: "done",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(screen.getByText("plaintext")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("block 模式语言为 undefined 时显示 plaintext", () => {
|
||||
renderWithProviders(
|
||||
createElement(CodeBlockWithCopy, {
|
||||
block: true,
|
||||
children: "some code",
|
||||
streamStatus: "done",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(screen.getByText("plaintext")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("inline 模式返回 code 元素", () => {
|
||||
const { container } = renderWithProviders(
|
||||
createElement(CodeBlockWithCopy, {
|
||||
block: false,
|
||||
children: "inline code",
|
||||
className: "language-ts",
|
||||
}),
|
||||
);
|
||||
|
||||
const code = container.querySelector("code.language-ts");
|
||||
expect(code).toBeTruthy();
|
||||
expect(code?.textContent).toBe("inline code");
|
||||
});
|
||||
|
||||
test("点击复制按钮调用 clipboard.writeText", () => {
|
||||
mockWriteText.mockClear();
|
||||
|
||||
renderWithProviders(
|
||||
createElement(CodeBlockWithCopy, {
|
||||
block: true,
|
||||
children: "copy me",
|
||||
lang: "javascript",
|
||||
streamStatus: "done",
|
||||
}),
|
||||
);
|
||||
|
||||
const copyBtn = screen.getByRole("button");
|
||||
copyBtn.click();
|
||||
|
||||
expect(mockWriteText).toHaveBeenCalledWith("copy me");
|
||||
});
|
||||
});
|
||||
49
tests/web/components/chat/ReasoningPart.test.tsx
Normal file
49
tests/web/components/chat/ReasoningPart.test.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import { fireEvent, screen } from "@testing-library/react";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createElement } from "react";
|
||||
|
||||
import { ReasoningPart } from "../../../../src/web/consoles/workbench/components/chat/parts/ReasoningPart";
|
||||
import { renderWithProviders } from "../../test-utils";
|
||||
|
||||
describe("ReasoningPart", () => {
|
||||
test("流式状态时自动展开显示文本", () => {
|
||||
const part = { state: "streaming", text: "正在思考...", type: "reasoning" };
|
||||
|
||||
renderWithProviders(createElement(ReasoningPart, { part }));
|
||||
|
||||
expect(screen.getByText("正在思考...")).toBeTruthy();
|
||||
expect(screen.getByText("思考中")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("完成状态时显示思考完成标签", () => {
|
||||
const part = { state: "complete", text: "思考内容", type: "reasoning" };
|
||||
|
||||
renderWithProviders(createElement(ReasoningPart, { part }));
|
||||
|
||||
expect(screen.getByText("思考完成")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("无状态时默认收起", () => {
|
||||
const part = { text: "默认内容", type: "reasoning" };
|
||||
|
||||
const { container } = renderWithProviders(createElement(ReasoningPart, { part }));
|
||||
|
||||
const expanded = container.querySelector('[aria-expanded="true"]');
|
||||
expect(expanded).toBeNull();
|
||||
});
|
||||
|
||||
test("用户点击折叠后尊重用户意图", () => {
|
||||
const part = { state: "streaming", text: "思考中内容", type: "reasoning" };
|
||||
|
||||
const { container } = renderWithProviders(createElement(ReasoningPart, { part }));
|
||||
|
||||
const collapseHeader = container.querySelector(".ant-collapse-header");
|
||||
expect(collapseHeader).toBeTruthy();
|
||||
|
||||
if (collapseHeader) {
|
||||
fireEvent.click(collapseHeader);
|
||||
}
|
||||
|
||||
expect(screen.getByText("思考中内容")).toBeTruthy();
|
||||
});
|
||||
});
|
||||
65
tests/web/components/chat/ToolPart.test.tsx
Normal file
65
tests/web/components/chat/ToolPart.test.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import { screen } from "@testing-library/react";
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { createElement } from "react";
|
||||
|
||||
import { ToolPart } from "../../../../src/web/consoles/workbench/components/chat/parts/ToolPart";
|
||||
import { renderWithProviders } from "../../test-utils";
|
||||
|
||||
describe("ToolPart 工具显示名", () => {
|
||||
test("无 toolMetadata 时使用 toolName", () => {
|
||||
const part = {
|
||||
input: { timezone: "Asia/Shanghai" },
|
||||
output: { iso: "2024-01-01T00:00:00.000Z", local: "2024年1月1日", timestamp: 1704067200000 },
|
||||
toolCallId: "call-1",
|
||||
toolName: "getCurrentTime",
|
||||
type: "tool-getCurrentTime",
|
||||
};
|
||||
|
||||
renderWithProviders(createElement(ToolPart, { part }));
|
||||
|
||||
expect(screen.getByText(/getCurrentTime/)).toBeTruthy();
|
||||
});
|
||||
|
||||
test("有 toolMetadata.displayName 时优先使用显示名", () => {
|
||||
const part = {
|
||||
input: { timezone: "Asia/Shanghai" },
|
||||
output: { iso: "2024-01-01T00:00:00.000Z", local: "2024年1月1日", timestamp: 1704067200000 },
|
||||
toolCallId: "call-1",
|
||||
toolMetadata: { displayName: "获取当前时间" },
|
||||
toolName: "getCurrentTime",
|
||||
type: "tool-getCurrentTime",
|
||||
};
|
||||
|
||||
renderWithProviders(createElement(ToolPart, { part }));
|
||||
|
||||
expect(screen.getByText("获取当前时间")).toBeTruthy();
|
||||
expect(screen.queryByText(/getCurrentTime/)).toBeNull();
|
||||
});
|
||||
|
||||
test("toolMetadata.displayName 非字符串时回退到 toolName", () => {
|
||||
const part = {
|
||||
input: {},
|
||||
output: {},
|
||||
toolCallId: "call-2",
|
||||
toolMetadata: { displayName: 123 },
|
||||
type: "tool-someTool",
|
||||
};
|
||||
|
||||
renderWithProviders(createElement(ToolPart, { part }));
|
||||
|
||||
expect(screen.getByText("someTool")).toBeTruthy();
|
||||
});
|
||||
|
||||
test("错误状态时使用显示名", () => {
|
||||
const part = {
|
||||
errorText: "超时",
|
||||
toolCallId: "call-3",
|
||||
toolMetadata: { displayName: "获取当前时间" },
|
||||
type: "tool-getCurrentTime",
|
||||
};
|
||||
|
||||
renderWithProviders(createElement(ToolPart, { part }));
|
||||
|
||||
expect(screen.getByText(/获取当前时间.*失败/)).toBeTruthy();
|
||||
});
|
||||
});
|
||||
190
tests/web/hooks/use-chat-scroll.test.ts
Normal file
190
tests/web/hooks/use-chat-scroll.test.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import type { UIMessage } from "ai";
|
||||
|
||||
import { act, renderHook } from "@testing-library/react";
|
||||
import { describe, expect, mock, test } from "bun:test";
|
||||
|
||||
import { useChatScroll } from "../../../src/web/consoles/workbench/components/chat/use-chat-scroll";
|
||||
|
||||
interface HookProps {
|
||||
loadingHistory: boolean;
|
||||
messages: UIMessage[];
|
||||
scrollRef: React.RefObject<HTMLDivElement | null>;
|
||||
status: string;
|
||||
viewportElement: HTMLDivElement | null;
|
||||
}
|
||||
|
||||
interface MockedElement {
|
||||
addEventListener: ReturnType<typeof mock>;
|
||||
clientHeight: number;
|
||||
removeEventListener: ReturnType<typeof mock>;
|
||||
scrollHeight: number;
|
||||
scrollTo: ReturnType<typeof mock>;
|
||||
scrollTop: number;
|
||||
}
|
||||
|
||||
function asRef(el: MockedElement): React.RefObject<HTMLDivElement | null> {
|
||||
return { current: el as unknown as HTMLDivElement };
|
||||
}
|
||||
|
||||
function asViewport(el: MockedElement): HTMLDivElement {
|
||||
return el as unknown as HTMLDivElement;
|
||||
}
|
||||
|
||||
function createScrollElement(overrides: Partial<MockedElement> = {}): MockedElement {
|
||||
return {
|
||||
addEventListener: mock(() => {}),
|
||||
clientHeight: 400,
|
||||
removeEventListener: mock(() => {}),
|
||||
scrollHeight: 1000,
|
||||
scrollTo: mock(() => {}),
|
||||
scrollTop: 600,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("useChatScroll", () => {
|
||||
test("在底部附近时自动滚动", () => {
|
||||
const el = createScrollElement({ scrollHeight: 1000, scrollTop: 920 });
|
||||
const scrollRef = asRef(el);
|
||||
const viewportElement = asViewport(el);
|
||||
const messages: UIMessage[] = [{ id: "1", parts: [], role: "user" }] as UIMessage[];
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ loadingHistory, messages, scrollRef, status, viewportElement }: HookProps) =>
|
||||
useChatScroll({ loadingHistory, messages, scrollRef, status, viewportElement }),
|
||||
{
|
||||
initialProps: { loadingHistory: false, messages, scrollRef, status: "streaming", viewportElement },
|
||||
},
|
||||
);
|
||||
|
||||
const newMessages: UIMessage[] = [
|
||||
{ id: "1", parts: [], role: "user" },
|
||||
{ id: "2", parts: [], role: "assistant" },
|
||||
] as UIMessage[];
|
||||
|
||||
rerender({ loadingHistory: false, messages: newMessages, scrollRef, status: "streaming", viewportElement });
|
||||
|
||||
expect(el.scrollTo).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("不在底部时不自动滚动", () => {
|
||||
const el = createScrollElement({ scrollHeight: 1000, scrollTop: 200 });
|
||||
const scrollRef = asRef(el);
|
||||
const viewportElement = asViewport(el);
|
||||
const messages: UIMessage[] = [{ id: "1", parts: [], role: "user" }] as UIMessage[];
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ loadingHistory, messages, scrollRef, status, viewportElement }: HookProps) =>
|
||||
useChatScroll({ loadingHistory, messages, scrollRef, status, viewportElement }),
|
||||
{
|
||||
initialProps: { loadingHistory: false, messages, scrollRef, status: "streaming", viewportElement },
|
||||
},
|
||||
);
|
||||
|
||||
const handlerCalls = el.addEventListener.mock.calls as unknown[][];
|
||||
const scrollHandler = handlerCalls.find((c) => c[0] === "scroll")?.[1];
|
||||
|
||||
if (scrollHandler) {
|
||||
el.scrollTop = 200;
|
||||
(scrollHandler as () => void)();
|
||||
}
|
||||
|
||||
el.scrollTo.mockClear();
|
||||
|
||||
const newMessages: UIMessage[] = [
|
||||
{ id: "1", parts: [], role: "user" },
|
||||
{ id: "2", parts: [], role: "assistant" },
|
||||
] as UIMessage[];
|
||||
|
||||
rerender({ loadingHistory: false, messages: newMessages, scrollRef, status: "streaming", viewportElement });
|
||||
|
||||
const scrollToCalls = el.scrollTo.mock.calls as unknown[][];
|
||||
expect(scrollToCalls.length).toBe(0);
|
||||
});
|
||||
|
||||
test("scrollToBottom 使用 instant 滚动", () => {
|
||||
const el = createScrollElement({ scrollHeight: 1000, scrollTop: 200 });
|
||||
const scrollRef = asRef(el);
|
||||
const viewportElement = asViewport(el);
|
||||
const messages: UIMessage[] = [];
|
||||
|
||||
const { result } = renderHook(() => useChatScroll({ messages, scrollRef, status: "ready", viewportElement }));
|
||||
|
||||
act(() => {
|
||||
result.current.scrollToBottom();
|
||||
});
|
||||
|
||||
expect(el.scrollTo).toHaveBeenCalledWith({ behavior: "instant", top: 1000 });
|
||||
});
|
||||
|
||||
test("流式时使用 instant 滚动", () => {
|
||||
const el = createScrollElement({ scrollHeight: 1000, scrollTop: 920 });
|
||||
const scrollRef = asRef(el);
|
||||
const viewportElement = asViewport(el);
|
||||
const messages: UIMessage[] = [{ id: "1", parts: [], role: "user" }] as UIMessage[];
|
||||
|
||||
renderHook(() =>
|
||||
useChatScroll({ loadingHistory: false, messages, scrollRef, status: "streaming", viewportElement }),
|
||||
);
|
||||
|
||||
const calls = el.scrollTo.mock.calls as unknown[][];
|
||||
const instantCall = calls.find((call) => {
|
||||
const opts = call[0] as Record<string, unknown> | undefined;
|
||||
return opts?.["behavior"] === "instant";
|
||||
});
|
||||
expect(instantCall).toBeTruthy();
|
||||
});
|
||||
|
||||
test("loadingHistory 从 true 变为 false 时强制 scrollToBottom", async () => {
|
||||
const el = createScrollElement({ scrollHeight: 1000, scrollTop: 0 });
|
||||
const scrollRef = asRef(el);
|
||||
const viewportElement = asViewport(el);
|
||||
const messages: UIMessage[] = [{ id: "1", parts: [], role: "user" }] as UIMessage[];
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ loadingHistory, messages, scrollRef, status, viewportElement }: HookProps) =>
|
||||
useChatScroll({ loadingHistory, messages, scrollRef, status, viewportElement }),
|
||||
{
|
||||
initialProps: { loadingHistory: true, messages, scrollRef, status: "ready", viewportElement },
|
||||
},
|
||||
);
|
||||
|
||||
el.scrollTo.mockClear();
|
||||
|
||||
act(() => {
|
||||
rerender({ loadingHistory: false, messages, scrollRef, status: "ready", viewportElement });
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||
});
|
||||
|
||||
expect(el.scrollTo).toHaveBeenCalledWith({ behavior: "instant", top: el.scrollHeight });
|
||||
});
|
||||
|
||||
test("loadingHistory 不变时不触发强制滚动", () => {
|
||||
const el = createScrollElement({ scrollHeight: 1000, scrollTop: 920 });
|
||||
const scrollRef = asRef(el);
|
||||
const viewportElement = asViewport(el);
|
||||
const messages: UIMessage[] = [];
|
||||
|
||||
const { rerender } = renderHook(
|
||||
({ loadingHistory, messages, scrollRef, status, viewportElement }: HookProps) =>
|
||||
useChatScroll({ loadingHistory, messages, scrollRef, status, viewportElement }),
|
||||
{
|
||||
initialProps: { loadingHistory: false, messages, scrollRef, status: "ready", viewportElement },
|
||||
},
|
||||
);
|
||||
|
||||
el.scrollTo.mockClear();
|
||||
|
||||
const newMessages: UIMessage[] = [{ id: "1", parts: [], role: "user" }] as UIMessage[];
|
||||
rerender({ loadingHistory: false, messages: newMessages, scrollRef, status: "ready", viewportElement });
|
||||
|
||||
const instantCalls = el.scrollTo.mock.calls.filter((call: unknown[]) => {
|
||||
const opts = call[0] as Record<string, unknown> | undefined;
|
||||
return opts?.["behavior"] === "instant" && opts?.["top"] === el.scrollHeight;
|
||||
});
|
||||
expect(instantCalls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,23 @@
|
||||
import { describe, expect, mock, test } from "bun:test";
|
||||
import { createElement } from "react";
|
||||
import { act, createElement, useState } from "react";
|
||||
|
||||
import type { Logger } from "../../../src/web/utils/logger";
|
||||
|
||||
import { useLogger } from "../../../src/web/hooks/use-logger";
|
||||
import { renderWithProviders } from "../test-utils";
|
||||
|
||||
function BindingsHookTester({
|
||||
bindings,
|
||||
onMount,
|
||||
}: {
|
||||
bindings?: Record<string, unknown>;
|
||||
onMount: (logger: Logger) => void;
|
||||
}) {
|
||||
const logger = useLogger(bindings);
|
||||
onMount(logger);
|
||||
return null;
|
||||
}
|
||||
|
||||
function HookTester({ onMount }: { onMount: (logger: Logger) => void }) {
|
||||
const logger = useLogger();
|
||||
onMount(logger);
|
||||
@@ -70,3 +82,109 @@ describe("useLogger", () => {
|
||||
expect(errorSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("useLogger bindings 参数", () => {
|
||||
test("传入 bindings 返回带上下文前缀的 Logger", () => {
|
||||
const spy = mock((..._args: unknown[]) => {});
|
||||
const origWarn = console.warn;
|
||||
console.warn = spy;
|
||||
|
||||
let logger: Logger | undefined;
|
||||
renderWithProviders(
|
||||
createElement(BindingsHookTester, {
|
||||
bindings: { component: "TestComp" },
|
||||
onMount: (l: Logger) => {
|
||||
logger = l;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
logger!.warn("绑定测试");
|
||||
|
||||
console.warn = origWarn;
|
||||
expect(spy).toHaveBeenCalledTimes(1);
|
||||
expect(spy.mock.calls[0]![0]).toMatch(/\[component=TestComp\]/);
|
||||
});
|
||||
|
||||
test("无参调用与空 bindings 行为一致", () => {
|
||||
let noBindingsLogger: Logger | undefined;
|
||||
let emptyBindingsLogger: Logger | undefined;
|
||||
|
||||
const spy = mock((..._args: unknown[]) => {});
|
||||
const origWarn = console.warn;
|
||||
console.warn = spy;
|
||||
|
||||
renderWithProviders(
|
||||
createElement(HookTester, {
|
||||
onMount: (l: Logger) => {
|
||||
noBindingsLogger = l;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
renderWithProviders(
|
||||
createElement(BindingsHookTester, {
|
||||
bindings: {},
|
||||
onMount: (l: Logger) => {
|
||||
emptyBindingsLogger = l;
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
noBindingsLogger!.warn("a");
|
||||
emptyBindingsLogger!.warn("b");
|
||||
|
||||
console.warn = origWarn;
|
||||
expect(spy).toHaveBeenCalledTimes(2);
|
||||
expect(spy.mock.calls[0]![0]).not.toMatch(/\[component=/);
|
||||
expect(spy.mock.calls[1]![0]).not.toMatch(/\[component=/);
|
||||
});
|
||||
|
||||
test("相同 bindings 值多次渲染返回同一 Logger 引用", () => {
|
||||
const refs: Logger[] = [];
|
||||
|
||||
function StableTester() {
|
||||
const logger = useLogger({ component: "Stable" });
|
||||
const [, setTick] = useState(0);
|
||||
refs.push(logger);
|
||||
return createElement("button", {
|
||||
onClick: () => setTick((t) => t + 1),
|
||||
});
|
||||
}
|
||||
|
||||
const result = renderWithProviders(createElement(StableTester));
|
||||
const initialCount = refs.length;
|
||||
|
||||
act(() => {
|
||||
result.getByRole("button").click();
|
||||
});
|
||||
act(() => {
|
||||
result.getByRole("button").click();
|
||||
});
|
||||
|
||||
expect(refs.length).toBeGreaterThan(initialCount);
|
||||
for (let i = 1; i < refs.length; i++) {
|
||||
expect(refs[i]).toBe(refs[0]);
|
||||
}
|
||||
});
|
||||
|
||||
test("bindings 值变化时返回新 Logger 引用", () => {
|
||||
const refs: Logger[] = [];
|
||||
let currentBindings: Record<string, unknown> = { component: "A" };
|
||||
|
||||
function DynamicTester() {
|
||||
const logger = useLogger(currentBindings);
|
||||
refs.push(logger);
|
||||
return null;
|
||||
}
|
||||
|
||||
const { rerender } = renderWithProviders(createElement(DynamicTester));
|
||||
const countBefore = refs.length;
|
||||
|
||||
currentBindings = { component: "B" };
|
||||
rerender(createElement(DynamicTester));
|
||||
|
||||
expect(refs.length).toBe(countBefore + 1);
|
||||
expect(refs[refs.length - 1]).not.toBe(refs[0]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { XProvider } from "@ant-design/x";
|
||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||
import { render } from "@testing-library/react";
|
||||
import { App, ConfigProvider } from "antd";
|
||||
import { App } from "antd";
|
||||
import { mock } from "bun:test";
|
||||
import { createElement, StrictMode } from "react";
|
||||
import { MemoryRouter } from "react-router";
|
||||
@@ -81,7 +82,7 @@ export function renderWithProviders(ui: React.ReactElement, options?: RenderWith
|
||||
createElement(
|
||||
MemoryRouter,
|
||||
{ initialEntries: [initialRoute] },
|
||||
createElement(ConfigProvider, null, createElement(App, null, ui)),
|
||||
createElement(XProvider, null, createElement(App, null, ui)),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user