| | |
| | | "react-barcode": "^1.6.1", |
| | | "react-dom": "^18.3.0", |
| | | "react-hook-form": "^7.53.0", |
| | | "react-markdown": "^10.1.0", |
| | | "react-router": "^6.22.0", |
| | | "react-router-dom": "^6.26.1", |
| | | "react-syntax-highlighter": "^15.5.0", |
| | | "react-to-print": "^2.14.11", |
| | | "recharts": "^2.15.0", |
| | | "remark-gfm": "^4.0.1", |
| | | "svgpath": "^2.6.0", |
| | | "three": "^0.155.0", |
| | | "tweedle.js": "^2.1.0" |
| | |
| | | "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@ampproject/remapping": "^2.2.0", |
| | | "@babel/code-frame": "^7.27.1", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", |
| | | "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@emotion/memoize": "^0.9.0" |
| | | } |
| | |
| | | "resolved": "https://registry.npmmirror.com/@emotion/react/-/react-11.14.0.tgz", |
| | | "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.18.3", |
| | | "@emotion/babel-plugin": "^11.13.5", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@emotion/styled/-/styled-11.14.0.tgz", |
| | | "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.18.3", |
| | | "@emotion/babel-plugin": "^11.13.5", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@mui/icons-material/-/icons-material-5.17.1.tgz", |
| | | "integrity": "sha512-CN86LocjkunFGG0yPlO4bgqHkNGgaEOEc3X/jG5Bzm401qYw79/SaLrofA7yAKCCXAGdIGnLoMHohc3+ubs95A==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.23.9" |
| | | }, |
| | |
| | | "resolved": "https://registry.npmmirror.com/@mui/material/-/material-5.17.1.tgz", |
| | | "integrity": "sha512-2B33kQf+GmPnrvXXweWAx+crbiUEsxCdCN979QDYnlH9ox4pd+0/IBriWLV+l6ORoBF60w39cWjFnJYGFdzXcw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.23.9", |
| | | "@mui/core-downloads-tracker": "^5.17.1", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@mui/system/-/system-6.4.11.tgz", |
| | | "integrity": "sha512-gibtsrZEwnDaT5+I/KloOj/yHluX5G8heknuxBpQOdEQ3Gc0avjSImn5hSeKp8D4thiwZiApuggIjZw1dQguUA==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.26.0", |
| | | "@mui/private-theming": "^6.4.9", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@mui/utils/-/utils-5.17.1.tgz", |
| | | "integrity": "sha512-jEZ8FTqInt2WzxDV8bhImWBqeQRD99c/id/fq83H0ER9tFl+sfZlaAoCdznGvbSQQ9ividMxqSV2c7cC1vBcQg==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.23.9", |
| | | "@mui/types": "~7.2.15", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/assets/-/assets-7.4.3.tgz", |
| | | "integrity": "sha512-StvjiJBSp/j9hHkGu8AFHNvwYUazXq64WhyhytztyDMRkg/l/cL7EcttY5T0qZNWlIpccdr60LUKrWDOuMpkiw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@types/css-font-loading-module": "^0.0.12" |
| | | }, |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/core/-/core-7.4.3.tgz", |
| | | "integrity": "sha512-5YDs11faWgVVTL8VZtLU05/Fl47vaP5Tnsbf+y/WRR0VSW3KhRRGTBU1J3Gdc2xEWbJhUK07KGP7eSZpvtPVgA==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@pixi/color": "7.4.3", |
| | | "@pixi/constants": "7.4.3", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/display/-/display-7.4.3.tgz", |
| | | "integrity": "sha512-b5m2dAaoNAVdxz1oDaxl3XZ059NEOcNtGkxTOZ4EYCw/jcp9sZXkgSROHRzsGn4k+NugH7+9MP4Id2Z0kkdUhw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "peerDependencies": { |
| | | "@pixi/core": "7.4.3" |
| | | } |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/events/-/events-7.4.3.tgz", |
| | | "integrity": "sha512-o3j/5Dxq6WDVS6eHfURB/cf/MP+NcsF/eC5PnbSHjXxJmDE7PoTVwLvxexm5uuvNRpFh/6/Fn0V8Vl4gV8sc8w==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "peerDependencies": { |
| | | "@pixi/core": "7.4.3", |
| | | "@pixi/display": "7.4.3" |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/graphics/-/graphics-7.4.3.tgz", |
| | | "integrity": "sha512-wWLivD8/URb8A7X4TqCZGG39C91IE+aOuWY/z9NCz5Z6WvA/VWnsc5fLTlO+ggjGHgKF0cSucCXZfUe1wm0AOQ==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "peerDependencies": { |
| | | "@pixi/core": "7.4.3", |
| | | "@pixi/display": "7.4.3", |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/mesh/-/mesh-7.4.3.tgz", |
| | | "integrity": "sha512-CikqFPtKvU3Zj986/MSoC8X39CWv5CEpiEW/tYp47p4tgQNDSkNWYnDiNYgb+4VX6pNsBrgX4DALLdTR17SlSA==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "peerDependencies": { |
| | | "@pixi/core": "7.4.3", |
| | | "@pixi/display": "7.4.3" |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/sprite/-/sprite-7.4.3.tgz", |
| | | "integrity": "sha512-iNBrpOFF9nXDT6m2jcyYy6l/sRzklLDDck1eFHprHZwvNquY2nzRfh+RGBCecxhBcijiLJ3fsZN33fP0LDXkvw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "peerDependencies": { |
| | | "@pixi/core": "7.4.3", |
| | | "@pixi/display": "7.4.3" |
| | |
| | | "resolved": "https://registry.npmmirror.com/@pixi/text/-/text-7.4.3.tgz", |
| | | "integrity": "sha512-IAF0iu04rPg3oiL0HZsEZI44fpJxq3UZ4xTmx8l1RyhhSXiElLvvSlSH57vt/BKMQZtCs+AqEit7yn8heK2+nQ==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "peerDependencies": { |
| | | "@pixi/core": "7.4.3", |
| | | "@pixi/sprite": "7.4.3" |
| | |
| | | "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@types/debug": { |
| | | "version": "4.1.13", |
| | | "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", |
| | | "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/ms": "*" |
| | | } |
| | | }, |
| | | "node_modules/@types/earcut": { |
| | | "version": "2.1.4", |
| | | "resolved": "https://registry.npmmirror.com/@types/earcut/-/earcut-2.1.4.tgz", |
| | |
| | | "version": "1.0.7", |
| | | "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz", |
| | | "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@types/estree-jsx": { |
| | | "version": "1.0.5", |
| | | "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", |
| | | "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/estree": "*" |
| | | } |
| | | }, |
| | | "node_modules/@types/hast": { |
| | | "version": "2.3.10", |
| | |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@types/mdast": { |
| | | "version": "4.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", |
| | | "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/@types/ms": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", |
| | | "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/@types/node": { |
| | | "version": "20.17.46", |
| | | "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.17.46.tgz", |
| | | "integrity": "sha512-0PQHLhZPWOxGW4auogW0eOQAuNIlCYvibIpG67ja0TOJ6/sehu+1en7sfceUn+QQtx4Rk3GxbLNwPh0Cav7TWw==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "undici-types": "~6.19.2" |
| | | } |
| | |
| | | "resolved": "https://registry.npmmirror.com/@types/react/-/react-18.3.21.tgz", |
| | | "integrity": "sha512-gXLBtmlcRJeT09/sI4PxVwyrku6SaNUj/6cMubjE6T6XdY1fDmBL7r0nX0jbSZPU/Xr0KuwLLZh6aOYY5d91Xw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@types/prop-types": "*", |
| | | "csstype": "^3.0.2" |
| | |
| | | "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", |
| | | "devOptional": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "peerDependencies": { |
| | | "@types/react": "^18.0.0" |
| | | } |
| | |
| | | "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", |
| | | "dev": true, |
| | | "license": "BSD-2-Clause", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@typescript-eslint/scope-manager": "5.62.0", |
| | | "@typescript-eslint/types": "5.62.0", |
| | |
| | | "version": "1.3.0", |
| | | "resolved": "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", |
| | | "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", |
| | | "dev": true, |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/@vitejs/plugin-react": { |
| | |
| | | "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "bin": { |
| | | "acorn": "bin/acorn" |
| | | }, |
| | |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/bail": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", |
| | | "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/balanced-match": { |
| | | "version": "1.0.2", |
| | | "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", |
| | |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "caniuse-lite": "^1.0.30001716", |
| | | "electron-to-chromium": "^1.5.149", |
| | |
| | | ], |
| | | "license": "CC-BY-4.0" |
| | | }, |
| | | "node_modules/ccount": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", |
| | | "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/chalk": { |
| | | "version": "4.1.2", |
| | | "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", |
| | |
| | | "version": "1.2.4", |
| | | "resolved": "https://registry.npmmirror.com/character-entities/-/character-entities-1.2.4.tgz", |
| | | "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/character-entities-html4": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", |
| | | "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | |
| | | "version": "3.1.3", |
| | | "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz", |
| | | "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", |
| | | "license": "MIT" |
| | | "license": "MIT", |
| | | "peer": true |
| | | }, |
| | | "node_modules/d3-array": { |
| | | "version": "3.2.4", |
| | |
| | | "resolved": "https://registry.npmmirror.com/date-fns/-/date-fns-3.6.0.tgz", |
| | | "integrity": "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/kossnocorp" |
| | |
| | | "version": "1.11.13", |
| | | "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.13.tgz", |
| | | "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", |
| | | "license": "MIT" |
| | | "license": "MIT", |
| | | "peer": true |
| | | }, |
| | | "node_modules/debug": { |
| | | "version": "4.4.0", |
| | |
| | | "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", |
| | | "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/decode-named-character-reference": { |
| | | "version": "1.3.0", |
| | | "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", |
| | | "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "character-entities": "^2.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/decode-named-character-reference/node_modules/character-entities": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", |
| | | "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/decode-uri-component": { |
| | | "version": "0.2.2", |
| | |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=0.4.0" |
| | | } |
| | | }, |
| | | "node_modules/dequal": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", |
| | | "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=6" |
| | | } |
| | | }, |
| | | "node_modules/devlop": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", |
| | | "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "dequal": "^2.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/dir-glob": { |
| | |
| | | "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@eslint-community/eslint-utils": "^4.2.0", |
| | | "@eslint-community/regexpp": "^4.6.1", |
| | |
| | | "node": ">=4.0" |
| | | } |
| | | }, |
| | | "node_modules/estree-util-is-identifier-name": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", |
| | | "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/esutils": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", |
| | |
| | | "version": "4.0.7", |
| | | "resolved": "https://registry.npmmirror.com/eventemitter3/-/eventemitter3-4.0.7.tgz", |
| | | "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/extend": { |
| | | "version": "3.0.2", |
| | | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", |
| | | "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/fast-deep-equal": { |
| | |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/hast-util-to-jsx-runtime": { |
| | | "version": "2.3.6", |
| | | "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", |
| | | "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/hast-util-to-jsx-runtime/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/hast-util-to-jsx-runtime/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/hast-util-to-jsx-runtime/node_modules/comma-separated-tokens": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", |
| | | "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/hast-util-to-jsx-runtime/node_modules/property-information": { |
| | | "version": "7.1.0", |
| | | "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", |
| | | "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/hast-util-to-jsx-runtime/node_modules/space-separated-tokens": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", |
| | | "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/hast-util-whitespace": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", |
| | | "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/hast": "^3.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/hast-util-whitespace/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/hastscript": { |
| | | "version": "6.0.0", |
| | | "resolved": "https://registry.npmmirror.com/hastscript/-/hastscript-6.0.0.tgz", |
| | |
| | | "resolved": "https://registry.npmmirror.com/react-is/-/react-is-16.13.1.tgz", |
| | | "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/html-url-attributes": { |
| | | "version": "3.0.1", |
| | | "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", |
| | | "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/ignore": { |
| | | "version": "5.3.2", |
| | |
| | | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", |
| | | "dev": true, |
| | | "license": "ISC" |
| | | }, |
| | | "node_modules/inline-style-parser": { |
| | | "version": "0.2.7", |
| | | "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", |
| | | "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/internal-slot": { |
| | | "version": "1.1.0", |
| | |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=8" |
| | | } |
| | | }, |
| | | "node_modules/is-plain-obj": { |
| | | "version": "4.1.0", |
| | | "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", |
| | | "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=12" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/is-regex": { |
| | |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/longest-streak": { |
| | | "version": "3.1.0", |
| | | "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", |
| | | "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/loose-envify": { |
| | | "version": "1.4.0", |
| | | "resolved": "https://registry.npmmirror.com/loose-envify/-/loose-envify-1.4.0.tgz", |
| | |
| | | "yallist": "^3.0.2" |
| | | } |
| | | }, |
| | | "node_modules/markdown-table": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", |
| | | "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/math-intrinsics": { |
| | | "version": "1.1.0", |
| | | "resolved": "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz", |
| | |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">= 0.4" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-find-and-replace": { |
| | | "version": "3.0.2", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", |
| | | "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { |
| | | "version": "5.0.0", |
| | | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", |
| | | "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", |
| | | "license": "MIT", |
| | | "engines": { |
| | | "node": ">=12" |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-from-markdown": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", |
| | | "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-from-markdown/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/mdast-util-gfm": { |
| | | "version": "3.1.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", |
| | | "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-gfm-autolink-literal": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", |
| | | "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-gfm-footnote": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", |
| | | "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-gfm-strikethrough": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", |
| | | "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/mdast": "^4.0.0", |
| | | "mdast-util-from-markdown": "^2.0.0", |
| | | "mdast-util-to-markdown": "^2.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-gfm-table": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", |
| | | "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-gfm-task-list-item": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", |
| | | "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/mdast": "^4.0.0", |
| | | "devlop": "^1.0.0", |
| | | "mdast-util-from-markdown": "^2.0.0", |
| | | "mdast-util-to-markdown": "^2.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-expression": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", |
| | | "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-expression/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx": { |
| | | "version": "3.2.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", |
| | | "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/character-entities-legacy": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", |
| | | "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/character-reference-invalid": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", |
| | | "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/is-alphabetical": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", |
| | | "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/is-alphanumerical": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", |
| | | "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "is-alphabetical": "^2.0.0", |
| | | "is-decimal": "^2.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/is-decimal": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", |
| | | "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/is-hexadecimal": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", |
| | | "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/parse-entities": { |
| | | "version": "4.0.2", |
| | | "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", |
| | | "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^2.0.0", |
| | | "character-entities-legacy": "^3.0.0", |
| | | "character-reference-invalid": "^2.0.0", |
| | | "decode-named-character-reference": "^1.0.0", |
| | | "is-alphanumerical": "^2.0.0", |
| | | "is-decimal": "^2.0.0", |
| | | "is-hexadecimal": "^2.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdx-jsx/node_modules/parse-entities/node_modules/@types/unist": { |
| | | "version": "2.0.11", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", |
| | | "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/mdast-util-mdxjs-esm": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", |
| | | "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-mdxjs-esm/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-phrasing": { |
| | | "version": "4.1.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", |
| | | "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/mdast": "^4.0.0", |
| | | "unist-util-is": "^6.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-to-hast": { |
| | | "version": "13.2.1", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", |
| | | "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-to-hast/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-to-markdown": { |
| | | "version": "2.1.2", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", |
| | | "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/mdast-util-to-markdown/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/mdast-util-to-string": { |
| | | "version": "4.0.0", |
| | | "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", |
| | | "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/mdast": "^4.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/memoize-one": { |
| | |
| | | "engines": { |
| | | "node": ">= 8" |
| | | } |
| | | }, |
| | | "node_modules/micromark": { |
| | | "version": "4.0.2", |
| | | "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", |
| | | "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "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" |
| | | } |
| | | }, |
| | | "node_modules/micromark-core-commonmark": { |
| | | "version": "2.0.3", |
| | | "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", |
| | | "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "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" |
| | | } |
| | | }, |
| | | "node_modules/micromark-extension-gfm": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", |
| | | "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/micromark-extension-gfm-autolink-literal": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", |
| | | "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/micromark-extension-gfm-footnote": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", |
| | | "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/micromark-extension-gfm-strikethrough": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", |
| | | "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/micromark-extension-gfm-table": { |
| | | "version": "2.1.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", |
| | | "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/micromark-extension-gfm-tagfilter": { |
| | | "version": "2.0.0", |
| | | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", |
| | | "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-types": "^2.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/micromark-extension-gfm-task-list-item": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", |
| | | "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/micromark-factory-destination": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", |
| | | "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-character": "^2.0.0", |
| | | "micromark-util-symbol": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-factory-label": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", |
| | | "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "devlop": "^1.0.0", |
| | | "micromark-util-character": "^2.0.0", |
| | | "micromark-util-symbol": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-factory-space": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", |
| | | "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-character": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-factory-title": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", |
| | | "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "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" |
| | | } |
| | | }, |
| | | "node_modules/micromark-factory-whitespace": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", |
| | | "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "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" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-character": { |
| | | "version": "2.1.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", |
| | | "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-symbol": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-chunked": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", |
| | | "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-symbol": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-classify-character": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", |
| | | "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-character": "^2.0.0", |
| | | "micromark-util-symbol": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-combine-extensions": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", |
| | | "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-chunked": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-decode-numeric-character-reference": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", |
| | | "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-symbol": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-decode-string": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", |
| | | "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "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" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-encode": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", |
| | | "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/micromark-util-html-tag-name": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", |
| | | "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/micromark-util-normalize-identifier": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", |
| | | "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-symbol": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-resolve-all": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", |
| | | "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-sanitize-uri": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", |
| | | "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "micromark-util-character": "^2.0.0", |
| | | "micromark-util-encode": "^2.0.0", |
| | | "micromark-util-symbol": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-subtokenize": { |
| | | "version": "2.1.0", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", |
| | | "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "devlop": "^1.0.0", |
| | | "micromark-util-chunked": "^2.0.0", |
| | | "micromark-util-symbol": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0" |
| | | } |
| | | }, |
| | | "node_modules/micromark-util-symbol": { |
| | | "version": "2.0.1", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", |
| | | "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/micromark-util-types": { |
| | | "version": "2.0.2", |
| | | "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", |
| | | "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", |
| | | "funding": [ |
| | | { |
| | | "type": "GitHub Sponsors", |
| | | "url": "https://github.com/sponsors/unifiedjs" |
| | | }, |
| | | { |
| | | "type": "OpenCollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | ], |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/micromatch": { |
| | | "version": "4.0.8", |
| | |
| | | "resolved": "https://registry.npmmirror.com/react/-/react-18.3.1.tgz", |
| | | "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "loose-envify": "^1.1.0" |
| | | }, |
| | |
| | | "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz", |
| | | "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "loose-envify": "^1.1.0", |
| | | "scheduler": "^0.23.2" |
| | |
| | | "resolved": "https://registry.npmmirror.com/react-hook-form/-/react-hook-form-7.56.3.tgz", |
| | | "integrity": "sha512-IK18V6GVbab4TAo1/cz3kqajxbDPGofdF0w7VHdCo0Nt8PrPlOZcuuDq9YYIV1BtjcX78x0XsldbQRQnQXWXmw==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "engines": { |
| | | "node": ">=18.0.0" |
| | | }, |
| | |
| | | "version": "19.1.0", |
| | | "resolved": "https://registry.npmmirror.com/react-is/-/react-is-19.1.0.tgz", |
| | | "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", |
| | | "license": "MIT" |
| | | "license": "MIT", |
| | | "peer": true |
| | | }, |
| | | "node_modules/react-markdown": { |
| | | "version": "10.1.0", |
| | | "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", |
| | | "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/hast": "^3.0.0", |
| | | "@types/mdast": "^4.0.0", |
| | | "devlop": "^1.0.0", |
| | | "hast-util-to-jsx-runtime": "^2.0.0", |
| | | "html-url-attributes": "^3.0.0", |
| | | "mdast-util-to-hast": "^13.0.0", |
| | | "remark-parse": "^11.0.0", |
| | | "remark-rehype": "^11.0.0", |
| | | "unified": "^11.0.0", |
| | | "unist-util-visit": "^5.0.0", |
| | | "vfile": "^6.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | }, |
| | | "peerDependencies": { |
| | | "@types/react": ">=18", |
| | | "react": ">=18" |
| | | } |
| | | }, |
| | | "node_modules/react-markdown/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/react-redux": { |
| | | "version": "8.1.3", |
| | |
| | | "resolved": "https://registry.npmmirror.com/react-router/-/react-router-6.30.0.tgz", |
| | | "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@remix-run/router": "1.23.0" |
| | | }, |
| | |
| | | "resolved": "https://registry.npmmirror.com/react-router-dom/-/react-router-dom-6.30.0.tgz", |
| | | "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@remix-run/router": "1.23.0", |
| | | "react-router": "6.30.0" |
| | |
| | | "resolved": "https://registry.npmmirror.com/redux/-/redux-4.2.1.tgz", |
| | | "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "@babel/runtime": "^7.9.2" |
| | | } |
| | |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/remark-gfm": { |
| | | "version": "4.0.1", |
| | | "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", |
| | | "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/remark-parse": { |
| | | "version": "11.0.0", |
| | | "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", |
| | | "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/mdast": "^4.0.0", |
| | | "mdast-util-from-markdown": "^2.0.0", |
| | | "micromark-util-types": "^2.0.0", |
| | | "unified": "^11.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/remark-rehype": { |
| | | "version": "11.1.2", |
| | | "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", |
| | | "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/remark-rehype/node_modules/@types/hast": { |
| | | "version": "3.0.4", |
| | | "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", |
| | | "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "*" |
| | | } |
| | | }, |
| | | "node_modules/remark-stringify": { |
| | | "version": "11.0.0", |
| | | "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", |
| | | "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/mdast": "^4.0.0", |
| | | "mdast-util-to-markdown": "^2.0.0", |
| | | "unified": "^11.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/remove-accents": { |
| | |
| | | "url": "https://github.com/sponsors/ljharb" |
| | | } |
| | | }, |
| | | "node_modules/stringify-entities": { |
| | | "version": "4.0.4", |
| | | "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", |
| | | "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "character-entities-html4": "^2.0.0", |
| | | "character-entities-legacy": "^3.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/stringify-entities/node_modules/character-entities-legacy": { |
| | | "version": "3.0.0", |
| | | "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", |
| | | "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/strip-ansi": { |
| | | "version": "6.0.1", |
| | | "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-6.0.1.tgz", |
| | |
| | | }, |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/style-to-js": { |
| | | "version": "1.1.21", |
| | | "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", |
| | | "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "style-to-object": "1.0.14" |
| | | } |
| | | }, |
| | | "node_modules/style-to-object": { |
| | | "version": "1.0.14", |
| | | "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", |
| | | "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "inline-style-parser": "0.2.7" |
| | | } |
| | | }, |
| | | "node_modules/stylis": { |
| | |
| | | }, |
| | | "engines": { |
| | | "node": ">=8.0" |
| | | } |
| | | }, |
| | | "node_modules/trim-lines": { |
| | | "version": "3.0.1", |
| | | "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", |
| | | "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/trough": { |
| | | "version": "2.2.0", |
| | | "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", |
| | | "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | }, |
| | | "node_modules/tslib": { |
| | |
| | | "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", |
| | | "dev": true, |
| | | "license": "Apache-2.0", |
| | | "peer": true, |
| | | "bin": { |
| | | "tsc": "bin/tsc", |
| | | "tsserver": "bin/tsserver" |
| | |
| | | "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-6.19.8.tgz", |
| | | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", |
| | | "dev": true, |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/unified": { |
| | | "version": "11.0.5", |
| | | "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", |
| | | "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", |
| | | "license": "MIT", |
| | | "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" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/unified/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/unist-util-is": { |
| | | "version": "6.0.1", |
| | | "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", |
| | | "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^3.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/unist-util-is/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/unist-util-position": { |
| | | "version": "5.0.0", |
| | | "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", |
| | | "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^3.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/unist-util-position/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/unist-util-stringify-position": { |
| | | "version": "4.0.0", |
| | | "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", |
| | | "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^3.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/unist-util-stringify-position/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/unist-util-visit": { |
| | | "version": "5.1.0", |
| | | "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", |
| | | "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^3.0.0", |
| | | "unist-util-is": "^6.0.0", |
| | | "unist-util-visit-parents": "^6.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/unist-util-visit-parents": { |
| | | "version": "6.0.2", |
| | | "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", |
| | | "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^3.0.0", |
| | | "unist-util-is": "^6.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/unist-util-visit-parents/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/unist-util-visit/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/update-browserslist-db": { |
| | |
| | | "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" |
| | | } |
| | | }, |
| | | "node_modules/vfile": { |
| | | "version": "6.0.3", |
| | | "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", |
| | | "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^3.0.0", |
| | | "vfile-message": "^4.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/vfile-message": { |
| | | "version": "4.0.3", |
| | | "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", |
| | | "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", |
| | | "license": "MIT", |
| | | "dependencies": { |
| | | "@types/unist": "^3.0.0", |
| | | "unist-util-stringify-position": "^4.0.0" |
| | | }, |
| | | "funding": { |
| | | "type": "opencollective", |
| | | "url": "https://opencollective.com/unified" |
| | | } |
| | | }, |
| | | "node_modules/vfile-message/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/vfile/node_modules/@types/unist": { |
| | | "version": "3.0.3", |
| | | "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", |
| | | "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", |
| | | "license": "MIT" |
| | | }, |
| | | "node_modules/victory-vendor": { |
| | | "version": "36.9.2", |
| | | "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", |
| | |
| | | "integrity": "sha512-qO3aKv3HoQC8QKiNSTuUM1l9o/XX3+c+VTgLHbJWHZGeTPVAg2XwazI9UWzoxjIJCGCV2zU60uqMzjeLZuULqA==", |
| | | "dev": true, |
| | | "license": "MIT", |
| | | "peer": true, |
| | | "dependencies": { |
| | | "esbuild": "^0.21.3", |
| | | "postcss": "^8.4.43", |
| | |
| | | "funding": { |
| | | "url": "https://github.com/sponsors/sindresorhus" |
| | | } |
| | | }, |
| | | "node_modules/zwitch": { |
| | | "version": "2.0.4", |
| | | "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", |
| | | "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", |
| | | "license": "MIT", |
| | | "funding": { |
| | | "type": "github", |
| | | "url": "https://github.com/sponsors/wooorm" |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | |
| | | "react-barcode": "^1.6.1", |
| | | "react-dom": "^18.3.0", |
| | | "react-hook-form": "^7.53.0", |
| | | "react-markdown": "^10.1.0", |
| | | "react-router": "^6.22.0", |
| | | "react-router-dom": "^6.26.1", |
| | | "react-syntax-highlighter": "^15.5.0", |
| | | "react-to-print": "^2.14.11", |
| | | "recharts": "^2.15.0", |
| | | "remark-gfm": "^4.0.1", |
| | | "svgpath": "^2.6.0", |
| | | "three": "^0.155.0", |
| | | "tweedle.js": "^2.1.0", |
| | | "recharts": "^2.15.0" |
| | | "tweedle.js": "^2.1.0" |
| | | }, |
| | | "devDependencies": { |
| | | "@types/node": "^20.10.7", |
| | |
| | | "vite": "^5.3.5" |
| | | }, |
| | | "name": "rsf" |
| | | } |
| | | } |
| | |
| | | import { PREFIX_BASE_URL, TOKEN_HEADER_NAME } from "@/config/setting"; |
| | | import { getToken } from "@/utils/token-util"; |
| | | |
| | | export const getAiRuntime = async (promptCode = "home.default", sessionId = null) => { |
| | | export const getAiRuntime = async (promptCode = "home.default", sessionId = null, aiParamId = null) => { |
| | | const res = await request.get("ai/chat/runtime", { |
| | | params: { promptCode, sessionId }, |
| | | params: { promptCode, sessionId, aiParamId }, |
| | | }); |
| | | const { code, msg, data } = res.data; |
| | | if (code === 200) { |
| | |
| | | throw new Error(msg || "AI 参数验证失败"); |
| | | }; |
| | | |
| | | export const setAiParamDefault = async (id) => { |
| | | const res = await request.post(`aiParam/set-default/${id}`); |
| | | const { code, msg, data } = res.data; |
| | | if (code === 200) { |
| | | return data; |
| | | } |
| | | throw new Error(msg || "设置默认 AI 参数失败"); |
| | | }; |
| | | |
| | | export const renderAiPromptPreview = async (payload) => { |
| | | const res = await request.post("aiPrompt/render-preview", payload); |
| | | const { code, msg, data } = res.data; |
| | |
| | | model: "Model", |
| | | baseUrl: "Base URL", |
| | | apiKey: "API Key", |
| | | defaultStatus: "Default Status", |
| | | temperature: "Temperature", |
| | | topP: "Top P", |
| | | maxTokens: "Max Tokens", |
| | |
| | | emptyDescription: "Create an OpenAI-compatible model card first.", |
| | | streaming: "Streaming", |
| | | nonStreaming: "Non-streaming", |
| | | }, |
| | | status: { |
| | | default: "Default", |
| | | nonDefault: "Non-default", |
| | | }, |
| | | actions: { |
| | | setDefault: "Set Default", |
| | | currentDefault: "Current Default", |
| | | setDefaultSuccess: "Default AI parameter updated", |
| | | setDefaultFailed: "Failed to set the default AI parameter", |
| | | }, |
| | | dialog: { |
| | | create: "New AI Parameter", |
| | |
| | | sessionMetric: "Session: %{id}", |
| | | promptMetric: "Prompt: %{value}", |
| | | modelMetric: "Model: %{value}", |
| | | modelSelectorLabel: "Chat Model", |
| | | modelSelectorHint: "Switching only affects subsequent replies in this session and does not change the global default model.", |
| | | modelSwitchFailed: "Failed to switch the chat model", |
| | | defaultModelSuffix: "(Default)", |
| | | mcpMetric: "MCP: %{value}", |
| | | historyMetric: "History: %{value}", |
| | | recentMetric: "Recent: %{value}", |
| | |
| | | model: "模型", |
| | | baseUrl: "服务地址", |
| | | apiKey: "API 密钥", |
| | | defaultStatus: "默认状态", |
| | | temperature: "温度", |
| | | topP: "Top P 采样", |
| | | maxTokens: "最大 Tokens", |
| | |
| | | emptyDescription: "可以先新建一个 OpenAI 兼容模型参数卡片。", |
| | | streaming: "流式响应", |
| | | nonStreaming: "非流式", |
| | | }, |
| | | status: { |
| | | default: "默认", |
| | | nonDefault: "非默认", |
| | | }, |
| | | actions: { |
| | | setDefault: "设为默认", |
| | | currentDefault: "当前默认", |
| | | setDefaultSuccess: "已设置为默认 AI 参数", |
| | | setDefaultFailed: "设置默认 AI 参数失败", |
| | | }, |
| | | dialog: { |
| | | create: "新建 AI 参数", |
| | |
| | | }, |
| | | }, |
| | | drawer: { |
| | | title: "AI 对话", |
| | | title: "WMS 助手", |
| | | runtimeFailed: "获取 AI 运行时失败", |
| | | sessionListFailed: "获取 AI 会话列表失败", |
| | | sessionDeleted: "会话已删除", |
| | |
| | | sessionMetric: "Session: %{id}", |
| | | promptMetric: "Prompt: %{value}", |
| | | modelMetric: "Model: %{value}", |
| | | modelSelectorLabel: "对话模型", |
| | | modelSelectorHint: "切换后仅影响当前会话后续回复,不会改动全局默认模型。", |
| | | modelSwitchFailed: "切换对话模型失败", |
| | | defaultModelSuffix: "(默认)", |
| | | mcpMetric: "MCP: %{value}", |
| | | historyMetric: "History: %{value}", |
| | | recentMetric: "Recent: %{value}", |
| | |
| | | import React, { useEffect, useMemo, useRef, useState } from "react"; |
| | | import { useLocation, useNavigate } from "react-router-dom"; |
| | | import { useNotify, useTranslate } from "react-admin"; |
| | | import ReactMarkdown from "react-markdown"; |
| | | import remarkGfm from "remark-gfm"; |
| | | import { |
| | | Alert, |
| | | Box, |
| | |
| | | List, |
| | | ListItemButton, |
| | | ListItemText, |
| | | MenuItem, |
| | | Paper, |
| | | Stack, |
| | | TextField, |
| | | Typography, |
| | | } from "@mui/material"; |
| | | import { Light as SyntaxHighlighter } from "react-syntax-highlighter"; |
| | | import { atomOneLight } from "react-syntax-highlighter/dist/esm/styles/hljs"; |
| | | import SmartToyOutlinedIcon from "@mui/icons-material/SmartToyOutlined"; |
| | | import SendRoundedIcon from "@mui/icons-material/SendRounded"; |
| | | import StopCircleOutlinedIcon from "@mui/icons-material/StopCircleOutlined"; |
| | |
| | | ANSWER: 2, |
| | | }; |
| | | |
| | | const normalizeMarkdownContent = (content) => { |
| | | if (!content) { |
| | | return ""; |
| | | } |
| | | return content |
| | | .replace(/\r\n/g, "\n") |
| | | .replace(/(\n[-*] .+)\n{2,}(?=[-*] )/g, "$1\n") |
| | | .replace(/(\n\d+\. .+)\n{2,}(?=\d+\. )/g, "$1\n") |
| | | .replace(/([^\n])\n{3,}/g, "$1\n\n"); |
| | | }; |
| | | |
| | | const markdownSx = { |
| | | width: "100%", |
| | | fontSize: "0.84rem", |
| | | "& > *:first-of-type": { |
| | | mt: 0, |
| | | }, |
| | | "& > *:last-child": { |
| | | mb: 0, |
| | | }, |
| | | "& p": { |
| | | m: 0, |
| | | lineHeight: 1.28, |
| | | }, |
| | | "& p + p": { |
| | | mt: 0.1, |
| | | }, |
| | | "& h1, & h2, & h3, & h4, & h5, & h6": { |
| | | mt: 0.25, |
| | | mb: 0.04, |
| | | lineHeight: 1.16, |
| | | fontWeight: 700, |
| | | }, |
| | | "& h1": { |
| | | fontSize: "0.96rem", |
| | | }, |
| | | "& h2": { |
| | | fontSize: "0.92rem", |
| | | }, |
| | | "& h3": { |
| | | fontSize: "0.89rem", |
| | | }, |
| | | "& ul, & ol": { |
| | | my: 0.02, |
| | | pl: 1.3, |
| | | }, |
| | | "& ul > li, & ol > li": { |
| | | lineHeight: 1.2, |
| | | }, |
| | | "& li + li": { |
| | | mt: 0, |
| | | }, |
| | | "& li > p": { |
| | | display: "inline", |
| | | m: 0, |
| | | lineHeight: "inherit", |
| | | }, |
| | | "& li::marker": { |
| | | fontSize: "0.78rem", |
| | | }, |
| | | "& blockquote": { |
| | | m: 0, |
| | | mt: 0.18, |
| | | px: 0.7, |
| | | py: 0.25, |
| | | borderLeft: "3px solid rgba(25, 118, 210, 0.35)", |
| | | bgcolor: "rgba(25, 118, 210, 0.06)", |
| | | }, |
| | | "& hr": { |
| | | my: 0.25, |
| | | border: 0, |
| | | borderTop: "1px solid rgba(0, 0, 0, 0.12)", |
| | | }, |
| | | "& table": { |
| | | width: "100%", |
| | | borderCollapse: "collapse", |
| | | mt: 0.18, |
| | | mb: 0.04, |
| | | fontSize: "0.78rem", |
| | | }, |
| | | "& th, & td": { |
| | | border: "1px solid rgba(0, 0, 0, 0.12)", |
| | | px: 0.4, |
| | | py: 0.22, |
| | | textAlign: "left", |
| | | verticalAlign: "top", |
| | | }, |
| | | "& th": { |
| | | bgcolor: "rgba(0, 0, 0, 0.04)", |
| | | fontWeight: 700, |
| | | }, |
| | | "& a": { |
| | | color: "primary.main", |
| | | textDecoration: "underline", |
| | | wordBreak: "break-all", |
| | | }, |
| | | "& img": { |
| | | maxWidth: "100%", |
| | | borderRadius: 1.5, |
| | | }, |
| | | "& code": { |
| | | fontFamily: "'Consolas', 'Monaco', monospace", |
| | | }, |
| | | }; |
| | | |
| | | const AiMarkdownContent = ({ content }) => ( |
| | | <Box sx={markdownSx}> |
| | | <ReactMarkdown |
| | | remarkPlugins={[remarkGfm]} |
| | | components={{ |
| | | p: ({ children }) => <Typography variant="body2">{children}</Typography>, |
| | | li: ({ children }) => <Box component="li" sx={{ fontSize: "0.875rem" }}>{children}</Box>, |
| | | blockquote: ({ children }) => <Box component="blockquote">{children}</Box>, |
| | | a: ({ href, children }) => ( |
| | | <Box |
| | | component="a" |
| | | href={href} |
| | | target="_blank" |
| | | rel="noreferrer" |
| | | > |
| | | {children} |
| | | </Box> |
| | | ), |
| | | code({ inline, className, children, ...props }) { |
| | | const match = /language-(\w+)/.exec(className || ""); |
| | | const code = String(children).replace(/\n$/, ""); |
| | | if (!inline) { |
| | | return ( |
| | | <Box sx={{ mt: 0.7, mb: 0.2, borderRadius: 1.5, overflow: "hidden" }}> |
| | | <SyntaxHighlighter |
| | | language={match?.[1]} |
| | | style={atomOneLight} |
| | | customStyle={{ |
| | | margin: 0, |
| | | padding: "6px 8px", |
| | | borderRadius: 12, |
| | | fontSize: "0.74rem", |
| | | }} |
| | | wrapLongLines |
| | | PreTag="div" |
| | | {...props} |
| | | > |
| | | {code} |
| | | </SyntaxHighlighter> |
| | | </Box> |
| | | ); |
| | | } |
| | | return ( |
| | | <Box |
| | | component="code" |
| | | sx={{ |
| | | px: 0.45, |
| | | py: "1px", |
| | | borderRadius: 0.75, |
| | | bgcolor: "rgba(0, 0, 0, 0.08)", |
| | | fontSize: "0.74em", |
| | | }} |
| | | {...props} |
| | | > |
| | | {children} |
| | | </Box> |
| | | ); |
| | | }, |
| | | }} |
| | | > |
| | | {normalizeMarkdownContent(content)} |
| | | </ReactMarkdown> |
| | | </Box> |
| | | ); |
| | | |
| | | const AiChatDrawer = ({ open, onClose }) => { |
| | | const navigate = useNavigate(); |
| | | const location = useLocation(); |
| | |
| | | const messagesContainerRef = useRef(null); |
| | | const messagesBottomRef = useRef(null); |
| | | const [runtime, setRuntime] = useState(null); |
| | | const [selectedAiParamId, setSelectedAiParamId] = useState(null); |
| | | const [sessionId, setSessionId] = useState(null); |
| | | const [sessions, setSessions] = useState([]); |
| | | const [persistedMessages, setPersistedMessages] = useState([]); |
| | |
| | | ]), [translate]); |
| | | |
| | | const promptCode = runtime?.promptCode || DEFAULT_PROMPT_CODE; |
| | | const selectableModelOptions = useMemo(() => { |
| | | if (runtime?.modelOptions?.length) { |
| | | return runtime.modelOptions; |
| | | } |
| | | if (runtime?.model) { |
| | | return [{ |
| | | aiParamId: runtime?.aiParamId ?? "CURRENT_MODEL", |
| | | name: runtime.model, |
| | | model: runtime.model, |
| | | active: true, |
| | | }]; |
| | | } |
| | | return []; |
| | | }, [runtime]); |
| | | |
| | | const runtimeSummary = useMemo(() => { |
| | | return { |
| | |
| | | ]); |
| | | }; |
| | | |
| | | const loadRuntime = async (targetSessionId = null) => { |
| | | const loadRuntime = async (targetSessionId = null, targetAiParamId = selectedAiParamId) => { |
| | | setLoadingRuntime(true); |
| | | setDrawerError(""); |
| | | try { |
| | | const data = await getAiRuntime(DEFAULT_PROMPT_CODE, targetSessionId); |
| | | const data = await getAiRuntime(DEFAULT_PROMPT_CODE, targetSessionId, targetAiParamId); |
| | | const historyMessages = data?.persistedMessages || []; |
| | | setRuntime(data); |
| | | setSelectedAiParamId(data?.aiParamId ?? null); |
| | | setSessionId(data?.sessionId || null); |
| | | setPersistedMessages(historyMessages); |
| | | setMessages(historyMessages); |
| | | return data; |
| | | } catch (error) { |
| | | const message = error.message || translate("ai.drawer.runtimeFailed"); |
| | | setDrawerError(message); |
| | | return null; |
| | | } finally { |
| | | setLoadingRuntime(false); |
| | | } |
| | |
| | | setThinkingEvents([]); |
| | | setThinkingExpanded(true); |
| | | await loadRuntime(targetSessionId); |
| | | }; |
| | | |
| | | const handleModelChange = async (event) => { |
| | | if (streaming) { |
| | | return; |
| | | } |
| | | const rawValue = event.target.value; |
| | | const nextAiParamId = rawValue === "" ? null : Number(rawValue); |
| | | if (nextAiParamId === selectedAiParamId) { |
| | | return; |
| | | } |
| | | const previousAiParamId = selectedAiParamId; |
| | | setSelectedAiParamId(nextAiParamId); |
| | | const data = await loadRuntime(sessionId, nextAiParamId); |
| | | if (!data) { |
| | | setSelectedAiParamId(previousAiParamId); |
| | | notify(translate("ai.drawer.modelSwitchFailed"), { type: "error" }); |
| | | } |
| | | }; |
| | | |
| | | const handleDeleteSession = async (targetSessionId) => { |
| | |
| | | |
| | | let completed = false; |
| | | let completedSessionId = sessionId; |
| | | let completedAiParamId = selectedAiParamId; |
| | | |
| | | try { |
| | | await streamAiChat( |
| | | { |
| | | sessionId, |
| | | aiParamId: selectedAiParamId, |
| | | promptCode, |
| | | messages: memoryMessages, |
| | | metadata: { |
| | |
| | | onEvent: (eventName, payload) => { |
| | | if (eventName === "start") { |
| | | setRuntime(payload); |
| | | setSelectedAiParamId(payload?.aiParamId ?? null); |
| | | if (payload?.sessionId) { |
| | | setSessionId(payload.sessionId); |
| | | completedSessionId = payload.sessionId; |
| | | } |
| | | completedAiParamId = payload?.aiParamId ?? completedAiParamId; |
| | | } |
| | | if (eventName === "delta") { |
| | | appendAssistantDelta(payload?.content || ""); |
| | |
| | | setStreaming(false); |
| | | if (completed) { |
| | | await Promise.all([ |
| | | loadRuntime(completedSessionId), |
| | | loadRuntime(completedSessionId, completedAiParamId), |
| | | loadSessions(sessionKeyword), |
| | | ]); |
| | | } |
| | |
| | | <Typography variant="caption" display="block" sx={{ opacity: 0.72, mb: 0.5 }}> |
| | | {message.role === "user" ? translate("ai.drawer.userRole") : translate("ai.drawer.assistantRole")} |
| | | </Typography> |
| | | <Typography variant="body2"> |
| | | {message.content || (streaming && index === messages.length - 1 ? translate("ai.drawer.thinking") : "")} |
| | | </Typography> |
| | | {message.role === "assistant" ? ( |
| | | <AiMarkdownContent |
| | | content={message.content || (streaming && index === messages.length - 1 |
| | | ? translate("ai.drawer.thinking") |
| | | : "")} |
| | | /> |
| | | ) : ( |
| | | <Typography variant="body2" sx={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}> |
| | | {message.content || ""} |
| | | </Typography> |
| | | )} |
| | | </Paper> |
| | | </Stack> |
| | | </Box> |
| | |
| | | maxRows={6} |
| | | placeholder={translate("ai.drawer.inputPlaceholder")} |
| | | /> |
| | | <Stack direction="row" spacing={1} justifyContent="flex-end" mt={1.25}> |
| | | <Stack |
| | | direction={{ xs: "column", sm: "row" }} |
| | | spacing={1} |
| | | justifyContent="space-between" |
| | | alignItems={{ xs: "stretch", sm: "center" }} |
| | | mt={1.25} |
| | | > |
| | | {!!selectableModelOptions.length && ( |
| | | <TextField |
| | | select |
| | | size="small" |
| | | label={translate("ai.drawer.modelSelectorLabel")} |
| | | value={selectedAiParamId ?? runtime?.aiParamId ?? selectableModelOptions[0]?.aiParamId ?? ""} |
| | | onChange={handleModelChange} |
| | | disabled={streaming || loadingRuntime || selectableModelOptions.length <= 1} |
| | | SelectProps={{ |
| | | MenuProps: { |
| | | disableScrollLock: true, |
| | | sx: { |
| | | zIndex: 1605, |
| | | }, |
| | | PaperProps: { |
| | | sx: { |
| | | zIndex: 1606, |
| | | }, |
| | | }, |
| | | }, |
| | | }} |
| | | sx={{ |
| | | minWidth: { xs: "100%", sm: 260 }, |
| | | maxWidth: { xs: "100%", sm: 320 }, |
| | | }} |
| | | > |
| | | {selectableModelOptions.map((item) => ( |
| | | <MenuItem key={String(item.aiParamId)} value={item.aiParamId}> |
| | | {`${item.name || item.model || "--"}${item.model && item.name !== item.model ? ` / ${item.model}` : ""}${item.active ? ` ${translate("ai.drawer.defaultModelSuffix")}` : ""}`} |
| | | </MenuItem> |
| | | ))} |
| | | </TextField> |
| | | )} |
| | | <Stack direction="row" spacing={1} justifyContent="flex-end"> |
| | | <Button onClick={() => setInput("")}>{translate("ai.drawer.clearInput")}</Button> |
| | | {streaming ? ( |
| | | <Button variant="outlined" color="warning" startIcon={<StopCircleOutlinedIcon />} onClick={() => stopStream(true)}> |
| | |
| | | {translate("ai.drawer.send")} |
| | | </Button> |
| | | )} |
| | | </Stack> |
| | | </Stack> |
| | | </Box> |
| | | </Box> |
| | |
| | | useTranslate, |
| | | } from "react-admin"; |
| | | import { Alert, Button, Grid, Stack, Typography } from "@mui/material"; |
| | | import StatusSelectInput from "@/page/components/StatusSelectInput"; |
| | | import { validateAiParamDraft } from "@/api/ai/configCenter"; |
| | | |
| | | const providerChoices = [ |
| | |
| | | <BooleanInput source="streamingEnabled" label="ai.param.fields.streamingEnabled" disabled={readOnly} /> |
| | | </Grid> |
| | | <Grid item xs={12} md={6}> |
| | | <StatusSelectInput disabled={readOnly} /> |
| | | <SelectInput |
| | | source="status" |
| | | label="ai.param.fields.defaultStatus" |
| | | choices={[ |
| | | { id: 1, name: "ai.param.status.default" }, |
| | | { id: 0, name: "ai.param.status.nonDefault" }, |
| | | ]} |
| | | fullWidth |
| | | disabled={readOnly} |
| | | /> |
| | | </Grid> |
| | | <Grid item xs={12}> |
| | | <TextInput source="memo" label="common.field.memo" fullWidth multiline minRows={3} disabled={readOnly} /> |
| | |
| | | import DeleteOutlineOutlinedIcon from "@mui/icons-material/DeleteOutlineOutlined"; |
| | | import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; |
| | | import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined"; |
| | | import CheckCircleOutlineRoundedIcon from "@mui/icons-material/CheckCircleOutlineRounded"; |
| | | import MyExportButton from "@/page/components/MyExportButton"; |
| | | import AiParamForm from "./AiParamForm"; |
| | | import AiConfigDialog from "../aiShared/AiConfigDialog"; |
| | | import AiRuntimeSummary from "../aiShared/AiRuntimeSummary"; |
| | | import { setAiParamDefault } from "@/api/ai/configCenter"; |
| | | |
| | | const filters = [ |
| | | <SearchInput source="condition" alwaysOn />, |
| | |
| | | <TextInput source="model" label="ai.param.fields.model" />, |
| | | <SelectInput |
| | | source="status" |
| | | label="common.field.status" |
| | | label="ai.param.fields.defaultStatus" |
| | | choices={[ |
| | | { id: "1", name: "common.enums.statusTrue" }, |
| | | { id: "0", name: "common.enums.statusFalse" }, |
| | | { id: "1", name: "ai.param.status.default" }, |
| | | { id: "0", name: "ai.param.status.nonDefault" }, |
| | | ]} |
| | | />, |
| | | ]; |
| | |
| | | return value.length > max ? `${value.slice(0, max)}...` : value; |
| | | }; |
| | | |
| | | const AiParamCards = ({ onView, onEdit, onDelete, deleting }) => { |
| | | const AiParamCards = ({ onView, onEdit, onDelete, onSetDefault, updatingDefaultId, deleting }) => { |
| | | const translate = useTranslate(); |
| | | const { data, isLoading } = useListContext(); |
| | | const records = useMemo(() => (Array.isArray(data) ? data : []), [data]); |
| | |
| | | <Chip |
| | | size="small" |
| | | color={record.statusBool ? "success" : "default"} |
| | | label={translate(record.statusBool ? "ai.common.enabled" : "ai.common.disabled")} |
| | | label={translate(record.statusBool ? "ai.param.status.default" : "ai.param.status.nonDefault")} |
| | | /> |
| | | </Stack> |
| | | <Stack direction="row" spacing={1} flexWrap="wrap" useFlexGap mt={1.5}> |
| | |
| | | <Button size="small" startIcon={<EditOutlinedIcon />} onClick={() => onEdit(record.id)}> |
| | | {translate("common.button.edit")} |
| | | </Button> |
| | | <Button |
| | | size="small" |
| | | color={record.statusBool ? "success" : "primary"} |
| | | startIcon={<CheckCircleOutlineRoundedIcon />} |
| | | onClick={() => onSetDefault(record)} |
| | | disabled={deleting || updatingDefaultId === record.id || record.statusBool} |
| | | > |
| | | {translate(record.statusBool ? "ai.param.actions.currentDefault" : "ai.param.actions.setDefault")} |
| | | </Button> |
| | | </Stack> |
| | | <Button |
| | | size="small" |
| | |
| | | const refresh = useRefresh(); |
| | | const [deleteOne, { isPending: deleting }] = useDelete(); |
| | | const [dialogState, setDialogState] = useState({ open: false, mode: "create", recordId: null }); |
| | | const [updatingDefaultId, setUpdatingDefaultId] = useState(null); |
| | | |
| | | const openDialog = (mode, recordId = null) => setDialogState({ open: true, mode, recordId }); |
| | | const closeDialog = () => setDialogState({ open: false, mode: "create", recordId: null }); |
| | |
| | | }, |
| | | } |
| | | ); |
| | | }; |
| | | |
| | | const handleSetDefault = async (record) => { |
| | | if (!record?.id || record.statusBool) { |
| | | return; |
| | | } |
| | | setUpdatingDefaultId(record.id); |
| | | try { |
| | | await setAiParamDefault(record.id); |
| | | notify(translate("ai.param.actions.setDefaultSuccess")); |
| | | refresh(); |
| | | } catch (error) { |
| | | notify(error?.message || translate("ai.param.actions.setDefaultFailed"), { type: "error" }); |
| | | } finally { |
| | | setUpdatingDefaultId(null); |
| | | } |
| | | }; |
| | | |
| | | const dialogTitle = { |
| | |
| | | onView={(id) => openDialog("show", id)} |
| | | onEdit={(id) => openDialog("edit", id)} |
| | | onDelete={handleDelete} |
| | | onSetDefault={handleSetDefault} |
| | | updatingDefaultId={updatingDefaultId} |
| | | deleting={deleting} |
| | | /> |
| | | </List> |
| | |
| | | executor.initialize(); |
| | | return executor; |
| | | } |
| | | |
| | | @Bean(name = "aiMemoryTaskExecutor") |
| | | public Executor aiMemoryTaskExecutor() { |
| | | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); |
| | | executor.setCorePoolSize(2); |
| | | executor.setMaxPoolSize(4); |
| | | executor.setQueueCapacity(200); |
| | | executor.setThreadNamePrefix("ai-memory-"); |
| | | executor.setWaitForTasksToCompleteOnShutdown(true); |
| | | executor.setAwaitTerminationSeconds(30); |
| | | executor.initialize(); |
| | | return executor; |
| | | } |
| | | } |
| | |
| | | public static final int MEMORY_SUMMARY_TRIGGER_MESSAGES = 12; |
| | | public static final int MEMORY_SUMMARY_MAX_LENGTH = 1200; |
| | | public static final int MEMORY_FACTS_MAX_LENGTH = 600; |
| | | public static final int CONFIG_CACHE_TTL_SECONDS = 300; |
| | | public static final int RUNTIME_CACHE_TTL_SECONDS = 120; |
| | | public static final int MEMORY_CACHE_TTL_SECONDS = 120; |
| | | public static final int SESSION_LIST_CACHE_TTL_SECONDS = 120; |
| | | public static final int MCP_PREVIEW_CACHE_TTL_SECONDS = 300; |
| | | public static final int MCP_HEALTH_CACHE_TTL_SECONDS = 120; |
| | | public static final int STREAM_STATE_TTL_SECONDS = 3600; |
| | | public static final int TOOL_RESULT_CACHE_TTL_SECONDS = 180; |
| | | public static final int CHAT_RATE_LIMIT_WINDOW_SECONDS = 60; |
| | | public static final int CHAT_RATE_LIMIT_MAX_REQUESTS = 30; |
| | | } |
| | |
| | | @PreAuthorize("isAuthenticated()") |
| | | @GetMapping("/ai/chat/runtime") |
| | | public R runtime(@RequestParam(required = false) String promptCode, |
| | | @RequestParam(required = false) Long sessionId) { |
| | | return R.ok().add(aiChatService.getRuntime(promptCode, sessionId, getLoginUserId(), getTenantId())); |
| | | @RequestParam(required = false) Long sessionId, |
| | | @RequestParam(required = false) Long aiParamId) { |
| | | return R.ok().add(aiChatService.getRuntime(promptCode, sessionId, aiParamId, getLoginUserId(), getTenantId())); |
| | | } |
| | | |
| | | /** |
| | |
| | | ? request.getRequestId().trim() |
| | | : UUID.randomUUID().toString().replace("-", ""); |
| | | request.setRequestId(requestId); |
| | | log.info("AI chat request accepted, requestId={}, userId={}, tenantId={}, sessionId={}", |
| | | requestId, getLoginUserId(), getTenantId(), request.getSessionId()); |
| | | log.info("AI chat request accepted, requestId={}, userId={}, tenantId={}, sessionId={}, aiParamId={}", |
| | | requestId, getLoginUserId(), getTenantId(), request.getSessionId(), request.getAiParamId()); |
| | | return aiChatService.stream(request, getLoginUserId(), getTenantId()); |
| | | } |
| | | } |
| | |
| | | return R.ok("Update Success").add(aiParam); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('system:aiParam:update')") |
| | | @OperationLog("Set Default AiParam") |
| | | @PostMapping("/aiParam/set-default/{id}") |
| | | public R setDefault(@PathVariable Long id) { |
| | | return R.ok("Update Success").add(aiParamService.setDefaultParam(id, getTenantId(), getLoginUserId())); |
| | | } |
| | | |
| | | @PreAuthorize("hasAuthority('system:aiParam:remove')") |
| | | @OperationLog("Delete AiParam") |
| | | @PostMapping("/aiParam/remove/{ids}") |
| New file |
| | |
| | | package com.vincent.rsf.server.ai.dto; |
| | | |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | |
| | | @Data |
| | | @Builder |
| | | public class AiChatModelOptionDto { |
| | | |
| | | private Long aiParamId; |
| | | |
| | | private String name; |
| | | |
| | | private String model; |
| | | |
| | | private String providerType; |
| | | |
| | | private Boolean active; |
| | | } |
| | |
| | | |
| | | private Long sessionId; |
| | | |
| | | private Long aiParamId; |
| | | |
| | | private List<AiChatMessageDto> messages; |
| | | |
| | | private String promptCode; |
| | |
| | | |
| | | private Long sessionId; |
| | | |
| | | private Long aiParamId; |
| | | |
| | | private String promptCode; |
| | | |
| | | private String promptName; |
| | | |
| | | private String model; |
| | | |
| | | private List<AiChatModelOptionDto> modelOptions; |
| | | |
| | | private Integer configuredMcpCount; |
| | | |
| | | private Integer mountedMcpCount; |
| | |
| | | |
| | | public interface AiChatService { |
| | | |
| | | AiChatRuntimeDto getRuntime(String promptCode, Long sessionId, Long userId, Long tenantId); |
| | | AiChatRuntimeDto getRuntime(String promptCode, Long sessionId, Long aiParamId, Long userId, Long tenantId); |
| | | |
| | | List<AiChatSessionDto> listSessions(String promptCode, String keyword, Long userId, Long tenantId); |
| | | |
| | |
| | | public interface AiConfigResolverService { |
| | | |
| | | AiResolvedConfig resolve(String promptCode, Long tenantId); |
| | | |
| | | AiResolvedConfig resolve(String promptCode, Long tenantId, Long aiParamId); |
| | | } |
| | |
| | | package com.vincent.rsf.server.ai.service; |
| | | |
| | | import com.baomidou.mybatisplus.extension.service.IService; |
| | | import com.vincent.rsf.server.ai.dto.AiChatModelOptionDto; |
| | | import com.vincent.rsf.server.ai.dto.AiParamValidateResultDto; |
| | | import com.vincent.rsf.server.ai.entity.AiParam; |
| | | |
| | | import java.util.List; |
| | | |
| | | public interface AiParamService extends IService<AiParam> { |
| | | |
| | | AiParam getActiveParam(Long tenantId); |
| | | |
| | | AiParam getChatParam(Long tenantId, Long aiParamId); |
| | | |
| | | List<AiChatModelOptionDto> listChatModelOptions(Long tenantId); |
| | | |
| | | AiParam setDefaultParam(Long id, Long tenantId, Long userId); |
| | | |
| | | void validateBeforeSave(AiParam aiParam, Long tenantId); |
| | | |
| | | void validateBeforeUpdate(AiParam aiParam, Long tenantId); |
| | |
| | | private static final Pattern BEARER_PATTERN = Pattern.compile("(?i)(bearer\\s+)([a-z0-9._-]+)"); |
| | | |
| | | private final AiMcpCallLogMapper aiMcpCallLogMapper; |
| | | private final AiRedisSupport aiRedisSupport; |
| | | |
| | | @Override |
| | | public AiCallLog startCallLog(String requestId, Long sessionId, Long userId, Long tenantId, String promptCode, |
| | |
| | | .setCreateTime(now) |
| | | .setUpdateTime(now); |
| | | this.save(callLog); |
| | | aiRedisSupport.recordObserveCallStarted(tenantId); |
| | | return callLog; |
| | | } |
| | | |
| | |
| | | .set(AiCallLog::getToolFailureCount, (int) toolFailureCount) |
| | | .set(AiCallLog::getToolCallCount, (int) (toolSuccessCount + toolFailureCount)) |
| | | .set(AiCallLog::getUpdateTime, new Date())); |
| | | AiCallLog latest = this.getById(callLogId); |
| | | if (latest != null) { |
| | | aiRedisSupport.recordObserveCallFinished(latest.getTenantId(), status, elapsedMs, firstTokenLatencyMs, totalTokens); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | .set(AiCallLog::getToolFailureCount, (int) toolFailureCount) |
| | | .set(AiCallLog::getToolCallCount, (int) (toolSuccessCount + toolFailureCount)) |
| | | .set(AiCallLog::getUpdateTime, new Date())); |
| | | AiCallLog latest = this.getById(callLogId); |
| | | if (latest != null) { |
| | | aiRedisSupport.recordObserveCallFinished(latest.getTenantId(), status, elapsedMs, firstTokenLatencyMs, null); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | |
| | | .setUserId(userId) |
| | | .setTenantId(tenantId) |
| | | .setCreateTime(new Date())); |
| | | aiRedisSupport.recordObserveToolCall(tenantId, toolName, status); |
| | | } |
| | | |
| | | @Override |
| | | public AiObserveStatsDto getObserveStats(Long tenantId) { |
| | | return aiRedisSupport.getObserveStats(tenantId, () -> loadObserveStatsFromDatabase(tenantId)); |
| | | } |
| | | |
| | | private AiObserveStatsDto loadObserveStatsFromDatabase(Long tenantId) { |
| | | // 数据库聚合只作为 Redis 冷启动兜底,正常情况下看板应直接消费实时计数。 |
| | | List<AiCallLog> callLogs = this.list(new LambdaQueryWrapper<AiCallLog>() |
| | | .eq(AiCallLog::getTenantId, tenantId) |
| | | .eq(AiCallLog::getDeleted, 0) |
| | |
| | | import com.vincent.rsf.server.ai.service.AiChatMemoryService; |
| | | import com.vincent.rsf.server.system.enums.StatusType; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.beans.factory.annotation.Qualifier; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | import java.util.concurrent.Executor; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | |
| | | @Service |
| | | @Slf4j |
| | | @RequiredArgsConstructor |
| | | public class AiChatMemoryServiceImpl implements AiChatMemoryService { |
| | | |
| | | private final AiChatSessionMapper aiChatSessionMapper; |
| | | private final AiChatMessageMapper aiChatMessageMapper; |
| | | private final AiRedisSupport aiRedisSupport; |
| | | @Qualifier("aiMemoryTaskExecutor") |
| | | private final Executor aiMemoryTaskExecutor; |
| | | |
| | | /** |
| | | * 用两个本地集合把“同一个会话的摘要刷新”合并成串行任务,避免连续消息把重复任务塞满线程池。 |
| | | */ |
| | | private final Set<Long> refreshingSessionIds = ConcurrentHashMap.newKeySet(); |
| | | private final Set<Long> pendingRefreshSessionIds = ConcurrentHashMap.newKeySet(); |
| | | |
| | | /** |
| | | * 读取会话记忆快照。 |
| | |
| | | public AiChatMemoryDto getMemory(Long userId, Long tenantId, String promptCode, Long sessionId) { |
| | | ensureIdentity(userId, tenantId); |
| | | String resolvedPromptCode = requirePromptCode(promptCode); |
| | | // 会话记忆属于典型“读多写少”数据,先走短 TTL 缓存能明显减轻抽屉初始化和切会话压力。 |
| | | AiChatMemoryDto cached = aiRedisSupport.getMemory(tenantId, userId, resolvedPromptCode, sessionId); |
| | | if (cached != null) { |
| | | return cached; |
| | | } |
| | | AiChatSession session = sessionId == null |
| | | ? findLatestSession(userId, tenantId, resolvedPromptCode) |
| | | : getSession(sessionId, userId, tenantId, resolvedPromptCode); |
| | | AiChatMemoryDto memory; |
| | | if (session == null) { |
| | | return AiChatMemoryDto.builder() |
| | | memory = AiChatMemoryDto.builder() |
| | | .sessionId(null) |
| | | .memorySummary(null) |
| | | .memoryFacts(null) |
| | |
| | | .persistedMessages(List.of()) |
| | | .shortMemoryMessages(List.of()) |
| | | .build(); |
| | | aiRedisSupport.cacheMemory(tenantId, userId, resolvedPromptCode, sessionId, memory); |
| | | return memory; |
| | | } |
| | | List<AiChatMessageDto> persistedMessages = listMessages(session.getId()); |
| | | List<AiChatMessageDto> shortMemoryMessages = tailMessagesByRounds(persistedMessages, AiDefaults.MEMORY_RECENT_ROUNDS); |
| | | return AiChatMemoryDto.builder() |
| | | memory = AiChatMemoryDto.builder() |
| | | .sessionId(session.getId()) |
| | | .memorySummary(session.getMemorySummary()) |
| | | .memoryFacts(session.getMemoryFacts()) |
| | |
| | | .persistedMessages(persistedMessages) |
| | | .shortMemoryMessages(shortMemoryMessages) |
| | | .build(); |
| | | aiRedisSupport.cacheMemory(tenantId, userId, resolvedPromptCode, session.getId(), memory); |
| | | if (sessionId == null || !session.getId().equals(sessionId)) { |
| | | aiRedisSupport.cacheMemory(tenantId, userId, resolvedPromptCode, null, memory); |
| | | } |
| | | return memory; |
| | | } |
| | | |
| | | /** |
| | |
| | | public List<AiChatSessionDto> listSessions(Long userId, Long tenantId, String promptCode, String keyword) { |
| | | ensureIdentity(userId, tenantId); |
| | | String resolvedPromptCode = requirePromptCode(promptCode); |
| | | List<AiChatSessionDto> cached = aiRedisSupport.getSessionList(tenantId, userId, resolvedPromptCode, keyword); |
| | | if (cached != null) { |
| | | return cached; |
| | | } |
| | | List<AiChatSession> sessions = aiChatSessionMapper.selectList(new LambdaQueryWrapper<AiChatSession>() |
| | | .eq(AiChatSession::getUserId, userId) |
| | | .eq(AiChatSession::getTenantId, tenantId) |
| | |
| | | .orderByDesc(AiChatSession::getLastMessageTime) |
| | | .orderByDesc(AiChatSession::getId)); |
| | | if (Cools.isEmpty(sessions)) { |
| | | aiRedisSupport.cacheSessionList(tenantId, userId, resolvedPromptCode, keyword, List.of()); |
| | | return List.of(); |
| | | } |
| | | List<AiChatSessionDto> result = new ArrayList<>(); |
| | | for (AiChatSession session : sessions) { |
| | | result.add(buildSessionDto(session)); |
| | | } |
| | | aiRedisSupport.cacheSessionList(tenantId, userId, resolvedPromptCode, keyword, result); |
| | | return result; |
| | | } |
| | | |
| | |
| | | .setUpdateBy(userId) |
| | | .setUpdateTime(now); |
| | | aiChatSessionMapper.insert(session); |
| | | evictConversationCaches(tenantId, userId); |
| | | return session; |
| | | } |
| | | |
| | | /** |
| | | * 落库保存一整轮对话。 |
| | | * 这里会顺序写入本轮用户消息和模型回复,并在最后刷新会话标题、最后活跃时间和记忆画像。 |
| | | * 这里会顺序写入本轮用户消息和模型回复,并在最后刷新会话标题和活跃时间。 |
| | | * 记忆画像改为后台异步刷新,避免把摘要重算耗时压在用户本轮响应尾部。 |
| | | */ |
| | | @Override |
| | | public void saveRound(AiChatSession session, Long userId, Long tenantId, List<AiChatMessageDto> memoryMessages, String assistantContent) { |
| | |
| | | .setUpdateBy(userId) |
| | | .setUpdateTime(now); |
| | | aiChatSessionMapper.updateById(update); |
| | | refreshMemoryProfile(session.getId(), userId); |
| | | evictConversationCaches(tenantId, userId); |
| | | scheduleMemoryProfileRefresh(session.getId(), userId, tenantId); |
| | | } |
| | | |
| | | /** 删除整个会话及其消息。 */ |
| | |
| | | .setDeleted(1); |
| | | aiChatMessageMapper.updateById(updateMessage); |
| | | } |
| | | evictConversationCaches(tenantId, userId); |
| | | } |
| | | |
| | | /** 更新会话标题并返回最新会话摘要。 */ |
| | |
| | | .setUpdateBy(userId) |
| | | .setUpdateTime(now); |
| | | aiChatSessionMapper.updateById(update); |
| | | return buildSessionDto(requireOwnedSession(sessionId, userId, tenantId)); |
| | | AiChatSessionDto sessionDto = buildSessionDto(requireOwnedSession(sessionId, userId, tenantId)); |
| | | evictConversationCaches(tenantId, userId); |
| | | return sessionDto; |
| | | } |
| | | |
| | | /** 更新会话置顶状态。 */ |
| | |
| | | .setUpdateBy(userId) |
| | | .setUpdateTime(now); |
| | | aiChatSessionMapper.updateById(update); |
| | | return buildSessionDto(requireOwnedSession(sessionId, userId, tenantId)); |
| | | AiChatSessionDto sessionDto = buildSessionDto(requireOwnedSession(sessionId, userId, tenantId)); |
| | | evictConversationCaches(tenantId, userId); |
| | | return sessionDto; |
| | | } |
| | | |
| | | /** 清空某个会话的全部消息和派生记忆字段。 */ |
| | |
| | | .setUpdateBy(userId) |
| | | .setUpdateTime(new Date()) |
| | | .setLastMessageTime(session.getCreateTime())); |
| | | evictConversationCaches(tenantId, userId); |
| | | } |
| | | |
| | | /** 只保留最近一轮问答,用于手动裁剪长会话。 */ |
| | |
| | | .setDeleted(1)); |
| | | } |
| | | } |
| | | refreshMemoryProfile(sessionId, userId); |
| | | evictConversationCaches(tenantId, userId); |
| | | scheduleMemoryProfileRefresh(sessionId, userId, tenantId); |
| | | } |
| | | |
| | | private void evictConversationCaches(Long tenantId, Long userId) { |
| | | // 会话标题、摘要、最近消息和 runtime 都会互相影响,统一按用户维度一起失效更稳妥。 |
| | | aiRedisSupport.evictUserConversationCaches(tenantId, userId); |
| | | } |
| | | |
| | | private void scheduleMemoryProfileRefresh(Long sessionId, Long userId, Long tenantId) { |
| | | if (sessionId == null) { |
| | | return; |
| | | } |
| | | if (!refreshingSessionIds.add(sessionId)) { |
| | | pendingRefreshSessionIds.add(sessionId); |
| | | return; |
| | | } |
| | | aiMemoryTaskExecutor.execute(() -> runMemoryProfileRefreshLoop(sessionId, userId, tenantId)); |
| | | } |
| | | |
| | | private void runMemoryProfileRefreshLoop(Long sessionId, Long userId, Long tenantId) { |
| | | try { |
| | | boolean shouldContinue; |
| | | do { |
| | | pendingRefreshSessionIds.remove(sessionId); |
| | | try { |
| | | refreshMemoryProfile(sessionId, userId); |
| | | evictConversationCaches(tenantId, userId); |
| | | } catch (Exception e) { |
| | | log.warn("AI memory profile refresh failed, sessionId={}, userId={}, tenantId={}, message={}", |
| | | sessionId, userId, tenantId, e.getMessage(), e); |
| | | } |
| | | shouldContinue = pendingRefreshSessionIds.remove(sessionId); |
| | | } while (shouldContinue); |
| | | } finally { |
| | | refreshingSessionIds.remove(sessionId); |
| | | if (pendingRefreshSessionIds.remove(sessionId) && refreshingSessionIds.add(sessionId)) { |
| | | aiMemoryTaskExecutor.execute(() -> runMemoryProfileRefreshLoop(sessionId, userId, tenantId)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | private AiChatSession findLatestSession(Long userId, Long tenantId, String promptCode) { |
| | |
| | | /** |
| | | * 重新计算会话的摘要记忆和关键事实。 |
| | | * 这是“持久化消息”和“模型上下文治理”之间的桥梁方法。 |
| | | * 现在它运行在后台线程里,因此允许短时间最终一致,而不是强制本轮同步完成。 |
| | | */ |
| | | List<AiChatMessageDto> messages = listMessages(sessionId); |
| | | List<AiChatMessageDto> shortMemoryMessages = tailMessagesByRounds(messages, AiDefaults.MEMORY_RECENT_ROUNDS); |
| | |
| | | import com.vincent.rsf.server.ai.dto.AiChatErrorDto; |
| | | import com.vincent.rsf.server.ai.dto.AiChatMemoryDto; |
| | | import com.vincent.rsf.server.ai.dto.AiChatMessageDto; |
| | | import com.vincent.rsf.server.ai.dto.AiChatModelOptionDto; |
| | | import com.vincent.rsf.server.ai.dto.AiChatRequest; |
| | | import com.vincent.rsf.server.ai.dto.AiChatRuntimeDto; |
| | | import com.vincent.rsf.server.ai.dto.AiChatStatusDto; |
| | |
| | | import com.vincent.rsf.server.ai.service.AiChatService; |
| | | import com.vincent.rsf.server.ai.service.AiChatMemoryService; |
| | | import com.vincent.rsf.server.ai.service.AiConfigResolverService; |
| | | import com.vincent.rsf.server.ai.service.AiParamService; |
| | | import com.vincent.rsf.server.ai.service.MountedToolCallback; |
| | | import com.vincent.rsf.server.ai.service.McpMountRuntimeFactory; |
| | | import io.micrometer.observation.ObservationRegistry; |
| | |
| | | |
| | | private final AiConfigResolverService aiConfigResolverService; |
| | | private final AiChatMemoryService aiChatMemoryService; |
| | | private final AiParamService aiParamService; |
| | | private final McpMountRuntimeFactory mcpMountRuntimeFactory; |
| | | private final AiCallLogService aiCallLogService; |
| | | private final AiRedisSupport aiRedisSupport; |
| | | private final GenericApplicationContext applicationContext; |
| | | private final ObservationRegistry observationRegistry; |
| | | private final ObjectMapper objectMapper; |
| | |
| | | * 该方法不会触发模型调用,而是把配置解析结果和会话记忆聚合成前端一次渲染所需的快照。 |
| | | */ |
| | | @Override |
| | | public AiChatRuntimeDto getRuntime(String promptCode, Long sessionId, Long userId, Long tenantId) { |
| | | AiResolvedConfig config = aiConfigResolverService.resolve(promptCode, tenantId); |
| | | public AiChatRuntimeDto getRuntime(String promptCode, Long sessionId, Long aiParamId, Long userId, Long tenantId) { |
| | | AiResolvedConfig config = aiConfigResolverService.resolve(promptCode, tenantId, aiParamId); |
| | | Long runtimeCacheAiParamId = aiParamId; |
| | | // runtime 是配置快照和会话记忆的聚合视图,单独缓存能减少一次页面进入时的重复拼装。 |
| | | AiChatRuntimeDto cached = aiRedisSupport.getRuntime(tenantId, userId, config.getPromptCode(), sessionId, runtimeCacheAiParamId); |
| | | if (cached != null) { |
| | | return cached; |
| | | } |
| | | AiChatMemoryDto memory = aiChatMemoryService.getMemory(userId, tenantId, config.getPromptCode(), sessionId); |
| | | return AiChatRuntimeDto.builder() |
| | | .requestId(null) |
| | | .sessionId(memory.getSessionId()) |
| | | .promptCode(config.getPromptCode()) |
| | | .promptName(config.getPrompt().getName()) |
| | | .model(config.getAiParam().getModel()) |
| | | .configuredMcpCount(config.getMcpMounts().size()) |
| | | .mountedMcpCount(config.getMcpMounts().size()) |
| | | .mountedMcpNames(config.getMcpMounts().stream().map(item -> item.getName()).toList()) |
| | | .mountErrors(List.of()) |
| | | .memorySummary(memory.getMemorySummary()) |
| | | .memoryFacts(memory.getMemoryFacts()) |
| | | .recentMessageCount(memory.getRecentMessageCount()) |
| | | .persistedMessages(memory.getPersistedMessages()) |
| | | .build(); |
| | | List<AiChatModelOptionDto> modelOptions = aiParamService.listChatModelOptions(tenantId); |
| | | AiChatRuntimeDto runtime = buildRuntimeSnapshot( |
| | | null, |
| | | memory.getSessionId(), |
| | | config, |
| | | modelOptions, |
| | | config.getMcpMounts().size(), |
| | | config.getMcpMounts().stream().map(item -> item.getName()).toList(), |
| | | List.of(), |
| | | memory |
| | | ); |
| | | aiRedisSupport.cacheRuntime(tenantId, userId, config.getPromptCode(), sessionId, runtimeCacheAiParamId, runtime); |
| | | if (memory.getSessionId() != null && !Objects.equals(memory.getSessionId(), sessionId)) { |
| | | aiRedisSupport.cacheRuntime(tenantId, userId, config.getPromptCode(), memory.getSessionId(), runtimeCacheAiParamId, runtime); |
| | | } |
| | | return runtime; |
| | | } |
| | | |
| | | /** |
| | |
| | | Long sessionId = request.getSessionId(); |
| | | Long callLogId = null; |
| | | String model = null; |
| | | String resolvedPromptCode = request.getPromptCode(); |
| | | ThinkingTraceEmitter thinkingTraceEmitter = null; |
| | | try { |
| | | ensureIdentity(userId, tenantId); |
| | | AiResolvedConfig config = resolveConfig(request, tenantId); |
| | | List<AiChatModelOptionDto> modelOptions = aiParamService.listChatModelOptions(tenantId); |
| | | resolvedPromptCode = config.getPromptCode(); |
| | | if (!aiRedisSupport.allowChatRequest(tenantId, userId, config.getPromptCode())) { |
| | | throw buildAiException("AI_RATE_LIMITED", AiErrorCategory.REQUEST, "RATE_LIMIT", |
| | | "当前提问过于频繁,请稍后再试", null); |
| | | } |
| | | final String resolvedModel = config.getAiParam().getModel(); |
| | | model = resolvedModel; |
| | | AiChatSession session = resolveSession(request, userId, tenantId, config.getPromptCode()); |
| | | sessionId = session.getId(); |
| | | // 流状态落 Redis 的目标是给多实例和后续运维查询留统一入口,不替代数据库日志。 |
| | | aiRedisSupport.markStreamState(requestId, tenantId, userId, sessionId, config.getPromptCode(), "RUNNING", null); |
| | | AiChatMemoryDto memory = loadMemory(userId, tenantId, config.getPromptCode(), session.getId()); |
| | | List<AiChatMessageDto> mergedMessages = mergeMessages(memory.getShortMemoryMessages(), request.getMessages()); |
| | | AiCallLog callLog = aiCallLogService.startCallLog( |
| | |
| | | ); |
| | | callLogId = callLog.getId(); |
| | | try (McpMountRuntimeFactory.McpMountRuntime runtime = createRuntime(config, userId)) { |
| | | emitStrict(emitter, "start", AiChatRuntimeDto.builder() |
| | | .requestId(requestId) |
| | | .sessionId(session.getId()) |
| | | .promptCode(config.getPromptCode()) |
| | | .promptName(config.getPrompt().getName()) |
| | | .model(config.getAiParam().getModel()) |
| | | .configuredMcpCount(config.getMcpMounts().size()) |
| | | .mountedMcpCount(runtime.getMountedCount()) |
| | | .mountedMcpNames(runtime.getMountedNames()) |
| | | .mountErrors(runtime.getErrors()) |
| | | .memorySummary(memory.getMemorySummary()) |
| | | .memoryFacts(memory.getMemoryFacts()) |
| | | .recentMessageCount(memory.getRecentMessageCount()) |
| | | .persistedMessages(memory.getPersistedMessages()) |
| | | .build()); |
| | | emitStrict(emitter, "start", buildRuntimeSnapshot( |
| | | requestId, |
| | | session.getId(), |
| | | config, |
| | | modelOptions, |
| | | runtime.getMountedCount(), |
| | | runtime.getMountedNames(), |
| | | runtime.getErrors(), |
| | | memory |
| | | )); |
| | | emitSafely(emitter, "status", AiChatStatusDto.builder() |
| | | .requestId(requestId) |
| | | .sessionId(session.getId()) |
| | |
| | | toolSuccessCount.get(), |
| | | toolFailureCount.get() |
| | | ); |
| | | aiRedisSupport.markStreamState(requestId, tenantId, userId, session.getId(), config.getPromptCode(), "COMPLETED", null); |
| | | log.info("AI chat completed, requestId={}, sessionId={}, elapsedMs={}, firstTokenLatencyMs={}", |
| | | requestId, session.getId(), System.currentTimeMillis() - startedAt, resolveFirstTokenLatency(startedAt, firstTokenAtRef.get())); |
| | | emitter.complete(); |
| | |
| | | toolSuccessCount.get(), |
| | | toolFailureCount.get() |
| | | ); |
| | | aiRedisSupport.markStreamState(requestId, tenantId, userId, session.getId(), config.getPromptCode(), "COMPLETED", null); |
| | | log.info("AI chat completed, requestId={}, sessionId={}, elapsedMs={}, firstTokenLatencyMs={}", |
| | | requestId, session.getId(), System.currentTimeMillis() - startedAt, resolveFirstTokenLatency(startedAt, firstTokenAtRef.get())); |
| | | emitter.complete(); |
| | | } |
| | | } catch (AiChatException e) { |
| | | handleStreamFailure(emitter, requestId, sessionId, model, startedAt, firstTokenAtRef.get(), e, |
| | | callLogId, toolSuccessCount.get(), toolFailureCount.get(), thinkingTraceEmitter); |
| | | callLogId, toolSuccessCount.get(), toolFailureCount.get(), thinkingTraceEmitter, |
| | | tenantId, userId, resolvedPromptCode); |
| | | } catch (Exception e) { |
| | | handleStreamFailure(emitter, requestId, sessionId, model, startedAt, firstTokenAtRef.get(), |
| | | buildAiException("AI_INTERNAL_ERROR", AiErrorCategory.INTERNAL, "INTERNAL", |
| | | e == null ? "AI 对话失败" : e.getMessage(), e), |
| | | callLogId, toolSuccessCount.get(), toolFailureCount.get(), thinkingTraceEmitter); |
| | | callLogId, toolSuccessCount.get(), toolFailureCount.get(), thinkingTraceEmitter, |
| | | tenantId, userId, resolvedPromptCode); |
| | | } finally { |
| | | log.debug("AI chat stream finished, requestId={}", requestId); |
| | | } |
| | |
| | | private AiResolvedConfig resolveConfig(AiChatRequest request, Long tenantId) { |
| | | /** 把请求里的 Prompt 场景解析成一份可直接执行的 AI 配置。 */ |
| | | try { |
| | | return aiConfigResolverService.resolve(request.getPromptCode(), tenantId); |
| | | return aiConfigResolverService.resolve(request.getPromptCode(), tenantId, request.getAiParamId()); |
| | | } catch (Exception e) { |
| | | throw buildAiException("AI_CONFIG_RESOLVE_ERROR", AiErrorCategory.CONFIG, "CONFIG_RESOLVE", |
| | | e == null ? "AI 配置解析失败" : e.getMessage(), e); |
| | | } |
| | | } |
| | | |
| | | private AiChatRuntimeDto buildRuntimeSnapshot(String requestId, Long sessionId, AiResolvedConfig config, |
| | | List<AiChatModelOptionDto> modelOptions, Integer mountedMcpCount, |
| | | List<String> mountedMcpNames, List<String> mountErrors, |
| | | AiChatMemoryDto memory) { |
| | | return AiChatRuntimeDto.builder() |
| | | .requestId(requestId) |
| | | .sessionId(sessionId) |
| | | .aiParamId(config.getAiParam().getId()) |
| | | .promptCode(config.getPromptCode()) |
| | | .promptName(config.getPrompt().getName()) |
| | | .model(config.getAiParam().getModel()) |
| | | .modelOptions(modelOptions) |
| | | .configuredMcpCount(config.getMcpMounts().size()) |
| | | .mountedMcpCount(mountedMcpCount) |
| | | .mountedMcpNames(mountedMcpNames) |
| | | .mountErrors(mountErrors) |
| | | .memorySummary(memory.getMemorySummary()) |
| | | .memoryFacts(memory.getMemoryFacts()) |
| | | .recentMessageCount(memory.getRecentMessageCount()) |
| | | .persistedMessages(memory.getPersistedMessages()) |
| | | .build(); |
| | | } |
| | | |
| | | private AiChatSession resolveSession(AiChatRequest request, Long userId, Long tenantId, String promptCode) { |
| | |
| | | private void handleStreamFailure(SseEmitter emitter, String requestId, Long sessionId, String model, long startedAt, |
| | | Long firstTokenAt, AiChatException exception, Long callLogId, |
| | | long toolSuccessCount, long toolFailureCount, |
| | | ThinkingTraceEmitter thinkingTraceEmitter) { |
| | | ThinkingTraceEmitter thinkingTraceEmitter, |
| | | Long tenantId, Long userId, String promptCode) { |
| | | if (isClientAbortException(exception)) { |
| | | log.warn("AI chat aborted by client, requestId={}, sessionId={}, stage={}, message={}", |
| | | requestId, sessionId, exception.getStage(), exception.getMessage()); |
| | |
| | | toolSuccessCount, |
| | | toolFailureCount |
| | | ); |
| | | aiRedisSupport.markStreamState(requestId, tenantId, userId, sessionId, promptCode, "ABORTED", exception.getMessage()); |
| | | emitter.completeWithError(exception); |
| | | return; |
| | | } |
| | |
| | | toolSuccessCount, |
| | | toolFailureCount |
| | | ); |
| | | aiRedisSupport.markStreamState(requestId, tenantId, userId, sessionId, promptCode, "FAILED", exception.getMessage()); |
| | | emitter.completeWithError(exception); |
| | | } |
| | | |
| | |
| | | String mountName = delegate instanceof MountedToolCallback ? ((MountedToolCallback) delegate).getMountName() : null; |
| | | String toolCallId = requestId + "-tool-" + toolCallSequence.incrementAndGet(); |
| | | long startedAt = System.currentTimeMillis(); |
| | | // 这里只对同一 request 内的重复工具调用做短期复用,避免把跨请求结果误当成通用缓存。 |
| | | AiRedisSupport.CachedToolResult cachedToolResult = aiRedisSupport.getToolResult(tenantId, requestId, toolName, toolInput); |
| | | if (cachedToolResult != null) { |
| | | emitSafely(emitter, "tool_result", AiChatToolEventDto.builder() |
| | | .requestId(requestId) |
| | | .sessionId(sessionId) |
| | | .toolCallId(toolCallId) |
| | | .toolName(toolName) |
| | | .mountName(mountName) |
| | | .status(cachedToolResult.isSuccess() ? "COMPLETED" : "FAILED") |
| | | .inputSummary(summarizeToolPayload(toolInput, 400)) |
| | | .outputSummary(summarizeToolPayload(cachedToolResult.getOutput(), 600)) |
| | | .errorMessage(cachedToolResult.getErrorMessage()) |
| | | .durationMs(0L) |
| | | .timestamp(System.currentTimeMillis()) |
| | | .build()); |
| | | if (thinkingTraceEmitter != null) { |
| | | thinkingTraceEmitter.onToolResult(toolName, toolCallId, !cachedToolResult.isSuccess()); |
| | | } |
| | | if (cachedToolResult.isSuccess()) { |
| | | toolSuccessCount.incrementAndGet(); |
| | | aiCallLogService.saveMcpCallLog(callLogId, requestId, sessionId, toolCallId, mountName, toolName, |
| | | "COMPLETED", summarizeToolPayload(toolInput, 400), summarizeToolPayload(cachedToolResult.getOutput(), 600), |
| | | null, 0L, userId, tenantId); |
| | | return cachedToolResult.getOutput(); |
| | | } |
| | | toolFailureCount.incrementAndGet(); |
| | | aiCallLogService.saveMcpCallLog(callLogId, requestId, sessionId, toolCallId, mountName, toolName, |
| | | "FAILED", summarizeToolPayload(toolInput, 400), null, cachedToolResult.getErrorMessage(), |
| | | 0L, userId, tenantId); |
| | | throw new CoolException(cachedToolResult.getErrorMessage()); |
| | | } |
| | | if (thinkingTraceEmitter != null) { |
| | | thinkingTraceEmitter.onToolStart(toolName, toolCallId); |
| | | } |
| | |
| | | if (thinkingTraceEmitter != null) { |
| | | thinkingTraceEmitter.onToolResult(toolName, toolCallId, false); |
| | | } |
| | | aiRedisSupport.cacheToolResult(tenantId, requestId, toolName, toolInput, true, output, null); |
| | | toolSuccessCount.incrementAndGet(); |
| | | aiCallLogService.saveMcpCallLog(callLogId, requestId, sessionId, toolCallId, mountName, toolName, |
| | | "COMPLETED", summarizeToolPayload(toolInput, 400), summarizeToolPayload(output, 600), |
| | |
| | | if (thinkingTraceEmitter != null) { |
| | | thinkingTraceEmitter.onToolResult(toolName, toolCallId, true); |
| | | } |
| | | aiRedisSupport.cacheToolResult(tenantId, requestId, toolName, toolInput, false, null, e.getMessage()); |
| | | toolFailureCount.incrementAndGet(); |
| | | aiCallLogService.saveMcpCallLog(callLogId, requestId, sessionId, toolCallId, mountName, toolName, |
| | | "FAILED", summarizeToolPayload(toolInput, 400), null, e.getMessage(), |
| | |
| | | import com.vincent.rsf.server.ai.dto.AiPromptPreviewDto; |
| | | import com.vincent.rsf.server.ai.dto.AiPromptPreviewRequest; |
| | | import com.vincent.rsf.server.ai.entity.AiMcpMount; |
| | | import com.vincent.rsf.server.ai.entity.AiParam; |
| | | import com.vincent.rsf.server.ai.entity.AiPrompt; |
| | | import com.vincent.rsf.server.ai.dto.AiResolvedConfig; |
| | | import com.vincent.rsf.server.ai.service.AiConfigOpsService; |
| | | import com.vincent.rsf.server.ai.service.AiConfigResolverService; |
| | | import com.vincent.rsf.server.ai.service.AiMcpMountService; |
| | | import com.vincent.rsf.server.ai.service.AiParamService; |
| | | import com.vincent.rsf.server.ai.service.AiPromptService; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | @RequiredArgsConstructor |
| | | public class AiConfigOpsServiceImpl implements AiConfigOpsService { |
| | | |
| | | private final AiParamService aiParamService; |
| | | private final AiPromptService aiPromptService; |
| | | private final AiMcpMountService aiMcpMountService; |
| | | private final AiConfigResolverService aiConfigResolverService; |
| | | private final AiPromptRenderSupport aiPromptRenderSupport; |
| | | private final AiPromptService aiPromptService; |
| | | |
| | | @Override |
| | | public AiConfigSummaryDto getSummary(String promptCode, Long tenantId) { |
| | | String finalPromptCode = StringUtils.hasText(promptCode) ? promptCode : AiDefaults.DEFAULT_PROMPT_CODE; |
| | | AiParam activeParam = aiParamService.getActiveParam(tenantId); |
| | | AiPrompt activePrompt = aiPromptService.getActivePrompt(finalPromptCode, tenantId); |
| | | List<AiMcpMount> mounts = aiMcpMountService.listActiveMounts(tenantId); |
| | | AiResolvedConfig resolvedConfig = aiConfigResolverService.resolve(finalPromptCode, tenantId); |
| | | List<AiMcpMount> mounts = resolvedConfig.getMcpMounts(); |
| | | return AiConfigSummaryDto.builder() |
| | | .promptCode(activePrompt.getCode()) |
| | | .promptName(activePrompt.getName()) |
| | | .promptScene(activePrompt.getScene()) |
| | | .activeParamName(activeParam.getName()) |
| | | .activeModel(activeParam.getModel()) |
| | | .activeParamUpdatedAt(activeParam.getUpdateTime$()) |
| | | .activeParamUpdatedBy(activeParam.getUpdateBy()) |
| | | .activeParamValidateStatus(activeParam.getValidateStatus()) |
| | | .activeParamValidateMessage(activeParam.getLastValidateMessage()) |
| | | .activeParamValidatedAt(activeParam.getLastValidateTime$()) |
| | | .promptCode(resolvedConfig.getPrompt().getCode()) |
| | | .promptName(resolvedConfig.getPrompt().getName()) |
| | | .promptScene(resolvedConfig.getPrompt().getScene()) |
| | | .activeParamName(resolvedConfig.getAiParam().getName()) |
| | | .activeModel(resolvedConfig.getAiParam().getModel()) |
| | | .activeParamUpdatedAt(resolvedConfig.getAiParam().getUpdateTime$()) |
| | | .activeParamUpdatedBy(resolvedConfig.getAiParam().getUpdateBy()) |
| | | .activeParamValidateStatus(resolvedConfig.getAiParam().getValidateStatus()) |
| | | .activeParamValidateMessage(resolvedConfig.getAiParam().getLastValidateMessage()) |
| | | .activeParamValidatedAt(resolvedConfig.getAiParam().getLastValidateTime$()) |
| | | .enabledMcpCount(mounts.size()) |
| | | .enabledMcpNames(mounts.stream().map(AiMcpMount::getName).toList()) |
| | | .activePromptUpdatedAt(activePrompt.getUpdateTime$()) |
| | | .activePromptUpdatedBy(activePrompt.getUpdateBy()) |
| | | .activePromptUpdatedAt(resolvedConfig.getPrompt().getUpdateTime$()) |
| | | .activePromptUpdatedBy(resolvedConfig.getPrompt().getUpdateBy()) |
| | | .build(); |
| | | } |
| | | |
| | |
| | | private final AiParamService aiParamService; |
| | | private final AiPromptService aiPromptService; |
| | | private final AiMcpMountService aiMcpMountService; |
| | | private final AiRedisSupport aiRedisSupport; |
| | | |
| | | /** |
| | | * 按租户解析一次完整的 AI 运行配置。 |
| | |
| | | */ |
| | | @Override |
| | | public AiResolvedConfig resolve(String promptCode, Long tenantId) { |
| | | return resolve(promptCode, tenantId, null); |
| | | } |
| | | |
| | | @Override |
| | | public AiResolvedConfig resolve(String promptCode, Long tenantId, Long aiParamId) { |
| | | if (tenantId == null) { |
| | | throw new CoolException("当前租户不存在"); |
| | | } |
| | | String finalPromptCode = StringUtils.hasText(promptCode) ? promptCode : AiDefaults.DEFAULT_PROMPT_CODE; |
| | | return AiResolvedConfig.builder() |
| | | // 配置解析是多个入口共享的热点路径,命中缓存时可以避免三张配置表的重复查询。 |
| | | AiResolvedConfig cached = aiRedisSupport.getResolvedConfig(tenantId, finalPromptCode, aiParamId); |
| | | if (cached != null) { |
| | | return cached; |
| | | } |
| | | AiResolvedConfig resolvedConfig = AiResolvedConfig.builder() |
| | | .promptCode(finalPromptCode) |
| | | .aiParam(aiParamService.getActiveParam(tenantId)) |
| | | .aiParam(aiParamService.getChatParam(tenantId, aiParamId)) |
| | | .prompt(aiPromptService.getActivePrompt(finalPromptCode, tenantId)) |
| | | .mcpMounts(aiMcpMountService.listActiveMounts(tenantId)) |
| | | .build(); |
| | | aiRedisSupport.cacheResolvedConfig(tenantId, finalPromptCode, aiParamId, resolvedConfig); |
| | | return resolvedConfig; |
| | | } |
| | | } |
| | |
| | | private final BuiltinMcpToolRegistry builtinMcpToolRegistry; |
| | | private final McpMountRuntimeFactory mcpMountRuntimeFactory; |
| | | private final ObjectMapper objectMapper; |
| | | private final AiRedisSupport aiRedisSupport; |
| | | |
| | | /** 查询某个租户下当前启用的 MCP 挂载列表。 */ |
| | | @Override |
| | |
| | | @Override |
| | | public List<AiMcpToolPreviewDto> previewTools(Long mountId, Long userId, Long tenantId) { |
| | | AiMcpMount mount = requireMount(mountId, tenantId); |
| | | // 工具目录预览初始化成本高,但变化频率低,适合做管理端短缓存。 |
| | | List<AiMcpToolPreviewDto> cached = aiRedisSupport.getToolPreview(tenantId, mountId); |
| | | if (cached != null) { |
| | | return cached; |
| | | } |
| | | long startedAt = System.currentTimeMillis(); |
| | | try (McpMountRuntimeFactory.McpMountRuntime runtime = mcpMountRuntimeFactory.create(List.of(mount), userId)) { |
| | | List<AiMcpToolPreviewDto> tools = buildToolPreviewDtos(runtime.getToolCallbacks(), |
| | |
| | | } |
| | | updateHealthStatus(mount.getId(), AiDefaults.MCP_HEALTH_HEALTHY, |
| | | "工具解析成功,共 " + tools.size() + " 个工具", System.currentTimeMillis() - startedAt); |
| | | aiRedisSupport.cacheToolPreview(tenantId, mountId, tools); |
| | | return tools; |
| | | } catch (CoolException e) { |
| | | throw e; |
| | |
| | | String message = String.join(";", runtime.getErrors()); |
| | | updateHealthStatus(mount.getId(), AiDefaults.MCP_HEALTH_UNHEALTHY, message, elapsedMs); |
| | | AiMcpMount latest = requireMount(mount.getId(), tenantId); |
| | | return buildConnectivityDto(latest, message, elapsedMs, runtime.getToolCallbacks().length); |
| | | AiMcpConnectivityTestDto connectivity = buildConnectivityDto(latest, message, elapsedMs, runtime.getToolCallbacks().length); |
| | | aiRedisSupport.cacheConnectivity(tenantId, mountId, connectivity); |
| | | return connectivity; |
| | | } |
| | | String message = "连通性测试成功,解析出 " + runtime.getToolCallbacks().length + " 个工具"; |
| | | updateHealthStatus(mount.getId(), AiDefaults.MCP_HEALTH_HEALTHY, message, elapsedMs); |
| | | AiMcpMount latest = requireMount(mount.getId(), tenantId); |
| | | return buildConnectivityDto(latest, message, elapsedMs, runtime.getToolCallbacks().length); |
| | | AiMcpConnectivityTestDto connectivity = buildConnectivityDto(latest, message, elapsedMs, runtime.getToolCallbacks().length); |
| | | aiRedisSupport.cacheConnectivity(tenantId, mountId, connectivity); |
| | | return connectivity; |
| | | } catch (CoolException e) { |
| | | throw e; |
| | | } catch (Exception e) { |
| | |
| | | String message = "连通性测试失败: " + e.getMessage(); |
| | | updateHealthStatus(mount.getId(), AiDefaults.MCP_HEALTH_UNHEALTHY, message, elapsedMs); |
| | | AiMcpMount latest = requireMount(mount.getId(), tenantId); |
| | | return buildConnectivityDto(latest, message, elapsedMs, 0); |
| | | AiMcpConnectivityTestDto connectivity = buildConnectivityDto(latest, message, elapsedMs, 0); |
| | | aiRedisSupport.cacheConnectivity(tenantId, mountId, connectivity); |
| | | return connectivity; |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | public boolean save(AiMcpMount entity) { |
| | | boolean saved = super.save(entity); |
| | | if (saved && entity != null && entity.getTenantId() != null) { |
| | | aiRedisSupport.evictMcpMountCaches(entity.getTenantId(), entity.getId()); |
| | | } |
| | | return saved; |
| | | } |
| | | |
| | | @Override |
| | | public boolean updateById(AiMcpMount entity) { |
| | | boolean updated = super.updateById(entity); |
| | | if (updated && entity != null && entity.getTenantId() != null) { |
| | | aiRedisSupport.evictMcpMountCaches(entity.getTenantId(), entity.getId()); |
| | | } |
| | | return updated; |
| | | } |
| | | |
| | | @Override |
| | | public boolean removeByIds(java.util.Collection<?> list) { |
| | | java.util.List<java.io.Serializable> ids = list == null ? java.util.List.of() : list.stream() |
| | | .filter(java.util.Objects::nonNull) |
| | | .map(item -> (java.io.Serializable) item) |
| | | .toList(); |
| | | java.util.List<AiMcpMount> records = this.listByIds(ids); |
| | | boolean removed = super.removeByIds(list); |
| | | if (removed) { |
| | | records.stream() |
| | | .filter(java.util.Objects::nonNull) |
| | | .forEach(item -> aiRedisSupport.evictMcpMountCaches(item.getTenantId(), item.getId())); |
| | | } |
| | | return removed; |
| | | } |
| | | |
| | | private void fillDefaults(AiMcpMount aiMcpMount) { |
| | | /** 为挂载草稿补齐统一默认值,保证后续运行时代码不需要重复判断空值。 */ |
| | | if (!StringUtils.hasText(aiMcpMount.getTransportType())) { |
| | |
| | | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
| | | import com.vincent.rsf.framework.exception.CoolException; |
| | | import com.vincent.rsf.server.ai.config.AiDefaults; |
| | | import com.vincent.rsf.server.ai.dto.AiChatModelOptionDto; |
| | | import com.vincent.rsf.server.ai.dto.AiParamValidateResultDto; |
| | | import com.vincent.rsf.server.ai.entity.AiParam; |
| | | import com.vincent.rsf.server.ai.mapper.AiParamMapper; |
| | |
| | | import com.vincent.rsf.server.system.enums.StatusType; |
| | | import lombok.RequiredArgsConstructor; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import org.springframework.util.StringUtils; |
| | | |
| | | import java.text.SimpleDateFormat; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | |
| | | @Service("aiParamService") |
| | | @RequiredArgsConstructor |
| | | public class AiParamServiceImpl extends ServiceImpl<AiParamMapper, AiParam> implements AiParamService { |
| | | |
| | | private final AiParamValidationSupport aiParamValidationSupport; |
| | | private final AiRedisSupport aiRedisSupport; |
| | | |
| | | @Override |
| | | public AiParam getActiveParam(Long tenantId) { |
| | |
| | | throw new CoolException("未找到启用中的 AI 参数配置"); |
| | | } |
| | | return aiParam; |
| | | } |
| | | |
| | | @Override |
| | | public AiParam getChatParam(Long tenantId, Long aiParamId) { |
| | | ensureTenantId(tenantId); |
| | | if (aiParamId == null) { |
| | | return getActiveParam(tenantId); |
| | | } |
| | | AiParam aiParam = requireOwnedRecord(aiParamId, tenantId); |
| | | if (!AiDefaults.PARAM_VALIDATE_VALID.equals(aiParam.getValidateStatus())) { |
| | | throw new CoolException("所选 AI 模型未通过校验,暂不可用于对话"); |
| | | } |
| | | return aiParam; |
| | | } |
| | | |
| | | @Override |
| | | public List<AiChatModelOptionDto> listChatModelOptions(Long tenantId) { |
| | | ensureTenantId(tenantId); |
| | | List<AiParam> params = this.list(new LambdaQueryWrapper<AiParam>() |
| | | .eq(AiParam::getTenantId, tenantId) |
| | | .eq(AiParam::getDeleted, 0) |
| | | .eq(AiParam::getValidateStatus, AiDefaults.PARAM_VALIDATE_VALID) |
| | | .orderByDesc(AiParam::getStatus) |
| | | .orderByDesc(AiParam::getUpdateTime) |
| | | .orderByDesc(AiParam::getCreateTime) |
| | | .orderByDesc(AiParam::getId)); |
| | | if (params.isEmpty()) { |
| | | return List.of(toChatModelOption(getActiveParam(tenantId))); |
| | | } |
| | | return params.stream() |
| | | .map(this::toChatModelOption) |
| | | .toList(); |
| | | } |
| | | |
| | | @Override |
| | | @Transactional(rollbackFor = Exception.class) |
| | | public AiParam setDefaultParam(Long id, Long tenantId, Long userId) { |
| | | ensureTenantId(tenantId); |
| | | if (id == null) { |
| | | throw new CoolException("AI 参数 ID 不能为空"); |
| | | } |
| | | AiParam target = requireOwnedRecord(id, tenantId); |
| | | if (!AiDefaults.PARAM_VALIDATE_VALID.equals(target.getValidateStatus())) { |
| | | throw new CoolException("仅允许将校验通过的 AI 参数设置为默认"); |
| | | } |
| | | Date now = new Date(); |
| | | this.lambdaUpdate() |
| | | .eq(AiParam::getTenantId, tenantId) |
| | | .eq(AiParam::getDeleted, 0) |
| | | .set(AiParam::getStatus, StatusType.DISABLE.val) |
| | | .set(AiParam::getUpdateBy, userId) |
| | | .set(AiParam::getUpdateTime, now) |
| | | .update(); |
| | | target.setStatus(StatusType.ENABLE.val); |
| | | target.setUpdateBy(userId); |
| | | target.setUpdateTime(now); |
| | | if (!super.updateById(target)) { |
| | | throw new CoolException("设置默认 AI 参数失败"); |
| | | } |
| | | aiRedisSupport.evictTenantConfigCaches(tenantId); |
| | | return target; |
| | | } |
| | | |
| | | @Override |
| | |
| | | AiParam current = requireOwnedRecord(aiParam.getId(), tenantId); |
| | | aiParam.setTenantId(current.getTenantId()); |
| | | ensureBaseFields(aiParam); |
| | | ensureDefaultStillExists(tenantId, current, aiParam.getStatus()); |
| | | ensureSingleActive(tenantId, aiParam.getId(), aiParam.getStatus()); |
| | | applyValidation(aiParam); |
| | | } |
| | |
| | | fillDefaults(aiParam); |
| | | ensureBaseFields(aiParam); |
| | | return aiParamValidationSupport.validate(aiParam); |
| | | } |
| | | |
| | | @Override |
| | | public boolean save(AiParam entity) { |
| | | boolean saved = super.save(entity); |
| | | if (saved && entity != null && entity.getTenantId() != null) { |
| | | aiRedisSupport.evictTenantConfigCaches(entity.getTenantId()); |
| | | } |
| | | return saved; |
| | | } |
| | | |
| | | @Override |
| | | public boolean updateById(AiParam entity) { |
| | | boolean updated = super.updateById(entity); |
| | | if (updated && entity != null && entity.getTenantId() != null) { |
| | | aiRedisSupport.evictTenantConfigCaches(entity.getTenantId()); |
| | | } |
| | | return updated; |
| | | } |
| | | |
| | | @Override |
| | | public boolean removeByIds(java.util.Collection<?> list) { |
| | | java.util.List<java.io.Serializable> ids = list == null ? java.util.List.of() : list.stream() |
| | | .filter(java.util.Objects::nonNull) |
| | | .map(item -> (java.io.Serializable) item) |
| | | .toList(); |
| | | java.util.List<AiParam> records = this.listByIds(ids); |
| | | ensureRemovingDefaultIsSafe(records); |
| | | boolean removed = super.removeByIds(list); |
| | | if (removed) { |
| | | records.stream() |
| | | .map(AiParam::getTenantId) |
| | | .filter(java.util.Objects::nonNull) |
| | | .distinct() |
| | | .forEach(aiRedisSupport::evictTenantConfigCaches); |
| | | } |
| | | return removed; |
| | | } |
| | | |
| | | private void ensureBaseFields(AiParam aiParam) { |
| | |
| | | } |
| | | if (this.count(wrapper) > 0) { |
| | | throw new CoolException("同一租户仅允许一条启用中的 AI 参数配置"); |
| | | } |
| | | } |
| | | |
| | | private void ensureDefaultStillExists(Long tenantId, AiParam current, Integer nextStatus) { |
| | | if (current == null || current.getStatus() == null || current.getStatus() != StatusType.ENABLE.val) { |
| | | return; |
| | | } |
| | | if (nextStatus != null && nextStatus == StatusType.ENABLE.val) { |
| | | return; |
| | | } |
| | | long otherDefaultCount = this.count(new LambdaQueryWrapper<AiParam>() |
| | | .eq(AiParam::getTenantId, tenantId) |
| | | .eq(AiParam::getDeleted, 0) |
| | | .eq(AiParam::getStatus, StatusType.ENABLE.val) |
| | | .ne(AiParam::getId, current.getId())); |
| | | if (otherDefaultCount == 0) { |
| | | throw new CoolException("请先将其他 AI 参数设置为默认,再取消当前默认"); |
| | | } |
| | | } |
| | | |
| | | private void ensureRemovingDefaultIsSafe(List<AiParam> records) { |
| | | if (records == null || records.isEmpty()) { |
| | | return; |
| | | } |
| | | records.stream() |
| | | .filter(item -> item.getTenantId() != null && item.getStatus() != null && item.getStatus() == StatusType.ENABLE.val) |
| | | .map(AiParam::getTenantId) |
| | | .distinct() |
| | | .forEach(this::ensureTenantHasRemainingDefaultAfterRemove); |
| | | } |
| | | |
| | | private void ensureTenantHasRemainingDefaultAfterRemove(Long tenantId) { |
| | | long defaultCount = this.count(new LambdaQueryWrapper<AiParam>() |
| | | .eq(AiParam::getTenantId, tenantId) |
| | | .eq(AiParam::getDeleted, 0) |
| | | .eq(AiParam::getStatus, StatusType.ENABLE.val)); |
| | | if (defaultCount <= 1) { |
| | | throw new CoolException("默认 AI 参数不能直接删除,请先将其他配置设为默认"); |
| | | } |
| | | } |
| | | |
| | |
| | | throw new CoolException("解析校验时间失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private AiChatModelOptionDto toChatModelOption(AiParam aiParam) { |
| | | return AiChatModelOptionDto.builder() |
| | | .aiParamId(aiParam.getId()) |
| | | .name(aiParam.getName()) |
| | | .model(aiParam.getModel()) |
| | | .providerType(aiParam.getProviderType()) |
| | | .active(aiParam.getStatus() != null && aiParam.getStatus() == StatusType.ENABLE.val) |
| | | .build(); |
| | | } |
| | | } |
| | |
| | | public class AiPromptServiceImpl extends ServiceImpl<AiPromptMapper, AiPrompt> implements AiPromptService { |
| | | |
| | | private final AiPromptRenderSupport aiPromptRenderSupport; |
| | | private final AiRedisSupport aiRedisSupport; |
| | | |
| | | @Override |
| | | public AiPrompt getActivePrompt(String code, Long tenantId) { |
| | |
| | | ); |
| | | } |
| | | |
| | | @Override |
| | | public boolean save(AiPrompt entity) { |
| | | boolean saved = super.save(entity); |
| | | if (saved && entity != null && entity.getTenantId() != null) { |
| | | aiRedisSupport.evictTenantConfigCaches(entity.getTenantId()); |
| | | } |
| | | return saved; |
| | | } |
| | | |
| | | @Override |
| | | public boolean updateById(AiPrompt entity) { |
| | | boolean updated = super.updateById(entity); |
| | | if (updated && entity != null && entity.getTenantId() != null) { |
| | | aiRedisSupport.evictTenantConfigCaches(entity.getTenantId()); |
| | | } |
| | | return updated; |
| | | } |
| | | |
| | | @Override |
| | | public boolean removeByIds(java.util.Collection<?> list) { |
| | | java.util.List<java.io.Serializable> ids = list == null ? java.util.List.of() : list.stream() |
| | | .filter(java.util.Objects::nonNull) |
| | | .map(item -> (java.io.Serializable) item) |
| | | .toList(); |
| | | java.util.List<AiPrompt> records = this.listByIds(ids); |
| | | boolean removed = super.removeByIds(list); |
| | | if (removed) { |
| | | records.stream() |
| | | .map(AiPrompt::getTenantId) |
| | | .filter(java.util.Objects::nonNull) |
| | | .distinct() |
| | | .forEach(aiRedisSupport::evictTenantConfigCaches); |
| | | } |
| | | return removed; |
| | | } |
| | | |
| | | private void ensureRequiredFields(AiPrompt aiPrompt) { |
| | | if (!StringUtils.hasText(aiPrompt.getName())) { |
| | | throw new CoolException("Prompt 名称不能为空"); |
| New file |
| | |
| | | package com.vincent.rsf.server.ai.service.impl; |
| | | |
| | | import com.fasterxml.jackson.core.type.TypeReference; |
| | | import com.fasterxml.jackson.databind.ObjectMapper; |
| | | import com.vincent.rsf.server.ai.config.AiDefaults; |
| | | import com.vincent.rsf.server.ai.dto.AiChatMemoryDto; |
| | | import com.vincent.rsf.server.ai.dto.AiChatRuntimeDto; |
| | | import com.vincent.rsf.server.ai.dto.AiChatSessionDto; |
| | | import com.vincent.rsf.server.ai.dto.AiMcpConnectivityTestDto; |
| | | import com.vincent.rsf.server.ai.dto.AiMcpToolPreviewDto; |
| | | import com.vincent.rsf.server.ai.dto.AiObserveStatsDto; |
| | | import com.vincent.rsf.server.ai.dto.AiResolvedConfig; |
| | | import com.vincent.rsf.server.common.service.RedisService; |
| | | import lombok.AllArgsConstructor; |
| | | import lombok.Builder; |
| | | import lombok.Data; |
| | | import lombok.NoArgsConstructor; |
| | | import lombok.RequiredArgsConstructor; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.util.StringUtils; |
| | | import redis.clients.jedis.Jedis; |
| | | |
| | | import java.net.URLEncoder; |
| | | import java.nio.charset.StandardCharsets; |
| | | import java.security.MessageDigest; |
| | | import java.time.Instant; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Set; |
| | | import java.util.UUID; |
| | | import java.util.function.Consumer; |
| | | import java.util.function.Function; |
| | | import java.util.function.Supplier; |
| | | |
| | | @Slf4j |
| | | @Service |
| | | @RequiredArgsConstructor |
| | | public class AiRedisSupport { |
| | | |
| | | /** 统一收口 AI 模块的 Redis key、TTL 和序列化策略,避免业务类直接散写 Redis。 */ |
| | | private static final String CONFIG_KEY_PREFIX = "AI:CONFIG:"; |
| | | private static final String RUNTIME_KEY_PREFIX = "AI:RUNTIME:"; |
| | | private static final String MEMORY_KEY_PREFIX = "AI:MEMORY:"; |
| | | private static final String SESSIONS_KEY_PREFIX = "AI:SESSIONS:"; |
| | | private static final String MCP_PREVIEW_KEY_PREFIX = "AI:MCP:PREVIEW:"; |
| | | private static final String MCP_HEALTH_KEY_PREFIX = "AI:MCP:HEALTH:"; |
| | | private static final String STREAM_STATE_KEY_PREFIX = "AI:STREAM:"; |
| | | private static final String TOOL_RESULT_KEY_PREFIX = "AI:TOOL:RESULT:"; |
| | | private static final String RATE_LIMIT_KEY_PREFIX = "AI:RATE:"; |
| | | private static final String OBSERVE_STATS_KEY_PREFIX = "AI:OBSERVE:STATS:"; |
| | | private static final String OBSERVE_TOOL_RANK_KEY_PREFIX = "AI:OBSERVE:TOOL:RANK:"; |
| | | private static final String OBSERVE_TOOL_FAIL_RANK_KEY_PREFIX = "AI:OBSERVE:TOOL:FAIL:RANK:"; |
| | | |
| | | private static final String FIELD_CALL_COUNT = "callCount"; |
| | | private static final String FIELD_SUCCESS_COUNT = "successCount"; |
| | | private static final String FIELD_FAILURE_COUNT = "failureCount"; |
| | | private static final String FIELD_ELAPSED_SUM = "elapsedSum"; |
| | | private static final String FIELD_ELAPSED_COUNT = "elapsedCount"; |
| | | private static final String FIELD_FIRST_TOKEN_SUM = "firstTokenSum"; |
| | | private static final String FIELD_FIRST_TOKEN_COUNT = "firstTokenCount"; |
| | | private static final String FIELD_TOTAL_TOKENS_SUM = "totalTokensSum"; |
| | | private static final String FIELD_TOTAL_TOKENS_COUNT = "totalTokensCount"; |
| | | private static final String FIELD_TOOL_CALL_COUNT = "toolCallCount"; |
| | | private static final String FIELD_TOOL_SUCCESS_COUNT = "toolSuccessCount"; |
| | | private static final String FIELD_TOOL_FAILURE_COUNT = "toolFailureCount"; |
| | | |
| | | private final RedisService redisService; |
| | | private final ObjectMapper objectMapper; |
| | | |
| | | public AiResolvedConfig getResolvedConfig(Long tenantId, String promptCode, Long aiParamId) { |
| | | return readJson(buildConfigKey(tenantId, promptCode, aiParamId), AiResolvedConfig.class); |
| | | } |
| | | |
| | | public void cacheResolvedConfig(Long tenantId, String promptCode, Long aiParamId, AiResolvedConfig config) { |
| | | writeJson(buildConfigKey(tenantId, promptCode, aiParamId), config, AiDefaults.CONFIG_CACHE_TTL_SECONDS); |
| | | } |
| | | |
| | | public void evictTenantConfigCaches(Long tenantId) { |
| | | deleteByPrefix(CONFIG_KEY_PREFIX + tenantId + ":"); |
| | | deleteByPrefix(RUNTIME_KEY_PREFIX + tenantId + ":"); |
| | | } |
| | | |
| | | public AiChatRuntimeDto getRuntime(Long tenantId, Long userId, String promptCode, Long sessionId, Long aiParamId) { |
| | | return readJson(buildRuntimeKey(tenantId, userId, promptCode, sessionId, aiParamId), AiChatRuntimeDto.class); |
| | | } |
| | | |
| | | public void cacheRuntime(Long tenantId, Long userId, String promptCode, Long sessionId, Long aiParamId, AiChatRuntimeDto runtime) { |
| | | writeJson(buildRuntimeKey(tenantId, userId, promptCode, sessionId, aiParamId), runtime, AiDefaults.RUNTIME_CACHE_TTL_SECONDS); |
| | | } |
| | | |
| | | public AiChatMemoryDto getMemory(Long tenantId, Long userId, String promptCode, Long sessionId) { |
| | | return readJson(buildMemoryKey(tenantId, userId, promptCode, sessionId), AiChatMemoryDto.class); |
| | | } |
| | | |
| | | public void cacheMemory(Long tenantId, Long userId, String promptCode, Long sessionId, AiChatMemoryDto memory) { |
| | | writeJson(buildMemoryKey(tenantId, userId, promptCode, sessionId), memory, AiDefaults.MEMORY_CACHE_TTL_SECONDS); |
| | | } |
| | | |
| | | public List<AiChatSessionDto> getSessionList(Long tenantId, Long userId, String promptCode, String keyword) { |
| | | return readJson(buildSessionsKey(tenantId, userId, promptCode, keyword), new TypeReference<List<AiChatSessionDto>>() { |
| | | }); |
| | | } |
| | | |
| | | public void cacheSessionList(Long tenantId, Long userId, String promptCode, String keyword, List<AiChatSessionDto> sessions) { |
| | | writeJson(buildSessionsKey(tenantId, userId, promptCode, keyword), sessions, AiDefaults.SESSION_LIST_CACHE_TTL_SECONDS); |
| | | } |
| | | |
| | | public void evictUserConversationCaches(Long tenantId, Long userId) { |
| | | deleteByPrefix(RUNTIME_KEY_PREFIX + tenantId + ":" + userId + ":"); |
| | | deleteByPrefix(MEMORY_KEY_PREFIX + tenantId + ":" + userId + ":"); |
| | | deleteByPrefix(SESSIONS_KEY_PREFIX + tenantId + ":" + userId + ":"); |
| | | } |
| | | |
| | | public List<AiMcpToolPreviewDto> getToolPreview(Long tenantId, Long mountId) { |
| | | return readJson(buildMcpPreviewKey(tenantId, mountId), new TypeReference<List<AiMcpToolPreviewDto>>() { |
| | | }); |
| | | } |
| | | |
| | | public void cacheToolPreview(Long tenantId, Long mountId, List<AiMcpToolPreviewDto> tools) { |
| | | writeJson(buildMcpPreviewKey(tenantId, mountId), tools, AiDefaults.MCP_PREVIEW_CACHE_TTL_SECONDS); |
| | | } |
| | | |
| | | public AiMcpConnectivityTestDto getConnectivity(Long tenantId, Long mountId) { |
| | | return readJson(buildMcpHealthKey(tenantId, mountId), AiMcpConnectivityTestDto.class); |
| | | } |
| | | |
| | | public void cacheConnectivity(Long tenantId, Long mountId, AiMcpConnectivityTestDto connectivity) { |
| | | writeJson(buildMcpHealthKey(tenantId, mountId), connectivity, AiDefaults.MCP_HEALTH_CACHE_TTL_SECONDS); |
| | | } |
| | | |
| | | public void evictMcpMountCaches(Long tenantId, Long mountId) { |
| | | if (mountId != null) { |
| | | delete(buildMcpPreviewKey(tenantId, mountId)); |
| | | delete(buildMcpHealthKey(tenantId, mountId)); |
| | | } else { |
| | | deleteByPrefix(MCP_PREVIEW_KEY_PREFIX + tenantId + ":"); |
| | | deleteByPrefix(MCP_HEALTH_KEY_PREFIX + tenantId + ":"); |
| | | } |
| | | evictTenantConfigCaches(tenantId); |
| | | } |
| | | |
| | | public boolean allowChatRequest(Long tenantId, Long userId, String promptCode) { |
| | | String key = buildRateLimitKey(tenantId, userId, promptCode); |
| | | long now = Instant.now().toEpochMilli(); |
| | | long windowStart = now - (AiDefaults.CHAT_RATE_LIMIT_WINDOW_SECONDS * 1000L); |
| | | Boolean allowed = execute(jedis -> { |
| | | // 用 zset 维护滑动窗口,而不是简单计数器,避免窗口边界出现突刺误判。 |
| | | jedis.zremrangeByScore(key, 0, windowStart); |
| | | long count = jedis.zcard(key); |
| | | if (count >= AiDefaults.CHAT_RATE_LIMIT_MAX_REQUESTS) { |
| | | jedis.expire(key, AiDefaults.CHAT_RATE_LIMIT_WINDOW_SECONDS); |
| | | return Boolean.FALSE; |
| | | } |
| | | jedis.zadd(key, now, now + ":" + UUID.randomUUID()); |
| | | jedis.expire(key, AiDefaults.CHAT_RATE_LIMIT_WINDOW_SECONDS); |
| | | return Boolean.TRUE; |
| | | }); |
| | | return Boolean.TRUE.equals(allowed); |
| | | } |
| | | |
| | | public void markStreamState(String requestId, Long tenantId, Long userId, Long sessionId, String promptCode, |
| | | String status, String errorMessage) { |
| | | if (!StringUtils.hasText(requestId)) { |
| | | return; |
| | | } |
| | | writeJson(buildStreamStateKey(tenantId, requestId), AiStreamState.builder() |
| | | .requestId(requestId) |
| | | .tenantId(tenantId) |
| | | .userId(userId) |
| | | .sessionId(sessionId) |
| | | .promptCode(promptCode) |
| | | .status(status) |
| | | .errorMessage(errorMessage) |
| | | .timestamp(Instant.now().toEpochMilli()) |
| | | .build(), AiDefaults.STREAM_STATE_TTL_SECONDS); |
| | | } |
| | | |
| | | public CachedToolResult getToolResult(Long tenantId, String requestId, String toolName, String toolInput) { |
| | | return readJson(buildToolResultKey(tenantId, requestId, toolName, toolInput), CachedToolResult.class); |
| | | } |
| | | |
| | | public void cacheToolResult(Long tenantId, String requestId, String toolName, String toolInput, |
| | | boolean success, String output, String errorMessage) { |
| | | writeJson(buildToolResultKey(tenantId, requestId, toolName, toolInput), CachedToolResult.builder() |
| | | .success(success) |
| | | .output(output) |
| | | .errorMessage(errorMessage) |
| | | .build(), AiDefaults.TOOL_RESULT_CACHE_TTL_SECONDS); |
| | | } |
| | | |
| | | public void recordObserveCallStarted(Long tenantId) { |
| | | executeVoid(jedis -> jedis.hincrBy(buildObserveStatsKey(tenantId), FIELD_CALL_COUNT, 1)); |
| | | } |
| | | |
| | | public void recordObserveCallFinished(Long tenantId, String status, Long elapsedMs, Long firstTokenLatencyMs, Integer totalTokens) { |
| | | executeVoid(jedis -> { |
| | | String key = buildObserveStatsKey(tenantId); |
| | | if ("COMPLETED".equals(status)) { |
| | | jedis.hincrBy(key, FIELD_SUCCESS_COUNT, 1); |
| | | } else if ("FAILED".equals(status)) { |
| | | jedis.hincrBy(key, FIELD_FAILURE_COUNT, 1); |
| | | } |
| | | if (elapsedMs != null) { |
| | | jedis.hincrBy(key, FIELD_ELAPSED_SUM, elapsedMs); |
| | | jedis.hincrBy(key, FIELD_ELAPSED_COUNT, 1); |
| | | } |
| | | if (firstTokenLatencyMs != null) { |
| | | jedis.hincrBy(key, FIELD_FIRST_TOKEN_SUM, firstTokenLatencyMs); |
| | | jedis.hincrBy(key, FIELD_FIRST_TOKEN_COUNT, 1); |
| | | } |
| | | if (totalTokens != null) { |
| | | jedis.hincrBy(key, FIELD_TOTAL_TOKENS_SUM, totalTokens.longValue()); |
| | | jedis.hincrBy(key, FIELD_TOTAL_TOKENS_COUNT, 1); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | public void recordObserveToolCall(Long tenantId, String toolName, String status) { |
| | | executeVoid(jedis -> { |
| | | String key = buildObserveStatsKey(tenantId); |
| | | jedis.hincrBy(key, FIELD_TOOL_CALL_COUNT, 1); |
| | | if ("COMPLETED".equals(status)) { |
| | | jedis.hincrBy(key, FIELD_TOOL_SUCCESS_COUNT, 1); |
| | | } else if ("FAILED".equals(status)) { |
| | | jedis.hincrBy(key, FIELD_TOOL_FAILURE_COUNT, 1); |
| | | } |
| | | if (StringUtils.hasText(toolName)) { |
| | | jedis.zincrby(buildToolRankKey(tenantId), 1D, toolName); |
| | | if ("FAILED".equals(status)) { |
| | | jedis.zincrby(buildToolFailRankKey(tenantId), 1D, toolName); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | |
| | | public AiObserveStatsDto getObserveStats(Long tenantId, Supplier<AiObserveStatsDto> fallbackLoader) { |
| | | AiObserveStatsDto cached = readObserveStats(tenantId); |
| | | if (cached != null) { |
| | | return cached; |
| | | } |
| | | // Redis 为空时再回源数据库,避免管理端看板每次都扫全量日志表。 |
| | | AiObserveStatsDto snapshot = fallbackLoader.get(); |
| | | if (snapshot != null) { |
| | | seedObserveStats(tenantId, snapshot); |
| | | } |
| | | return snapshot; |
| | | } |
| | | |
| | | private AiObserveStatsDto readObserveStats(Long tenantId) { |
| | | Map<String, String> fields = execute(jedis -> { |
| | | String key = buildObserveStatsKey(tenantId); |
| | | if (!jedis.exists(key)) { |
| | | return null; |
| | | } |
| | | return jedis.hgetAll(key); |
| | | }); |
| | | if (fields == null || fields.isEmpty()) { |
| | | return null; |
| | | } |
| | | long callCount = parseLong(fields.get(FIELD_CALL_COUNT)); |
| | | long successCount = parseLong(fields.get(FIELD_SUCCESS_COUNT)); |
| | | long failureCount = parseLong(fields.get(FIELD_FAILURE_COUNT)); |
| | | long elapsedSum = parseLong(fields.get(FIELD_ELAPSED_SUM)); |
| | | long elapsedCount = parseLong(fields.get(FIELD_ELAPSED_COUNT)); |
| | | long firstTokenSum = parseLong(fields.get(FIELD_FIRST_TOKEN_SUM)); |
| | | long firstTokenCount = parseLong(fields.get(FIELD_FIRST_TOKEN_COUNT)); |
| | | long totalTokensSum = parseLong(fields.get(FIELD_TOTAL_TOKENS_SUM)); |
| | | long totalTokensCount = parseLong(fields.get(FIELD_TOTAL_TOKENS_COUNT)); |
| | | long toolCallCount = parseLong(fields.get(FIELD_TOOL_CALL_COUNT)); |
| | | long toolSuccessCount = parseLong(fields.get(FIELD_TOOL_SUCCESS_COUNT)); |
| | | long toolFailureCount = parseLong(fields.get(FIELD_TOOL_FAILURE_COUNT)); |
| | | return AiObserveStatsDto.builder() |
| | | .callCount(callCount) |
| | | .successCount(successCount) |
| | | .failureCount(failureCount) |
| | | .avgElapsedMs(elapsedCount == 0 ? 0L : elapsedSum / elapsedCount) |
| | | .avgFirstTokenLatencyMs(firstTokenCount == 0 ? 0L : firstTokenSum / firstTokenCount) |
| | | .totalTokens(totalTokensSum) |
| | | .avgTotalTokens(totalTokensCount == 0 ? 0L : totalTokensSum / totalTokensCount) |
| | | .toolCallCount(toolCallCount) |
| | | .toolSuccessCount(toolSuccessCount) |
| | | .toolFailureCount(toolFailureCount) |
| | | .toolSuccessRate(toolCallCount == 0 ? 0D : (toolSuccessCount * 100D) / toolCallCount) |
| | | .build(); |
| | | } |
| | | |
| | | private void seedObserveStats(Long tenantId, AiObserveStatsDto snapshot) { |
| | | executeVoid(jedis -> { |
| | | String key = buildObserveStatsKey(tenantId); |
| | | Map<String, String> values = new LinkedHashMap<>(); |
| | | values.put(FIELD_CALL_COUNT, String.valueOf(defaultLong(snapshot.getCallCount()))); |
| | | values.put(FIELD_SUCCESS_COUNT, String.valueOf(defaultLong(snapshot.getSuccessCount()))); |
| | | values.put(FIELD_FAILURE_COUNT, String.valueOf(defaultLong(snapshot.getFailureCount()))); |
| | | values.put(FIELD_ELAPSED_SUM, String.valueOf(defaultLong(snapshot.getAvgElapsedMs()) * defaultLong(snapshot.getCallCount()))); |
| | | values.put(FIELD_ELAPSED_COUNT, String.valueOf(defaultLong(snapshot.getCallCount()))); |
| | | values.put(FIELD_FIRST_TOKEN_SUM, String.valueOf(defaultLong(snapshot.getAvgFirstTokenLatencyMs()) * defaultLong(snapshot.getCallCount()))); |
| | | values.put(FIELD_FIRST_TOKEN_COUNT, String.valueOf(defaultLong(snapshot.getCallCount()))); |
| | | values.put(FIELD_TOTAL_TOKENS_SUM, String.valueOf(defaultLong(snapshot.getTotalTokens()))); |
| | | values.put(FIELD_TOTAL_TOKENS_COUNT, String.valueOf(defaultLong(snapshot.getCallCount()))); |
| | | values.put(FIELD_TOOL_CALL_COUNT, String.valueOf(defaultLong(snapshot.getToolCallCount()))); |
| | | values.put(FIELD_TOOL_SUCCESS_COUNT, String.valueOf(defaultLong(snapshot.getToolSuccessCount()))); |
| | | values.put(FIELD_TOOL_FAILURE_COUNT, String.valueOf(defaultLong(snapshot.getToolFailureCount()))); |
| | | jedis.hset(key, values); |
| | | }); |
| | | } |
| | | |
| | | private String buildConfigKey(Long tenantId, String promptCode, Long aiParamId) { |
| | | return CONFIG_KEY_PREFIX + tenantId + ":" + safeToken(promptCode) + ":" + aiParamToken(aiParamId); |
| | | } |
| | | |
| | | private String buildRuntimeKey(Long tenantId, Long userId, String promptCode, Long sessionId, Long aiParamId) { |
| | | return RUNTIME_KEY_PREFIX + tenantId + ":" + userId + ":" + safeToken(promptCode) + ":" + sessionToken(sessionId) + ":" + aiParamToken(aiParamId); |
| | | } |
| | | |
| | | private String buildMemoryKey(Long tenantId, Long userId, String promptCode, Long sessionId) { |
| | | return MEMORY_KEY_PREFIX + tenantId + ":" + userId + ":" + safeToken(promptCode) + ":" + sessionToken(sessionId); |
| | | } |
| | | |
| | | private String buildSessionsKey(Long tenantId, Long userId, String promptCode, String keyword) { |
| | | return SESSIONS_KEY_PREFIX + tenantId + ":" + userId + ":" + safeToken(promptCode) + ":" + safeToken(keyword); |
| | | } |
| | | |
| | | private String buildMcpPreviewKey(Long tenantId, Long mountId) { |
| | | return MCP_PREVIEW_KEY_PREFIX + tenantId + ":" + mountId; |
| | | } |
| | | |
| | | private String buildMcpHealthKey(Long tenantId, Long mountId) { |
| | | return MCP_HEALTH_KEY_PREFIX + tenantId + ":" + mountId; |
| | | } |
| | | |
| | | private String buildStreamStateKey(Long tenantId, String requestId) { |
| | | return STREAM_STATE_KEY_PREFIX + tenantId + ":" + safeToken(requestId); |
| | | } |
| | | |
| | | private String buildToolResultKey(Long tenantId, String requestId, String toolName, String toolInput) { |
| | | return TOOL_RESULT_KEY_PREFIX + tenantId + ":" + safeToken(requestId) + ":" + safeToken(toolName) + ":" + digest(toolInput); |
| | | } |
| | | |
| | | private String buildRateLimitKey(Long tenantId, Long userId, String promptCode) { |
| | | return RATE_LIMIT_KEY_PREFIX + tenantId + ":" + userId + ":" + safeToken(promptCode); |
| | | } |
| | | |
| | | private String buildObserveStatsKey(Long tenantId) { |
| | | return OBSERVE_STATS_KEY_PREFIX + tenantId; |
| | | } |
| | | |
| | | private String buildToolRankKey(Long tenantId) { |
| | | return OBSERVE_TOOL_RANK_KEY_PREFIX + tenantId; |
| | | } |
| | | |
| | | private String buildToolFailRankKey(Long tenantId) { |
| | | return OBSERVE_TOOL_FAIL_RANK_KEY_PREFIX + tenantId; |
| | | } |
| | | |
| | | private String sessionToken(Long sessionId) { |
| | | return sessionId == null ? "LATEST" : String.valueOf(sessionId); |
| | | } |
| | | |
| | | private String aiParamToken(Long aiParamId) { |
| | | return aiParamId == null ? "DEFAULT" : String.valueOf(aiParamId); |
| | | } |
| | | |
| | | private String safeToken(String source) { |
| | | if (!StringUtils.hasText(source)) { |
| | | return "_"; |
| | | } |
| | | return URLEncoder.encode(source.trim(), StandardCharsets.UTF_8); |
| | | } |
| | | |
| | | private String digest(String source) { |
| | | try { |
| | | MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); |
| | | byte[] bytes = messageDigest.digest((source == null ? "" : source).getBytes(StandardCharsets.UTF_8)); |
| | | StringBuilder builder = new StringBuilder(); |
| | | for (byte value : bytes) { |
| | | builder.append(String.format("%02x", value)); |
| | | } |
| | | return builder.toString(); |
| | | } catch (Exception e) { |
| | | return safeToken(source); |
| | | } |
| | | } |
| | | |
| | | private <T> T readJson(String key, Class<T> type) { |
| | | return readJson(key, value -> objectMapper.readValue(value, type)); |
| | | } |
| | | |
| | | private <T> T readJson(String key, TypeReference<T> typeReference) { |
| | | return readJson(key, value -> objectMapper.readValue(value, typeReference)); |
| | | } |
| | | |
| | | private <T> T readJson(String key, JsonReader<T> reader) { |
| | | return execute(jedis -> { |
| | | String value = jedis.get(key); |
| | | if (!StringUtils.hasText(value)) { |
| | | return null; |
| | | } |
| | | try { |
| | | return reader.read(value); |
| | | } catch (Exception e) { |
| | | // 反序列化失败时直接删坏缓存,让下次请求自然回源重建。 |
| | | log.warn("AI redis cache deserialize failed, key={}, message={}", key, e.getMessage()); |
| | | jedis.del(key); |
| | | return null; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private void writeJson(String key, Object value, int ttlSeconds) { |
| | | if (value == null) { |
| | | delete(key); |
| | | return; |
| | | } |
| | | executeVoid(jedis -> { |
| | | try { |
| | | jedis.setex(key, ttlSeconds, objectMapper.writeValueAsString(value)); |
| | | } catch (Exception e) { |
| | | log.warn("AI redis cache serialize failed, key={}, message={}", key, e.getMessage()); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | private void delete(String key) { |
| | | executeVoid(jedis -> jedis.del(key)); |
| | | } |
| | | |
| | | private void deleteByPrefix(String prefix) { |
| | | executeVoid(jedis -> { |
| | | Set<String> keys = jedis.keys(prefix + "*"); |
| | | if (keys == null || keys.isEmpty()) { |
| | | return; |
| | | } |
| | | jedis.del(keys.toArray(new String[0])); |
| | | }); |
| | | } |
| | | |
| | | private long parseLong(String source) { |
| | | if (!StringUtils.hasText(source)) { |
| | | return 0L; |
| | | } |
| | | try { |
| | | return Long.parseLong(source); |
| | | } catch (Exception e) { |
| | | return 0L; |
| | | } |
| | | } |
| | | |
| | | private long defaultLong(Long value) { |
| | | return value == null ? 0L : value; |
| | | } |
| | | |
| | | private <T> T execute(Function<Jedis, T> action) { |
| | | Jedis jedis = redisService.getJedis(); |
| | | if (jedis == null) { |
| | | return null; |
| | | } |
| | | try (jedis) { |
| | | return action.apply(jedis); |
| | | } catch (Exception e) { |
| | | log.warn("AI redis operation skipped, message={}", e.getMessage()); |
| | | return null; |
| | | } |
| | | } |
| | | |
| | | private void executeVoid(Consumer<Jedis> action) { |
| | | Jedis jedis = redisService.getJedis(); |
| | | if (jedis == null) { |
| | | return; |
| | | } |
| | | try (jedis) { |
| | | action.accept(jedis); |
| | | } catch (Exception e) { |
| | | log.warn("AI redis operation skipped, message={}", e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | @FunctionalInterface |
| | | private interface JsonReader<T> { |
| | | T read(String value) throws Exception; |
| | | } |
| | | |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | public static class CachedToolResult { |
| | | |
| | | private boolean success; |
| | | |
| | | private String output; |
| | | |
| | | private String errorMessage; |
| | | } |
| | | |
| | | @Data |
| | | @Builder |
| | | @NoArgsConstructor |
| | | @AllArgsConstructor |
| | | private static class AiStreamState { |
| | | |
| | | private String requestId; |
| | | |
| | | private Long tenantId; |
| | | |
| | | private Long userId; |
| | | |
| | | private Long sessionId; |
| | | |
| | | private String promptCode; |
| | | |
| | | private String status; |
| | | |
| | | private String errorMessage; |
| | | |
| | | private Long timestamp; |
| | | } |
| | | } |
| | |
| | | class: javax.net.ssl.SSLSocketFactory |
| | | redis: |
| | | host: 127.0.0.1 |
| | | password: xltys1995 |
| | | port: 6379 |
| | | timeout: 5000 |
| | | index: 15 |