feat: implement RAG dashboard with MUI and react-router

Add new RAG dashboard feature including:
- Main application layout with header and sidebar
- Multiple pages (Home, KnowledgeBase, PipelineConfig, Dashboard, MCP)
- Theme configuration and styling
- Routing setup with protected routes
- Login page and authentication flow
- Various UI components and mock data for dashboard views
This commit is contained in:
2025-10-09 17:23:15 +08:00
parent 446b422a12
commit 5f93573e57
15 changed files with 3521 additions and 31 deletions

View File

@@ -10,8 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.1",
"@mui/icons-material": "^7.3.4",
"@mui/material": "^7.3.4",
"react": "^18.3.1",
"react-dom": "^18.3.1"
"react-dom": "^18.3.1",
"react-is": "18.3.1",
"react-router-dom": "^7.9.4"
},
"devDependencies": {
"@eslint/js": "^9.36.0",

602
pnpm-lock.yaml generated
View File

@@ -8,12 +8,30 @@ importers:
.:
dependencies:
'@emotion/react':
specifier: ^11.14.0
version: 11.14.0(@types/react@19.2.2)(react@18.3.1)
'@emotion/styled':
specifier: ^11.14.1
version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
'@mui/icons-material':
specifier: ^7.3.4
version: 7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
'@mui/material':
specifier: ^7.3.4
version: 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react:
specifier: ^18.3.1
version: 18.3.1
react-dom:
specifier: ^18.3.1
version: 18.3.1(react@18.3.1)
react-is:
specifier: 18.3.1
version: 18.3.1
react-router-dom:
specifier: ^7.9.4
version: 7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
devDependencies:
'@eslint/js':
specifier: ^9.36.0
@@ -125,6 +143,10 @@ packages:
peerDependencies:
'@babel/core': ^7.0.0-0
'@babel/runtime@7.28.4':
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
'@babel/template@7.27.2':
resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==}
engines: {node: '>=6.9.0'}
@@ -137,6 +159,60 @@ packages:
resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==}
engines: {node: '>=6.9.0'}
'@emotion/babel-plugin@11.13.5':
resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==}
'@emotion/cache@11.14.0':
resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==}
'@emotion/hash@0.9.2':
resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==}
'@emotion/is-prop-valid@1.4.0':
resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==}
'@emotion/memoize@0.9.0':
resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==}
'@emotion/react@11.14.0':
resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==}
peerDependencies:
'@types/react': '*'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
'@emotion/serialize@1.3.3':
resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==}
'@emotion/sheet@1.4.0':
resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==}
'@emotion/styled@11.14.1':
resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==}
peerDependencies:
'@emotion/react': ^11.0.0-rc.0
'@types/react': '*'
react: '>=16.8.0'
peerDependenciesMeta:
'@types/react':
optional: true
'@emotion/unitless@0.10.0':
resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==}
'@emotion/use-insertion-effect-with-fallbacks@1.2.0':
resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==}
peerDependencies:
react: '>=16.8.0'
'@emotion/utils@1.4.2':
resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==}
'@emotion/weak-memoize@0.4.0':
resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==}
'@esbuild/aix-ppc64@0.25.10':
resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
engines: {node: '>=18'}
@@ -363,6 +439,97 @@ packages:
'@jridgewell/trace-mapping@0.3.31':
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
'@mui/core-downloads-tracker@7.3.4':
resolution: {integrity: sha512-BIktMapG3r4iXwIhYNpvk97ZfYWTreBBQTWjQKbNbzI64+ULHfYavQEX2w99aSWHS58DvXESWIgbD9adKcUOBw==}
'@mui/icons-material@7.3.4':
resolution: {integrity: sha512-9n6Xcq7molXWYb680N2Qx+FRW8oT6j/LXF5PZFH3ph9X/Rct0B/BlLAsFI7iL9ySI6LVLuQIVtrLiPT82R7OZw==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@mui/material': ^7.3.4
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@mui/material@7.3.4':
resolution: {integrity: sha512-gEQL9pbJZZHT7lYJBKQCS723v1MGys2IFc94COXbUIyCTWa+qC77a7hUax4Yjd5ggEm35dk4AyYABpKKWC4MLw==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
'@mui/material-pigment-css': ^7.3.3
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/react':
optional: true
'@emotion/styled':
optional: true
'@mui/material-pigment-css':
optional: true
'@types/react':
optional: true
'@mui/private-theming@7.3.3':
resolution: {integrity: sha512-OJM+9nj5JIyPUvsZ5ZjaeC9PfktmK+W5YaVLToLR8L0lB/DGmv1gcKE43ssNLSvpoW71Hct0necfade6+kW3zQ==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@mui/styled-engine@7.3.3':
resolution: {integrity: sha512-CmFxvRJIBCEaWdilhXMw/5wFJ1+FT9f3xt+m2pPXhHPeVIbBg9MnMvNSJjdALvnQJMPw8jLhrUtXmN7QAZV2fw==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.4.1
'@emotion/styled': ^11.3.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/react':
optional: true
'@emotion/styled':
optional: true
'@mui/system@7.3.3':
resolution: {integrity: sha512-Lqq3emZr5IzRLKaHPuMaLBDVaGvxoh6z7HMWd1RPKawBM5uMRaQ4ImsmmgXWtwJdfZux5eugfDhXJUo2mliS8Q==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@emotion/react': ^11.5.0
'@emotion/styled': ^11.3.0
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@emotion/react':
optional: true
'@emotion/styled':
optional: true
'@types/react':
optional: true
'@mui/types@7.4.7':
resolution: {integrity: sha512-8vVje9rdEr1rY8oIkYgP+Su5Kwl6ik7O3jQ0wl78JGSmiZhRHV+vkjooGdKD8pbtZbutXFVTWQYshu2b3sG9zw==}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@mui/utils@7.3.3':
resolution: {integrity: sha512-kwNAUh7bLZ7mRz9JZ+6qfRnnxbE4Zuc+RzXnhSpRSxjTlSTj7b4JxRLXpG+MVtPVtqks5k/XC8No1Vs3x4Z2gg==}
engines: {node: '>=14.0.0'}
peerDependencies:
'@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
peerDependenciesMeta:
'@types/react':
optional: true
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -375,6 +542,9 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
'@rolldown/pluginutils@1.0.0-beta.38':
resolution: {integrity: sha512-N/ICGKleNhA5nc9XXQG/kkKHJ7S55u0x0XUJbbkmdCnFuoRkM1Il12q9q0eX19+M7KKUEPw/daUPIRnxhcxAIw==}
@@ -509,11 +679,22 @@ packages:
'@types/node@24.7.0':
resolution: {integrity: sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==}
'@types/parse-json@4.0.2':
resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
'@types/prop-types@15.7.15':
resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
'@types/react-dom@19.2.1':
resolution: {integrity: sha512-/EEvYBdT3BflCWvTMO7YkYBHVE9Ci6XdqZciZANQgKpaiDRGOLIlRo91jbTNRQjgPFWVaRxcYc0luVNFitz57A==}
peerDependencies:
'@types/react': ^19.2.0
'@types/react-transition-group@4.4.12':
resolution: {integrity: sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==}
peerDependencies:
'@types/react': '*'
'@types/react@19.2.2':
resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==}
@@ -602,6 +783,10 @@ packages:
argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
babel-plugin-macros@3.1.0:
resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
engines: {node: '>=10', npm: '>=6'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -635,6 +820,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
color-convert@2.0.1:
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
engines: {node: '>=7.0.0'}
@@ -645,9 +834,20 @@ packages:
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
convert-source-map@1.9.0:
resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
cookie@1.0.2:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
cosmiconfig@7.1.0:
resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
engines: {node: '>=10'}
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -667,9 +867,15 @@ packages:
deep-is@0.1.4:
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
electron-to-chromium@1.5.233:
resolution: {integrity: sha512-iUdTQSf7EFXsDdQsp8MwJz5SVk4APEFqXU/S47OtQ0YLqacSwPXdZ5vRlMX3neb07Cy2vgioNuRnWUXFwuslkg==}
error-ex@1.3.4:
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
esbuild@0.25.10:
resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
engines: {node: '>=18'}
@@ -769,6 +975,9 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
find-root@1.1.0:
resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==}
find-up@5.0.0:
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
engines: {node: '>=10'}
@@ -785,6 +994,9 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
gensync@1.0.0-beta.2:
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
engines: {node: '>=6.9.0'}
@@ -812,6 +1024,13 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
hoist-non-react-statics@3.3.2:
resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==}
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@@ -828,6 +1047,13 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
is-core-module@2.16.1:
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
engines: {node: '>= 0.4'}
is-extglob@2.1.1:
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
engines: {node: '>=0.10.0'}
@@ -858,6 +1084,9 @@ packages:
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
@@ -876,6 +1105,9 @@ packages:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
lines-and-columns@1.2.4:
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -919,6 +1151,10 @@ packages:
node-releases@2.0.23:
resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -935,6 +1171,10 @@ packages:
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
engines: {node: '>=6'}
parse-json@5.2.0:
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
engines: {node: '>=8'}
path-exists@4.0.0:
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
engines: {node: '>=8'}
@@ -943,6 +1183,13 @@ packages:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
@@ -962,6 +1209,9 @@ packages:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -974,10 +1224,42 @@ packages:
peerDependencies:
react: ^18.3.1
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-is@18.3.1:
resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==}
react-is@19.2.0:
resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==}
react-refresh@0.17.0:
resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
engines: {node: '>=0.10.0'}
react-router-dom@7.9.4:
resolution: {integrity: sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
react-router@7.9.4:
resolution: {integrity: sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
peerDependenciesMeta:
react-dom:
optional: true
react-transition-group@4.4.5:
resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==}
peerDependencies:
react: '>=16.6.0'
react-dom: '>=16.6.0'
react@18.3.1:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
@@ -986,6 +1268,11 @@ packages:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
resolve@1.22.10:
resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==}
engines: {node: '>= 0.4'}
hasBin: true
reusify@1.1.0:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -1010,6 +1297,9 @@ packages:
engines: {node: '>=10'}
hasBin: true
set-cookie-parser@2.7.1:
resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -1022,14 +1312,25 @@ packages:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
source-map@0.5.7:
resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==}
engines: {node: '>=0.10.0'}
strip-json-comments@3.1.1:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
stylis@4.2.0:
resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==}
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
@@ -1124,6 +1425,10 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
yocto-queue@0.1.0:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
@@ -1219,6 +1524,8 @@ snapshots:
'@babel/core': 7.28.4
'@babel/helper-plugin-utils': 7.27.1
'@babel/runtime@7.28.4': {}
'@babel/template@7.27.2':
dependencies:
'@babel/code-frame': 7.27.1
@@ -1242,6 +1549,89 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
'@emotion/babel-plugin@11.13.5':
dependencies:
'@babel/helper-module-imports': 7.27.1
'@babel/runtime': 7.28.4
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/serialize': 1.3.3
babel-plugin-macros: 3.1.0
convert-source-map: 1.9.0
escape-string-regexp: 4.0.0
find-root: 1.1.0
source-map: 0.5.7
stylis: 4.2.0
transitivePeerDependencies:
- supports-color
'@emotion/cache@11.14.0':
dependencies:
'@emotion/memoize': 0.9.0
'@emotion/sheet': 1.4.0
'@emotion/utils': 1.4.2
'@emotion/weak-memoize': 0.4.0
stylis: 4.2.0
'@emotion/hash@0.9.2': {}
'@emotion/is-prop-valid@1.4.0':
dependencies:
'@emotion/memoize': 0.9.0
'@emotion/memoize@0.9.0': {}
'@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
'@emotion/utils': 1.4.2
'@emotion/weak-memoize': 0.4.0
hoist-non-react-statics: 3.3.2
react: 18.3.1
optionalDependencies:
'@types/react': 19.2.2
transitivePeerDependencies:
- supports-color
'@emotion/serialize@1.3.3':
dependencies:
'@emotion/hash': 0.9.2
'@emotion/memoize': 0.9.0
'@emotion/unitless': 0.10.0
'@emotion/utils': 1.4.2
csstype: 3.1.3
'@emotion/sheet@1.4.0': {}
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/babel-plugin': 11.13.5
'@emotion/is-prop-valid': 1.4.0
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@18.3.1)
'@emotion/serialize': 1.3.3
'@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@18.3.1)
'@emotion/utils': 1.4.2
react: 18.3.1
optionalDependencies:
'@types/react': 19.2.2
transitivePeerDependencies:
- supports-color
'@emotion/unitless@0.10.0': {}
'@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@18.3.1)':
dependencies:
react: 18.3.1
'@emotion/utils@1.4.2': {}
'@emotion/weak-memoize@0.4.0': {}
'@esbuild/aix-ppc64@0.25.10':
optional: true
@@ -1396,6 +1786,93 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.5
'@mui/core-downloads-tracker@7.3.4': {}
'@mui/icons-material@7.3.4(@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/material': 7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
optionalDependencies:
'@types/react': 19.2.2
'@mui/material@7.3.4(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/core-downloads-tracker': 7.3.4
'@mui/system': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
'@mui/types': 7.4.7(@types/react@19.2.2)
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@18.3.1)
'@popperjs/core': 2.11.8
'@types/react-transition-group': 4.4.12(@types/react@19.2.2)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-is: 19.2.0
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
'@types/react': 19.2.2
'@mui/private-theming@7.3.3(@types/react@19.2.2)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@18.3.1)
prop-types: 15.8.1
react: 18.3.1
optionalDependencies:
'@types/react': 19.2.2
'@mui/styled-engine@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@emotion/cache': 11.14.0
'@emotion/serialize': 1.3.3
'@emotion/sheet': 1.4.0
csstype: 3.1.3
prop-types: 15.8.1
react: 18.3.1
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
'@mui/system@7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/private-theming': 7.3.3(@types/react@19.2.2)(react@18.3.1)
'@mui/styled-engine': 7.3.3(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1))(react@18.3.1)
'@mui/types': 7.4.7(@types/react@19.2.2)
'@mui/utils': 7.3.3(@types/react@19.2.2)(react@18.3.1)
clsx: 2.1.1
csstype: 3.1.3
prop-types: 15.8.1
react: 18.3.1
optionalDependencies:
'@emotion/react': 11.14.0(@types/react@19.2.2)(react@18.3.1)
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.2)(react@18.3.1))(@types/react@19.2.2)(react@18.3.1)
'@types/react': 19.2.2
'@mui/types@7.4.7(@types/react@19.2.2)':
dependencies:
'@babel/runtime': 7.28.4
optionalDependencies:
'@types/react': 19.2.2
'@mui/utils@7.3.3(@types/react@19.2.2)(react@18.3.1)':
dependencies:
'@babel/runtime': 7.28.4
'@mui/types': 7.4.7(@types/react@19.2.2)
'@types/prop-types': 15.7.15
clsx: 2.1.1
prop-types: 15.8.1
react: 18.3.1
react-is: 19.2.0
optionalDependencies:
'@types/react': 19.2.2
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -1408,6 +1885,8 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@popperjs/core@2.11.8': {}
'@rolldown/pluginutils@1.0.0-beta.38': {}
'@rollup/rollup-android-arm-eabi@4.52.4':
@@ -1505,10 +1984,18 @@ snapshots:
dependencies:
undici-types: 7.14.0
'@types/parse-json@4.0.2': {}
'@types/prop-types@15.7.15': {}
'@types/react-dom@19.2.1(@types/react@19.2.2)':
dependencies:
'@types/react': 19.2.2
'@types/react-transition-group@4.4.12(@types/react@19.2.2)':
dependencies:
'@types/react': 19.2.2
'@types/react@19.2.2':
dependencies:
csstype: 3.1.3
@@ -1637,6 +2124,12 @@ snapshots:
argparse@2.0.1: {}
babel-plugin-macros@3.1.0:
dependencies:
'@babel/runtime': 7.28.4
cosmiconfig: 7.1.0
resolve: 1.22.10
balanced-match@1.0.2: {}
baseline-browser-mapping@2.8.14: {}
@@ -1671,6 +2164,8 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
clsx@2.1.1: {}
color-convert@2.0.1:
dependencies:
color-name: 1.1.4
@@ -1679,8 +2174,20 @@ snapshots:
concat-map@0.0.1: {}
convert-source-map@1.9.0: {}
convert-source-map@2.0.0: {}
cookie@1.0.2: {}
cosmiconfig@7.1.0:
dependencies:
'@types/parse-json': 4.0.2
import-fresh: 3.3.1
parse-json: 5.2.0
path-type: 4.0.0
yaml: 1.10.2
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -1695,8 +2202,17 @@ snapshots:
deep-is@0.1.4: {}
dom-helpers@5.2.1:
dependencies:
'@babel/runtime': 7.28.4
csstype: 3.1.3
electron-to-chromium@1.5.233: {}
error-ex@1.3.4:
dependencies:
is-arrayish: 0.2.1
esbuild@0.25.10:
optionalDependencies:
'@esbuild/aix-ppc64': 0.25.10
@@ -1835,6 +2351,8 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
find-root@1.1.0: {}
find-up@5.0.0:
dependencies:
locate-path: 6.0.0
@@ -1850,6 +2368,8 @@ snapshots:
fsevents@2.3.3:
optional: true
function-bind@1.1.2: {}
gensync@1.0.0-beta.2: {}
glob-parent@5.1.2:
@@ -1868,6 +2388,14 @@ snapshots:
has-flag@4.0.0: {}
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
hoist-non-react-statics@3.3.2:
dependencies:
react-is: 16.13.1
ignore@5.3.2: {}
ignore@7.0.5: {}
@@ -1879,6 +2407,12 @@ snapshots:
imurmurhash@0.1.4: {}
is-arrayish@0.2.1: {}
is-core-module@2.16.1:
dependencies:
hasown: 2.0.2
is-extglob@2.1.1: {}
is-glob@4.0.3:
@@ -1899,6 +2433,8 @@ snapshots:
json-buffer@3.0.1: {}
json-parse-even-better-errors@2.3.1: {}
json-schema-traverse@0.4.1: {}
json-stable-stringify-without-jsonify@1.0.1: {}
@@ -1914,6 +2450,8 @@ snapshots:
prelude-ls: 1.2.1
type-check: 0.4.0
lines-and-columns@1.2.4: {}
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
@@ -1951,6 +2489,8 @@ snapshots:
node-releases@2.0.23: {}
object-assign@4.1.1: {}
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -1972,10 +2512,21 @@ snapshots:
dependencies:
callsites: 3.1.0
parse-json@5.2.0:
dependencies:
'@babel/code-frame': 7.27.1
error-ex: 1.3.4
json-parse-even-better-errors: 2.3.1
lines-and-columns: 1.2.4
path-exists@4.0.0: {}
path-key@3.1.1: {}
path-parse@1.0.7: {}
path-type@4.0.0: {}
picocolors@1.1.1: {}
picomatch@2.3.1: {}
@@ -1990,6 +2541,12 @@ snapshots:
prelude-ls@1.2.1: {}
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
punycode@2.3.1: {}
queue-microtask@1.2.3: {}
@@ -2000,14 +2557,49 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
react-is@16.13.1: {}
react-is@18.3.1: {}
react-is@19.2.0: {}
react-refresh@0.17.0: {}
react-router-dom@7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-router: 7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react-router@7.9.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
cookie: 1.0.2
react: 18.3.1
set-cookie-parser: 2.7.1
optionalDependencies:
react-dom: 18.3.1(react@18.3.1)
react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.28.4
dom-helpers: 5.2.1
loose-envify: 1.4.0
prop-types: 15.8.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react@18.3.1:
dependencies:
loose-envify: 1.4.0
resolve-from@4.0.0: {}
resolve@1.22.10:
dependencies:
is-core-module: 2.16.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
reusify@1.1.0: {}
rollup@4.52.4:
@@ -2050,6 +2642,8 @@ snapshots:
semver@7.7.3: {}
set-cookie-parser@2.7.1: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -2058,12 +2652,18 @@ snapshots:
source-map-js@1.2.1: {}
source-map@0.5.7: {}
strip-json-comments@3.1.1: {}
stylis@4.2.0: {}
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
supports-preserve-symlinks-flag@1.0.0: {}
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
@@ -2126,4 +2726,6 @@ snapshots:
yallist@3.1.1: {}
yaml@1.10.2: {}
yocto-queue@0.1.0: {}

View File

@@ -1,35 +1,17 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { BrowserRouter } from 'react-router-dom';
import { CssBaseline, ThemeProvider } from '@mui/material';
import { theme } from './theme';
import AppRoutes from './routes';
function App() {
const [count, setCount] = useState(0)
return (
<>
<div>
<a href="https://vite.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
)
<ThemeProvider theme={theme}>
<CssBaseline />
<BrowserRouter>
<AppRoutes />
</BrowserRouter>
</ThemeProvider>
);
}
export default App
export default App;

View File

@@ -0,0 +1,73 @@
import { Box, InputBase, styled } from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import AccountCircleIcon from '@mui/icons-material/AccountCircle';
const HeaderContainer = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
padding: '0 20px',
height: '60px',
backgroundColor: '#FFFFFF',
color: '#333',
boxShadow: '0 1px 3px rgba(0,0,0,0.1)',
borderBottom: '1px solid #E5E5E5',
}));
const BrandTitle = styled(Box)(({ theme }) => ({
fontSize: '1.2rem',
fontWeight: 'bold',
color: '#333',
}));
const SearchBox = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
backgroundColor: '#F8F9FA',
borderRadius: '6px',
padding: '6px 12px',
width: '320px',
border: '1px solid #E5E5E5',
marginLeft: 'auto',
marginRight: '20px',
'&:focus-within': {
borderColor: theme.palette.primary.main,
boxShadow: `0 0 0 2px rgba(226,0,116,0.1)`,
},
'& .MuiInputBase-input': {
border: 'none',
outline: 'none',
backgroundColor: 'transparent',
fontSize: '14px',
color: '#333',
'&::placeholder': {
color: '#999',
},
},
}));
const SearchInput = styled(InputBase)(({ theme }) => ({
marginLeft: '8px',
flex: 1,
fontSize: '0.875rem',
}));
const UserAvatar = styled(AccountCircleIcon)(({ theme }) => ({
color: '#666',
cursor: 'pointer',
fontSize: '2rem',
}));
const Header = () => {
return (
<HeaderContainer>
<BrandTitle>RAG Dashboard</BrandTitle>
<SearchBox>
<SearchIcon sx={{ color: '#999', fontSize: '1.2rem' }} />
<SearchInput placeholder="Search queries, KB names..." />
</SearchBox>
<UserAvatar titleAccess="User Profile" />
</HeaderContainer>
);
};
export default Header;

View File

@@ -0,0 +1,39 @@
import { Box, styled } from '@mui/material';
import { Outlet } from 'react-router-dom';
import Header from './Header';
import Sidebar from './Sidebar';
const LayoutContainer = styled(Box)({
display: 'flex',
height: '100vh',
});
const MainContent = styled(Box)({
flex: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
});
const ContentArea = styled(Box)({
flex: 1,
padding: '20px',
overflow: 'auto',
backgroundColor: '#f5f7fa',
});
const MainLayout = () => {
return (
<LayoutContainer>
<Sidebar />
<MainContent>
<Header />
<ContentArea>
<Outlet />
</ContentArea>
</MainContent>
</LayoutContainer>
);
};
export default MainLayout;

View File

@@ -0,0 +1,73 @@
import { Box, List, ListItem, ListItemButton, ListItemText, Typography, styled } from '@mui/material';
import { Link, useLocation } from 'react-router-dom';
const SidebarContainer = styled(Box)(({ theme }) => ({
width: '240px',
backgroundColor: '#1E1E24',
color: '#DDD',
height: '100vh',
padding: '1rem 0',
display: 'flex',
flexDirection: 'column',
}));
const Logo = styled(Typography)(({ theme }) => ({
fontSize: '1.05rem',
fontWeight: 600,
padding: '0 1.25rem 1rem',
margin: '0 0 0.5rem',
color: '#FFF',
letterSpacing: '0.5px',
}));
const NavItem = styled(ListItemButton)<{ active?: boolean }>(({ active, theme }) => ({
color: active ? '#FFF' : '#B9B9C2',
backgroundColor: active ? 'rgba(226,0,116,0.12)' : 'transparent',
borderLeft: active ? `4px solid ${theme.palette.primary.main}` : '4px solid transparent',
fontWeight: active ? 600 : 'normal',
'&:hover': {
backgroundColor: 'rgba(255,255,255,0.05)',
color: '#FFF',
},
'& .MuiListItemText-primary': {
fontSize: '0.9rem',
},
}));
const Footer = styled(Box)(({ theme }) => ({
marginTop: 'auto',
padding: '20px',
fontSize: '0.75rem',
opacity: 0.7,
}));
const navItems = [
{ text: 'Overview', path: '/' },
{ text: 'Knowledge Bases', path: '/kb-list' },
{ text: 'RAG Pipeline', path: '/pipeline-config' },
{ text: 'Operations', path: '/dashboard' },
{ text: 'Models & Resources', path: '/models-resources' },
{ text: 'MCP', path: '/mcp' },
];
const Sidebar = () => {
const location = useLocation();
return (
<SidebarContainer>
<Logo>RAGflow Prototype</Logo>
<List>
{navItems.map((item) => (
<Link to={item.path} style={{ textDecoration: 'none', color: 'inherit' }}>
<NavItem active={location.pathname === item.path}>
<ListItemText primary={item.text} />
</NavItem>
</Link>
))}
</List>
<Footer>© 2025 RAG Demo</Footer>
</SidebarContainer>
);
};
export default Sidebar;

369
src/pages/Dashboard.tsx Normal file
View File

@@ -0,0 +1,369 @@
import React, { useState } from 'react';
import {
Box,
Typography,
Card,
CardContent,
Grid,
Button,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
Chip,
LinearProgress,
Select,
MenuItem,
FormControl,
InputLabel,
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Assessment as AssessmentIcon,
Speed as SpeedIcon,
Error as ErrorIcon,
CheckCircle as CheckCircleIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
const PageContainer = styled(Box)(({ theme }) => ({
padding: '1.5rem',
backgroundColor: '#F8F9FA',
minHeight: 'calc(100vh - 60px)',
}));
const PageHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
}));
const MetricCard = styled(Card)(({ theme }) => ({
height: '100%',
border: '1px solid #E5E5E5',
transition: 'all 0.2s ease-in-out',
'&:hover': {
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
},
}));
const MetricValue = styled(Typography)(({ theme }) => ({
fontSize: '2rem',
fontWeight: 700,
lineHeight: 1.2,
}));
const TrendIndicator = styled(Box)<{ trend: 'up' | 'down' }>(({ trend, theme }) => ({
display: 'flex',
alignItems: 'center',
gap: '0.25rem',
color: trend === 'up' ? '#28A745' : '#DC3545',
fontSize: '0.875rem',
fontWeight: 500,
}));
const StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
fontSize: '0.75rem',
height: '24px',
backgroundColor:
status === 'success' ? '#E8F5E8' :
status === 'warning' ? '#FFF3CD' :
status === 'error' ? '#F8D7DA' : '#F8F9FA',
color:
status === 'success' ? '#155724' :
status === 'warning' ? '#856404' :
status === 'error' ? '#721C24' : '#666',
}));
const mockMetrics = {
totalQueries: { value: 12847, trend: 'up', change: '+12.5%' },
avgResponseTime: { value: '2.3s', trend: 'down', change: '-8.2%' },
successRate: { value: '98.7%', trend: 'up', change: '+0.3%' },
activeUsers: { value: 1256, trend: 'up', change: '+5.7%' },
};
const mockRecentQueries = [
{
id: 1,
query: '如何配置RAG Pipeline的参数',
user: 'user@example.com',
status: 'success',
responseTime: '1.8s',
timestamp: '2024-01-15 14:30:25',
knowledgeBase: '产品文档库',
},
{
id: 2,
query: '客服系统的常见问题有哪些?',
user: 'admin@company.com',
status: 'success',
responseTime: '2.1s',
timestamp: '2024-01-15 14:28:15',
knowledgeBase: '客服FAQ',
},
{
id: 3,
query: '法律合规要求的详细说明',
user: 'legal@company.com',
status: 'warning',
responseTime: '4.2s',
timestamp: '2024-01-15 14:25:10',
knowledgeBase: '法律合规',
},
{
id: 4,
query: '员工培训流程和要求',
user: 'hr@company.com',
status: 'error',
responseTime: 'N/A',
timestamp: '2024-01-15 14:22:45',
knowledgeBase: '培训资料',
},
];
const Dashboard: React.FC = () => {
const [timeRange, setTimeRange] = useState('24h');
return (
<PageContainer>
<PageHeader>
<Typography variant="h4" fontWeight={600} color="#333">
</Typography>
<FormControl size="small" sx={{ minWidth: 120 }}>
<InputLabel></InputLabel>
<Select
value={timeRange}
onChange={(e) => setTimeRange(e.target.value)}
label="时间范围"
>
<MenuItem value="1h">1</MenuItem>
<MenuItem value="24h">24</MenuItem>
<MenuItem value="7d">7</MenuItem>
<MenuItem value="30d">30</MenuItem>
</Select>
</FormControl>
</PageHeader>
{/* 关键指标卡片 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={3}>
<MetricCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<MetricValue color="primary">
{mockMetrics.totalQueries.value.toLocaleString()}
</MetricValue>
<TrendIndicator trend={mockMetrics.totalQueries.trend}>
{mockMetrics.totalQueries.trend === 'up' ?
<TrendingUpIcon fontSize="small" /> :
<TrendingDownIcon fontSize="small" />
}
{mockMetrics.totalQueries.change}
</TrendIndicator>
</Box>
<AssessmentIcon color="primary" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</MetricCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<MetricCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<MetricValue color="warning.main">
{mockMetrics.avgResponseTime.value}
</MetricValue>
<TrendIndicator trend={mockMetrics.avgResponseTime.trend}>
{mockMetrics.avgResponseTime.trend === 'up' ?
<TrendingUpIcon fontSize="small" /> :
<TrendingDownIcon fontSize="small" />
}
{mockMetrics.avgResponseTime.change}
</TrendIndicator>
</Box>
<SpeedIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</MetricCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<MetricCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<MetricValue color="success.main">
{mockMetrics.successRate.value}
</MetricValue>
<TrendIndicator trend={mockMetrics.successRate.trend}>
{mockMetrics.successRate.trend === 'up' ?
<TrendingUpIcon fontSize="small" /> :
<TrendingDownIcon fontSize="small" />
}
{mockMetrics.successRate.change}
</TrendIndicator>
</Box>
<CheckCircleIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</MetricCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<MetricCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<MetricValue color="info.main">
{mockMetrics.activeUsers.value.toLocaleString()}
</MetricValue>
<TrendIndicator trend={mockMetrics.activeUsers.trend}>
{mockMetrics.activeUsers.trend === 'up' ?
<TrendingUpIcon fontSize="small" /> :
<TrendingDownIcon fontSize="small" />
}
{mockMetrics.activeUsers.change}
</TrendIndicator>
</Box>
<AssessmentIcon color="info" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</MetricCard>
</Grid>
</Grid>
{/* 系统状态 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<Box mb={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">CPU 使</Typography>
<Typography variant="body2" fontWeight={600}>45%</Typography>
</Box>
<LinearProgress variant="determinate" value={45} sx={{ height: 8, borderRadius: 4 }} />
</Box>
<Box mb={2}>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">使</Typography>
<Typography variant="body2" fontWeight={600}>67%</Typography>
</Box>
<LinearProgress variant="determinate" value={67} color="warning" sx={{ height: 8, borderRadius: 4 }} />
</Box>
<Box>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2">使</Typography>
<Typography variant="body2" fontWeight={600}>23%</Typography>
</Box>
<LinearProgress variant="determinate" value={23} color="success" sx={{ height: 8, borderRadius: 4 }} />
</Box>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<Box display="flex" flexDirection="column" gap={1}>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2"></Typography>
<StatusChip status="success" label="正常" size="small" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2">FAQ</Typography>
<StatusChip status="success" label="正常" size="small" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2"></Typography>
<StatusChip status="warning" label="同步中" size="small" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2"></Typography>
<StatusChip status="error" label="错误" size="small" />
</Box>
</Box>
</CardContent>
</Card>
</Grid>
</Grid>
{/* 最近查询记录 */}
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{mockRecentQueries.map((query) => (
<TableRow key={query.id} hover>
<TableCell>
<Typography variant="body2" sx={{ maxWidth: '300px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
{query.query}
</Typography>
</TableCell>
<TableCell>{query.user}</TableCell>
<TableCell>{query.knowledgeBase}</TableCell>
<TableCell>
<StatusChip status={query.status} label={
query.status === 'success' ? '成功' :
query.status === 'warning' ? '警告' : '失败'
} size="small" />
</TableCell>
<TableCell>{query.responseTime}</TableCell>
<TableCell>
<Typography variant="body2" color="text.secondary">
{query.timestamp}
</Typography>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</PageContainer>
);
};
export default Dashboard;

218
src/pages/Home.tsx Normal file
View File

@@ -0,0 +1,218 @@
import {
Box,
Button,
Card,
CardContent,
Grid,
LinearProgress,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Typography,
styled
} from '@mui/material';
const StyledCard = styled(Card)({
height: '100%',
display: 'flex',
flexDirection: 'column',
boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
borderRadius: '8px',
});
const CardTitle = styled(Typography)({
fontSize: '1rem',
fontWeight: 'bold',
marginBottom: '16px',
});
const MetricsContainer = styled(Box)({
display: 'flex',
justifyContent: 'space-between',
marginBottom: '16px',
});
const Metric = styled(Box)({
display: 'flex',
flexDirection: 'column',
});
const MetricValue = styled(Typography)({
fontSize: '1.5rem',
fontWeight: 'bold',
});
const MetricLabel = styled(Typography)({
fontSize: '0.75rem',
color: '#666',
});
const ProgressContainer = styled(Box)({
marginBottom: '16px',
});
const ProgressLabel = styled(Box)({
display: 'flex',
justifyContent: 'space-between',
fontSize: '0.65rem',
marginBottom: '4px',
});
const StyledLinearProgress = styled(LinearProgress)({
height: '8px',
borderRadius: '4px',
});
const StatusPill = styled(Box)<{ status?: string }>(({ status }) => ({
display: 'inline-block',
padding: '4px 8px',
borderRadius: '12px',
fontSize: '0.65rem',
fontWeight: 'bold',
backgroundColor: status === 'OK' ? '#e6f7ed' : '#ffebee',
color: status === 'OK' ? '#00a389' : '#d32f2f',
}));
const InlineNote = styled('span')({
fontSize: '0.75rem',
color: '#666',
fontWeight: 'normal',
marginLeft: '4px',
});
// 模拟数据
const recentQueries = [
{ query: 'How to reset device firmware?', latency: 732, source: 'manual.pdf', time: '09:21', status: 'OK' },
{ query: 'List authentication failure codes', latency: 801, source: 'auth_guide.html', time: '09:18', status: 'OK' },
{ query: 'Can we purge stale vectors?', latency: 915, source: 'system_kb', time: '09:10', status: 'OK' },
{ query: 'Explain retrieval scoring logic', latency: 845, source: 'design_notes', time: '08:57', status: 'OK' },
{ query: 'Pipeline concurrency limits?', latency: 1042, source: 'ops_doc', time: '08:43', status: 'OK' },
];
const Home = () => {
return (
<Box>
<Grid container spacing={3}>
{/* Knowledge Base Status Card */}
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<StyledCard>
<CardContent>
<CardTitle>Knowledge Base Status</CardTitle>
<MetricsContainer>
<Metric>
<MetricLabel>Documents</MetricLabel>
<MetricValue>4,218</MetricValue>
</Metric>
<Metric>
<MetricLabel>Sources</MetricLabel>
<MetricValue>17</MetricValue>
</Metric>
<Metric>
<MetricLabel>Vectors</MetricLabel>
<MetricValue>1.2M</MetricValue>
</Metric>
</MetricsContainer>
<ProgressContainer>
<ProgressLabel>
<span>Sync Progress</span>
<span>62%</span>
</ProgressLabel>
<StyledLinearProgress variant="determinate" value={62} />
</ProgressContainer>
<Button
variant="contained"
fullWidth
sx={{
backgroundColor: '#1a1a2e',
'&:hover': { backgroundColor: '#2a2a3e' }
}}
>
Create New Knowledge Base
</Button>
</CardContent>
</StyledCard>
</Grid>
{/* Recent Activity Card */}
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<StyledCard>
<CardContent>
<CardTitle>
Recent Activity <InlineNote>(latest 24h)</InlineNote>
</CardTitle>
<Box sx={{ fontSize: '0.7rem', lineHeight: 1.8 }}>
<Box>152 user queries processed</Box>
<Box>87 new documents ingested</Box>
<Box>4 pipeline adjustments</Box>
</Box>
<Box sx={{ marginTop: 'auto', fontSize: '0.65rem', opacity: 0.75, mt: 2 }}>
Latency stable at p95 820ms
</Box>
</CardContent>
</StyledCard>
</Grid>
{/* Model Overview Card */}
<Grid size={{ xs: 12, md: 6, lg: 4 }}>
<StyledCard>
<CardContent>
<CardTitle>Model Overview</CardTitle>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '0.4rem', fontSize: '0.7rem' }}>
<Box>Embedding Model: <strong>text-embedding-3-large</strong></Box>
<Box>Generator: <strong>gpt-4o-mini</strong></Box>
<Box>Reranker: <strong>cross-encoder-v2</strong></Box>
<Box>Chunking: 512 tokens</Box>
<Box>Retriever Top-K: 8</Box>
</Box>
<StatusPill status="OK" sx={{ mt: 1 }}>Healthy</StatusPill>
</CardContent>
</StyledCard>
</Grid>
{/* Recent RAG Queries Table */}
<Grid size={12}>
<StyledCard>
<CardContent>
<CardTitle>
Recent RAG Queries <InlineNote>(latest 5)</InlineNote>
</CardTitle>
<TableContainer>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Query</TableCell>
<TableCell>Latency (ms)</TableCell>
<TableCell>Source</TableCell>
<TableCell>Time</TableCell>
<TableCell>Status</TableCell>
</TableRow>
</TableHead>
<TableBody>
{recentQueries.map((row, index) => (
<TableRow key={index}>
<TableCell>{row.query}</TableCell>
<TableCell>{row.latency}</TableCell>
<TableCell>{row.source}</TableCell>
<TableCell>{row.time}</TableCell>
<TableCell>
<StatusPill status={row.status}>{row.status}</StatusPill>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</StyledCard>
</Grid>
</Grid>
</Box>
);
};
export default Home;

View File

@@ -0,0 +1,265 @@
import React, { useState } from 'react';
import {
Box,
Typography,
Button,
Card,
CardContent,
Grid,
Chip,
IconButton,
Menu,
MenuItem,
TextField,
InputAdornment,
Fab,
} from '@mui/material';
import {
Search as SearchIcon,
Add as AddIcon,
MoreVert as MoreVertIcon,
Folder as FolderIcon,
Description as DocumentIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
const PageContainer = styled(Box)(({ theme }) => ({
padding: '1.5rem',
backgroundColor: '#F8F9FA',
minHeight: 'calc(100vh - 60px)',
}));
const PageHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
}));
const SearchContainer = styled(Box)(({ theme }) => ({
display: 'flex',
gap: '1rem',
marginBottom: '1.5rem',
}));
const KBCard = styled(Card)(({ theme }) => ({
height: '100%',
transition: 'all 0.2s ease-in-out',
border: '1px solid #E5E5E5',
'&:hover': {
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
transform: 'translateY(-2px)',
},
}));
const StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
fontSize: '0.75rem',
height: '24px',
backgroundColor:
status === 'active' ? '#E8F5E8' :
status === 'processing' ? '#FFF3CD' : '#F8D7DA',
color:
status === 'active' ? '#155724' :
status === 'processing' ? '#856404' : '#721C24',
}));
const StatsBox = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
marginTop: '1rem',
padding: '0.75rem',
backgroundColor: '#F8F9FA',
borderRadius: '6px',
}));
const StatItem = styled(Box)(({ theme }) => ({
textAlign: 'center',
'& .number': {
fontSize: '1.25rem',
fontWeight: 600,
color: theme.palette.primary.main,
},
'& .label': {
fontSize: '0.75rem',
color: '#666',
marginTop: '0.25rem',
},
}));
const mockKnowledgeBases = [
{
id: 1,
name: '产品文档库',
description: '包含所有产品相关的技术文档和用户手册',
status: 'active',
documents: 156,
size: '2.3 GB',
lastUpdated: '2024-01-15',
},
{
id: 2,
name: '客服FAQ',
description: '常见问题解答和客服对话记录',
status: 'processing',
documents: 89,
size: '1.1 GB',
lastUpdated: '2024-01-14',
},
{
id: 3,
name: '法律合规',
description: '法律条文、合规要求和相关政策文档',
status: 'active',
documents: 234,
size: '3.7 GB',
lastUpdated: '2024-01-13',
},
{
id: 4,
name: '培训资料',
description: '员工培训材料和学习资源',
status: 'inactive',
documents: 67,
size: '890 MB',
lastUpdated: '2024-01-10',
},
];
const KnowledgeBaseList: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedKB, setSelectedKB] = useState<number | null>(null);
const handleMenuClick = (event: React.MouseEvent<HTMLElement>, kbId: number) => {
setAnchorEl(event.currentTarget);
setSelectedKB(kbId);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedKB(null);
};
const filteredKBs = mockKnowledgeBases.filter(kb =>
kb.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
kb.description.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<PageContainer>
<PageHeader>
<Typography variant="h4" fontWeight={600} color="#333">
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
sx={{ borderRadius: '6px' }}
>
</Button>
</PageHeader>
<SearchContainer>
<TextField
placeholder="搜索知识库..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="action" />
</InputAdornment>
),
}}
sx={{ width: '400px' }}
/>
</SearchContainer>
<Grid container spacing={3}>
{filteredKBs.map((kb) => (
<Grid key={kb.id} size={{xs:12, sm:6, md:4}}>
<KBCard>
<CardContent>
<Box display="flex" justifyContent="space-between" alignItems="flex-start">
<Box display="flex" alignItems="center" gap={1}>
<FolderIcon color="primary" />
<Typography variant="h6" fontWeight={600}>
{kb.name}
</Typography>
</Box>
<Box>
<StatusChip
status={kb.status}
label={
kb.status === 'active' ? '活跃' :
kb.status === 'processing' ? '处理中' : '未激活'
}
size="small"
/>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, kb.id)}
>
<MoreVertIcon />
</IconButton>
</Box>
</Box>
<Typography
variant="body2"
color="text.secondary"
sx={{ mt: 1, mb: 2 }}
>
{kb.description}
</Typography>
<StatsBox>
<StatItem>
<div className="number">{kb.documents}</div>
<div className="label"></div>
</StatItem>
<StatItem>
<div className="number">{kb.size}</div>
<div className="label"></div>
</StatItem>
</StatsBox>
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
: {kb.lastUpdated}
</Typography>
</CardContent>
</KBCard>
</Grid>
))}
</Grid>
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
</MenuItem>
</Menu>
<Fab
color="primary"
aria-label="add"
sx={{
position: 'fixed',
bottom: 24,
right: 24,
}}
>
<AddIcon />
</Fab>
</PageContainer>
);
};
export default KnowledgeBaseList;

262
src/pages/Login.tsx Normal file
View File

@@ -0,0 +1,262 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
Button,
Checkbox,
Container,
FormControlLabel,
TextField,
Typography,
Link,
styled
} from '@mui/material';
const LoginContainer = styled(Box)({
height: '100vh',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#f5f7fa',
});
const TopBar = styled(Box)({
height: '60px',
backgroundColor: '#1a1a2e',
display: 'flex',
alignItems: 'center',
padding: '0 20px',
});
const BrandLogo = styled(Box)({
width: '40px',
height: '40px',
backgroundColor: '#fff',
color: '#1a1a2e',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
borderRadius: '50%',
fontWeight: 'bold',
fontSize: '1.2rem',
});
const LoginMain = styled(Box)({
flex: 1,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
const LoginCard = styled(Box)({
width: '400px',
backgroundColor: '#fff',
borderRadius: '8px',
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
padding: '30px',
});
const ServiceName = styled(Typography)({
fontSize: '0.75rem',
color: '#666',
marginBottom: '10px',
});
const LoginTitle = styled(Typography)({
fontSize: '1.5rem',
fontWeight: 'bold',
marginBottom: '20px',
});
const LoginForm = styled('form')({
display: 'flex',
flexDirection: 'column',
gap: '20px',
});
const RememberRow = styled(Box)({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
});
const ActionButtons = styled(Box)({
display: 'flex',
gap: '10px',
marginTop: '10px',
});
const PrimaryButton = styled(Button)({
backgroundColor: '#e20074',
'&:hover': {
backgroundColor: '#c10062',
},
});
const SecondaryButton = styled(Button)({
color: '#333',
backgroundColor: '#f5f5f5',
'&:hover': {
backgroundColor: '#e0e0e0',
},
});
const HelpSection = styled(Box)({
marginTop: '20px',
textAlign: 'center',
fontSize: '0.875rem',
});
const SocialLogin = styled(Box)({
display: 'flex',
justifyContent: 'center',
gap: '10px',
marginTop: '15px',
});
const SocialButton = styled(Box)({
width: '30px',
height: '30px',
borderRadius: '50%',
backgroundColor: '#e0e0e0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
});
const Footer = styled(Box)({
height: '50px',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '0 20px',
borderTop: '1px solid #eee',
fontSize: '0.75rem',
color: '#666',
});
const LegalLinks = styled(Box)({
display: 'flex',
gap: '15px',
});
const Login = () => {
const [username, setUsername] = useState('');
const [rememberUser, setRememberUser] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [error, setError] = useState(false);
const navigate = useNavigate();
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
if (!username.trim()) {
setError(true);
return;
}
setIsSubmitting(true);
setError(false);
// 模拟登录过程
setTimeout(() => {
navigate('/');
}, 800);
};
const handleCancel = () => {
navigate('/');
};
return (
<LoginContainer>
<TopBar>
<BrandLogo>T</BrandLogo>
</TopBar>
<LoginMain>
<LoginCard>
<ServiceName>Servicename</ServiceName>
<LoginTitle>
Enter Login <br/> Username
</LoginTitle>
<LoginForm onSubmit={handleSubmit} noValidate>
<TextField
fullWidth
id="username"
name="username"
placeholder="Username"
autoComplete="username"
value={username}
onChange={(e) => setUsername(e.target.value)}
error={error}
required
/>
<RememberRow>
<FormControlLabel
control={
<Checkbox
checked={rememberUser}
onChange={(e) => setRememberUser(e.target.checked)}
id="rememberUser"
/>
}
label="Remember username"
/>
<Link href="#" variant="caption">
Forgot your username or password?
</Link>
</RememberRow>
<ActionButtons>
<PrimaryButton
type="submit"
variant="contained"
fullWidth
disabled={isSubmitting}
>
{isSubmitting ? 'Processing...' : 'Next'}
</PrimaryButton>
<SecondaryButton
type="button"
variant="contained"
fullWidth
onClick={handleCancel}
>
Cancel
</SecondaryButton>
</ActionButtons>
</LoginForm>
<HelpSection>
<Link href="#">Do you need help?</Link>
<Box mt={1}>
No account? <Link href="#">Sign up</Link> or log in with your social network account.
</Box>
</HelpSection>
<SocialLogin>
<SocialButton aria-label="Login with Facebook">
<Link href="#" underline="none" color="inherit">f</Link>
</SocialButton>
<SocialButton aria-label="Login with Twitter">
<Link href="#" underline="none" color="inherit">t</Link>
</SocialButton>
</SocialLogin>
</LoginCard>
</LoginMain>
<Footer>
<Box>© Deutsche Telekom AG</Box>
<LegalLinks>
<Link href="#" color="inherit">Imprint</Link>
<Link href="#" color="inherit">Data privacy</Link>
</LegalLinks>
</Footer>
</LoginContainer>
);
};
export default Login;

516
src/pages/MCP.tsx Normal file
View File

@@ -0,0 +1,516 @@
import React, { useState } from 'react';
import {
Box,
Typography,
Card,
CardContent,
Grid,
Button,
Switch,
FormControlLabel,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Chip,
IconButton,
Menu,
MenuItem,
TextField,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Alert,
Tabs,
Tab,
} from '@mui/material';
import {
Add as AddIcon,
MoreVert as MoreVertIcon,
Settings as SettingsIcon,
Link as LinkIcon,
Security as SecurityIcon,
Speed as SpeedIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
const PageContainer = styled(Box)(({ theme }) => ({
padding: '1.5rem',
backgroundColor: '#F8F9FA',
minHeight: 'calc(100vh - 60px)',
}));
const PageHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
}));
const StatusCard = styled(Card)(({ theme }) => ({
height: '100%',
border: '1px solid #E5E5E5',
transition: 'all 0.2s ease-in-out',
'&:hover': {
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
},
}));
const StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
fontSize: '0.75rem',
height: '24px',
backgroundColor:
status === 'connected' ? '#E8F5E8' :
status === 'connecting' ? '#FFF3CD' :
status === 'error' ? '#F8D7DA' : '#F8F9FA',
color:
status === 'connected' ? '#155724' :
status === 'connecting' ? '#856404' :
status === 'error' ? '#721C24' : '#666',
}));
const mockMCPServers = [
{
id: 1,
name: 'File System Server',
description: '文件系统操作服务器',
url: 'mcp://localhost:3001',
status: 'connected',
version: '1.0.0',
tools: ['read_file', 'write_file', 'list_directory'],
lastPing: '2024-01-15 14:30:25',
},
{
id: 2,
name: 'Database Server',
description: '数据库查询服务器',
url: 'mcp://db.example.com:3002',
status: 'connected',
version: '1.2.1',
tools: ['query_sql', 'execute_sql', 'get_schema'],
lastPing: '2024-01-15 14:30:20',
},
{
id: 3,
name: 'Web Scraper',
description: '网页抓取服务器',
url: 'mcp://scraper.example.com:3003',
status: 'connecting',
version: '0.9.5',
tools: ['scrape_url', 'extract_text', 'get_links'],
lastPing: '2024-01-15 14:28:15',
},
{
id: 4,
name: 'API Gateway',
description: 'API网关服务器',
url: 'mcp://api.example.com:3004',
status: 'error',
version: '2.1.0',
tools: ['call_api', 'auth_token', 'rate_limit'],
lastPing: '2024-01-15 14:25:10',
},
];
const MCP: React.FC = () => {
const [tabValue, setTabValue] = useState(0);
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedServer, setSelectedServer] = useState<number | null>(null);
const [dialogOpen, setDialogOpen] = useState(false);
const [newServer, setNewServer] = useState({
name: '',
url: '',
description: '',
});
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleMenuClick = (event: React.MouseEvent<HTMLElement>, serverId: number) => {
setAnchorEl(event.currentTarget);
setSelectedServer(serverId);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedServer(null);
};
const handleAddServer = () => {
setDialogOpen(true);
};
const handleDialogClose = () => {
setDialogOpen(false);
setNewServer({ name: '', url: '', description: '' });
};
const handleSaveServer = () => {
// 保存服务器逻辑
console.log('添加服务器:', newServer);
handleDialogClose();
};
const connectedServers = mockMCPServers.filter(s => s.status === 'connected').length;
const totalTools = mockMCPServers.reduce((acc, server) => acc + server.tools.length, 0);
return (
<PageContainer>
<PageHeader>
<Box>
<Typography variant="h4" fontWeight={600} color="#333">
MCP
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.5 }}>
Model Context Protocol -
</Typography>
</Box>
<Button
variant="contained"
startIcon={<AddIcon />}
onClick={handleAddServer}
sx={{ borderRadius: '6px' }}
>
</Button>
</PageHeader>
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={handleTabChange}>
<Tab label="服务器管理" />
<Tab label="工具配置" />
<Tab label="协议监控" />
</Tabs>
</Box>
{tabValue === 0 && (
<>
{/* 状态概览 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="success.main">
{connectedServers}
</Typography>
</Box>
<CheckCircleIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="primary">
{mockMCPServers.length}
</Typography>
</Box>
<LinkIcon color="primary" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="info.main">
{totalTools}
</Typography>
</Box>
<SettingsIcon color="info" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<StatusCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="warning.main">
45ms
</Typography>
</Box>
<SpeedIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</StatusCard>
</Grid>
</Grid>
{/* 服务器列表 */}
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
MCP
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell></TableCell>
<TableCell>URL</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{mockMCPServers.map((server) => (
<TableRow key={server.id} hover>
<TableCell>
<Box>
<Typography variant="body2" fontWeight={600}>
{server.name}
</Typography>
<Typography variant="caption" color="text.secondary">
{server.description}
</Typography>
</Box>
</TableCell>
<TableCell>
<Typography variant="body2" fontFamily="monospace">
{server.url}
</Typography>
</TableCell>
<TableCell>
<StatusChip
status={server.status}
label={
server.status === 'connected' ? '已连接' :
server.status === 'connecting' ? '连接中' : '错误'
}
size="small"
/>
</TableCell>
<TableCell>
<Chip
label={server.version}
size="small"
variant="outlined"
/>
</TableCell>
<TableCell>
<Typography variant="body2">
{server.tools.length}
</Typography>
</TableCell>
<TableCell>
<Typography variant="body2" color="text.secondary">
{server.lastPing}
</Typography>
</TableCell>
<TableCell>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, server.id)}
>
<MoreVertIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{tabValue === 1 && (
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<Alert severity="info" sx={{ mb: 2 }}>
MCP
</Alert>
<Grid container spacing={2}>
{mockMCPServers.map((server) => (
<Grid item xs={12} md={6} key={server.id}>
<Card variant="outlined">
<CardContent>
<Typography variant="subtitle1" fontWeight={600} mb={1}>
{server.name}
</Typography>
<Box display="flex" flexDirection="column" gap={1}>
{server.tools.map((tool, index) => (
<FormControlLabel
key={index}
control={<Switch defaultChecked size="small" />}
label={
<Typography variant="body2" fontFamily="monospace">
{tool}
</Typography>
}
/>
))}
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</CardContent>
</Card>
)}
{tabValue === 2 && (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
<SecurityIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
</Typography>
<Box display="flex" flexDirection="column" gap={2}>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2">SSL/TLS </Typography>
<CheckCircleIcon color="success" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2"></Typography>
<CheckCircleIcon color="success" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2">访</Typography>
<CheckCircleIcon color="success" />
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2"></Typography>
<ErrorIcon color="error" />
</Box>
</Box>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
<SpeedIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
</Typography>
<Box display="flex" flexDirection="column" gap={2}>
<Box>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2"></Typography>
<Typography variant="body2" fontWeight={600}>45ms</Typography>
</Box>
<Box bgcolor="#F8F9FA" p={1} borderRadius="4px">
<Typography variant="caption" color="text.secondary">
24
</Typography>
</Box>
</Box>
<Box>
<Box display="flex" justifyContent="space-between" mb={1}>
<Typography variant="body2"></Typography>
<Typography variant="body2" fontWeight={600} color="success.main">
99.2%
</Typography>
</Box>
<Box bgcolor="#F8F9FA" p={1} borderRadius="4px">
<Typography variant="caption" color="text.secondary">
24
</Typography>
</Box>
</Box>
</Box>
</CardContent>
</Card>
</Grid>
</Grid>
)}
{/* 菜单 */}
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}>
<SettingsIcon sx={{ mr: 1 }} fontSize="small" />
</MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
</MenuItem>
</Menu>
{/* 添加服务器对话框 */}
<Dialog open={dialogOpen} onClose={handleDialogClose} maxWidth="sm" fullWidth>
<DialogTitle> MCP </DialogTitle>
<DialogContent>
<Box display="flex" flexDirection="column" gap={2} pt={1}>
<TextField
fullWidth
label="服务器名称"
value={newServer.name}
onChange={(e) => setNewServer(prev => ({ ...prev, name: e.target.value }))}
/>
<TextField
fullWidth
label="服务器 URL"
placeholder="mcp://localhost:3000"
value={newServer.url}
onChange={(e) => setNewServer(prev => ({ ...prev, url: e.target.value }))}
/>
<TextField
fullWidth
label="描述"
multiline
rows={3}
value={newServer.description}
onChange={(e) => setNewServer(prev => ({ ...prev, description: e.target.value }))}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={handleDialogClose}></Button>
<Button onClick={handleSaveServer} variant="contained">
</Button>
</DialogActions>
</Dialog>
</PageContainer>
);
};
export default MCP;

View File

@@ -0,0 +1,540 @@
import React, { useState } from 'react';
import {
Box,
Typography,
Card,
CardContent,
Grid,
Button,
Chip,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Paper,
LinearProgress,
IconButton,
Menu,
MenuItem,
Tabs,
Tab,
TextField,
InputAdornment,
} from '@mui/material';
import {
Memory as MemoryIcon,
Storage as StorageIcon,
Speed as SpeedIcon,
CloudDownload as CloudDownloadIcon,
Settings as SettingsIcon,
MoreVert as MoreVertIcon,
Search as SearchIcon,
Add as AddIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
const PageContainer = styled(Box)(({ theme }) => ({
padding: '1.5rem',
backgroundColor: '#F8F9FA',
minHeight: 'calc(100vh - 60px)',
}));
const PageHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
}));
const ResourceCard = styled(Card)(({ theme }) => ({
height: '100%',
border: '1px solid #E5E5E5',
transition: 'all 0.2s ease-in-out',
'&:hover': {
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
},
}));
const StatusChip = styled(Chip)<{ status: string }>(({ status, theme }) => ({
fontSize: '0.75rem',
height: '24px',
backgroundColor:
status === 'active' ? '#E8F5E8' :
status === 'loading' ? '#FFF3CD' :
status === 'error' ? '#F8D7DA' : '#F8F9FA',
color:
status === 'active' ? '#155724' :
status === 'loading' ? '#856404' :
status === 'error' ? '#721C24' : '#666',
}));
const UsageBar = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: '0.5rem',
marginTop: '0.5rem',
}));
const mockModels = [
{
id: 1,
name: 'GPT-4',
type: 'LLM',
provider: 'OpenAI',
status: 'active',
usage: 75,
cost: '$234.56',
requests: '12.3K',
latency: '1.2s',
},
{
id: 2,
name: 'text-embedding-ada-002',
type: 'Embedding',
provider: 'OpenAI',
status: 'active',
usage: 45,
cost: '$89.12',
requests: '45.6K',
latency: '0.3s',
},
{
id: 3,
name: 'Claude-3-Sonnet',
type: 'LLM',
provider: 'Anthropic',
status: 'loading',
usage: 0,
cost: '$0.00',
requests: '0',
latency: 'N/A',
},
{
id: 4,
name: 'BGE-Large-ZH',
type: 'Embedding',
provider: 'BAAI',
status: 'error',
usage: 0,
cost: '$0.00',
requests: '0',
latency: 'N/A',
},
];
const mockResources = [
{
id: 1,
name: 'GPU-Server-01',
type: 'GPU',
specs: 'NVIDIA A100 80GB',
usage: 85,
status: 'active',
location: '北京机房',
},
{
id: 2,
name: 'CPU-Cluster-01',
type: 'CPU',
specs: '64 Core Intel Xeon',
usage: 45,
status: 'active',
location: '上海机房',
},
{
id: 3,
name: 'Storage-Pool-01',
type: 'Storage',
specs: '10TB NVMe SSD',
usage: 67,
status: 'active',
location: '深圳机房',
},
];
const ModelsResources: React.FC = () => {
const [tabValue, setTabValue] = useState(0);
const [searchTerm, setSearchTerm] = useState('');
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [selectedItem, setSelectedItem] = useState<number | null>(null);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleMenuClick = (event: React.MouseEvent<HTMLElement>, itemId: number) => {
setAnchorEl(event.currentTarget);
setSelectedItem(itemId);
};
const handleMenuClose = () => {
setAnchorEl(null);
setSelectedItem(null);
};
const filteredModels = mockModels.filter(model =>
model.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
model.provider.toLowerCase().includes(searchTerm.toLowerCase())
);
const filteredResources = mockResources.filter(resource =>
resource.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
resource.type.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<PageContainer>
<PageHeader>
<Typography variant="h4" fontWeight={600} color="#333">
</Typography>
<Button
variant="contained"
startIcon={<AddIcon />}
sx={{ borderRadius: '6px' }}
>
</Button>
</PageHeader>
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={handleTabChange}>
<Tab label="AI 模型" />
<Tab label="计算资源" />
</Tabs>
</Box>
<Box display="flex" gap={2} mb={3}>
<TextField
placeholder={tabValue === 0 ? "搜索模型..." : "搜索资源..."}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon color="action" />
</InputAdornment>
),
}}
sx={{ width: '400px' }}
/>
</Box>
{tabValue === 0 && (
<>
{/* 模型概览卡片 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="primary">
{mockModels.filter(m => m.status === 'active').length}
</Typography>
</Box>
<MemoryIcon color="primary" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="success.main">
57.9K
</Typography>
</Box>
<SpeedIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="warning.main">
$323.68
</Typography>
</Box>
<CloudDownloadIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
</Typography>
<Typography variant="h4" fontWeight={600} color="info.main">
0.8s
</Typography>
</Box>
<SpeedIcon color="info" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
</Grid>
{/* 模型列表 */}
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell>使</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredModels.map((model) => (
<TableRow key={model.id} hover>
<TableCell>
<Typography variant="body2" fontWeight={600}>
{model.name}
</Typography>
</TableCell>
<TableCell>
<Chip
label={model.type}
size="small"
variant="outlined"
color={model.type === 'LLM' ? 'primary' : 'secondary'}
/>
</TableCell>
<TableCell>{model.provider}</TableCell>
<TableCell>
<StatusChip
status={model.status}
label={
model.status === 'active' ? '活跃' :
model.status === 'loading' ? '加载中' : '错误'
}
size="small"
/>
</TableCell>
<TableCell>
<UsageBar>
<LinearProgress
variant="determinate"
value={model.usage}
sx={{ width: '60px', height: '6px', borderRadius: '3px' }}
color={model.usage > 80 ? 'error' : model.usage > 60 ? 'warning' : 'primary'}
/>
<Typography variant="body2" color="text.secondary">
{model.usage}%
</Typography>
</UsageBar>
</TableCell>
<TableCell>{model.requests}</TableCell>
<TableCell>{model.cost}</TableCell>
<TableCell>{model.latency}</TableCell>
<TableCell>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, model.id)}
>
<MoreVertIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
{tabValue === 1 && (
<>
{/* 资源概览卡片 */}
<Grid container spacing={3} sx={{ mb: 3 }}>
<Grid item xs={12} sm={6} md={4}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
GPU 使
</Typography>
<Typography variant="h4" fontWeight={600} color="error.main">
85%
</Typography>
</Box>
<MemoryIcon color="error" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
CPU 使
</Typography>
<Typography variant="h4" fontWeight={600} color="warning.main">
45%
</Typography>
</Box>
<SpeedIcon color="warning" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
<Grid item xs={12} sm={6} md={4}>
<ResourceCard>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between">
<Box>
<Typography color="text.secondary" variant="body2">
使
</Typography>
<Typography variant="h4" fontWeight={600} color="success.main">
67%
</Typography>
</Box>
<StorageIcon color="success" sx={{ fontSize: '3rem', opacity: 0.3 }} />
</Box>
</CardContent>
</ResourceCard>
</Grid>
</Grid>
{/* 资源列表 */}
<Card sx={{ border: '1px solid #E5E5E5' }}>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<TableContainer>
<Table>
<TableHead>
<TableRow>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell>使</TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
<TableCell></TableCell>
</TableRow>
</TableHead>
<TableBody>
{filteredResources.map((resource) => (
<TableRow key={resource.id} hover>
<TableCell>
<Typography variant="body2" fontWeight={600}>
{resource.name}
</Typography>
</TableCell>
<TableCell>
<Chip
label={resource.type}
size="small"
variant="outlined"
color={
resource.type === 'GPU' ? 'error' :
resource.type === 'CPU' ? 'warning' : 'success'
}
/>
</TableCell>
<TableCell>{resource.specs}</TableCell>
<TableCell>
<UsageBar>
<LinearProgress
variant="determinate"
value={resource.usage}
sx={{ width: '80px', height: '6px', borderRadius: '3px' }}
color={resource.usage > 80 ? 'error' : resource.usage > 60 ? 'warning' : 'primary'}
/>
<Typography variant="body2" color="text.secondary">
{resource.usage}%
</Typography>
</UsageBar>
</TableCell>
<TableCell>
<StatusChip
status={resource.status}
label={resource.status === 'active' ? '活跃' : '离线'}
size="small"
/>
</TableCell>
<TableCell>{resource.location}</TableCell>
<TableCell>
<IconButton
size="small"
onClick={(e) => handleMenuClick(e, resource.id)}
>
<MoreVertIcon />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
</CardContent>
</Card>
</>
)}
<Menu
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleMenuClose}
>
<MenuItem onClick={handleMenuClose}>
<SettingsIcon sx={{ mr: 1 }} fontSize="small" />
</MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose}></MenuItem>
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
</MenuItem>
</Menu>
</PageContainer>
);
};
export default ModelsResources;

View File

@@ -0,0 +1,349 @@
import React, { useState } from 'react';
import {
Box,
Typography,
Card,
CardContent,
Grid,
Button,
Switch,
FormControlLabel,
Slider,
TextField,
Select,
MenuItem,
FormControl,
InputLabel,
Chip,
Divider,
Alert,
LinearProgress,
} from '@mui/material';
import {
Settings as SettingsIcon,
PlayArrow as PlayIcon,
Stop as StopIcon,
Refresh as RefreshIcon,
Save as SaveIcon,
} from '@mui/icons-material';
import { styled } from '@mui/material/styles';
const PageContainer = styled(Box)(({ theme }) => ({
padding: '1.5rem',
backgroundColor: '#F8F9FA',
minHeight: 'calc(100vh - 60px)',
}));
const PageHeader = styled(Box)(({ theme }) => ({
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '1.5rem',
}));
const ConfigCard = styled(Card)(({ theme }) => ({
marginBottom: '1.5rem',
border: '1px solid #E5E5E5',
}));
const ConfigSection = styled(Box)(({ theme }) => ({
marginBottom: '1.5rem',
'&:last-child': {
marginBottom: 0,
},
}));
const StatusIndicator = styled(Box)<{ status: 'running' | 'stopped' | 'error' }>(({ status, theme }) => ({
display: 'inline-flex',
alignItems: 'center',
gap: '0.5rem',
padding: '0.25rem 0.75rem',
borderRadius: '12px',
fontSize: '0.875rem',
fontWeight: 500,
backgroundColor:
status === 'running' ? '#E8F5E8' :
status === 'stopped' ? '#F8F9FA' : '#F8D7DA',
color:
status === 'running' ? '#155724' :
status === 'stopped' ? '#666' : '#721C24',
'&::before': {
content: '""',
width: '8px',
height: '8px',
borderRadius: '50%',
backgroundColor:
status === 'running' ? '#28A745' :
status === 'stopped' ? '#6C757D' : '#DC3545',
},
}));
const PipelineConfig: React.FC = () => {
const [pipelineStatus, setPipelineStatus] = useState<'running' | 'stopped' | 'error'>('stopped');
const [config, setConfig] = useState({
enabled: false,
chunkSize: 512,
chunkOverlap: 50,
embeddingModel: 'text-embedding-ada-002',
retrievalTopK: 5,
temperature: 0.7,
maxTokens: 2048,
systemPrompt: '你是一个专业的AI助手请基于提供的知识库内容回答用户问题。',
});
const handleConfigChange = (key: string, value: any) => {
setConfig(prev => ({ ...prev, [key]: value }));
};
const handleStartPipeline = () => {
setPipelineStatus('running');
};
const handleStopPipeline = () => {
setPipelineStatus('stopped');
};
const handleSaveConfig = () => {
// 保存配置逻辑
console.log('保存配置:', config);
};
return (
<PageContainer>
<PageHeader>
<Box>
<Typography variant="h4" fontWeight={600} color="#333">
RAG Pipeline
</Typography>
<Box display="flex" alignItems="center" gap={2} mt={1}>
<StatusIndicator status={pipelineStatus}>
{pipelineStatus === 'running' ? '运行中' :
pipelineStatus === 'stopped' ? '已停止' : '错误'}
</StatusIndicator>
{pipelineStatus === 'running' && (
<LinearProgress sx={{ width: '200px', height: '6px', borderRadius: '3px' }} />
)}
</Box>
</Box>
<Box display="flex" gap={1}>
<Button
variant="outlined"
startIcon={<SaveIcon />}
onClick={handleSaveConfig}
>
</Button>
{pipelineStatus === 'running' ? (
<Button
variant="contained"
color="error"
startIcon={<StopIcon />}
onClick={handleStopPipeline}
>
</Button>
) : (
<Button
variant="contained"
startIcon={<PlayIcon />}
onClick={handleStartPipeline}
>
</Button>
)}
</Box>
</PageHeader>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<ConfigCard>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
<SettingsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
</Typography>
<ConfigSection>
<FormControlLabel
control={
<Switch
checked={config.enabled}
onChange={(e) => handleConfigChange('enabled', e.target.checked)}
/>
}
label="启用 RAG Pipeline"
/>
</ConfigSection>
<ConfigSection>
<Typography gutterBottom></Typography>
<Slider
value={config.chunkSize}
onChange={(_, value) => handleConfigChange('chunkSize', value)}
min={128}
max={2048}
step={64}
marks={[
{ value: 128, label: '128' },
{ value: 512, label: '512' },
{ value: 1024, label: '1024' },
{ value: 2048, label: '2048' },
]}
valueLabelDisplay="on"
/>
</ConfigSection>
<ConfigSection>
<Typography gutterBottom> (%)</Typography>
<Slider
value={config.chunkOverlap}
onChange={(_, value) => handleConfigChange('chunkOverlap', value)}
min={0}
max={50}
step={5}
valueLabelDisplay="on"
/>
</ConfigSection>
<ConfigSection>
<FormControl fullWidth>
<InputLabel></InputLabel>
<Select
value={config.embeddingModel}
onChange={(e) => handleConfigChange('embeddingModel', e.target.value)}
label="嵌入模型"
>
<MenuItem value="text-embedding-ada-002">text-embedding-ada-002</MenuItem>
<MenuItem value="text-embedding-3-small">text-embedding-3-small</MenuItem>
<MenuItem value="text-embedding-3-large">text-embedding-3-large</MenuItem>
</Select>
</FormControl>
</ConfigSection>
</CardContent>
</ConfigCard>
</Grid>
<Grid item xs={12} md={6}>
<ConfigCard>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
</Typography>
<ConfigSection>
<TextField
fullWidth
label="检索文档数量 (Top-K)"
type="number"
value={config.retrievalTopK}
onChange={(e) => handleConfigChange('retrievalTopK', parseInt(e.target.value))}
inputProps={{ min: 1, max: 20 }}
/>
</ConfigSection>
<ConfigSection>
<Typography gutterBottom></Typography>
<Slider
value={config.temperature}
onChange={(_, value) => handleConfigChange('temperature', value)}
min={0}
max={1}
step={0.1}
marks={[
{ value: 0, label: '0' },
{ value: 0.5, label: '0.5' },
{ value: 1, label: '1' },
]}
valueLabelDisplay="on"
/>
</ConfigSection>
<ConfigSection>
<TextField
fullWidth
label="最大输出Token数"
type="number"
value={config.maxTokens}
onChange={(e) => handleConfigChange('maxTokens', parseInt(e.target.value))}
inputProps={{ min: 256, max: 4096 }}
/>
</ConfigSection>
<ConfigSection>
<TextField
fullWidth
label="系统提示词"
multiline
rows={4}
value={config.systemPrompt}
onChange={(e) => handleConfigChange('systemPrompt', e.target.value)}
/>
</ConfigSection>
</CardContent>
</ConfigCard>
</Grid>
<Grid item xs={12}>
<ConfigCard>
<CardContent>
<Typography variant="h6" fontWeight={600} mb={2}>
Pipeline
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} sm={6} md={3}>
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
<Typography variant="h4" color="primary" fontWeight={600}>
1,234
</Typography>
<Typography variant="body2" color="text.secondary">
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
<Typography variant="h4" color="success.main" fontWeight={600}>
98.5%
</Typography>
<Typography variant="body2" color="text.secondary">
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
<Typography variant="h4" color="warning.main" fontWeight={600}>
2.3s
</Typography>
<Typography variant="body2" color="text.secondary">
</Typography>
</Box>
</Grid>
<Grid item xs={12} sm={6} md={3}>
<Box textAlign="center" p={2} bgcolor="#F8F9FA" borderRadius="6px">
<Typography variant="h4" color="info.main" fontWeight={600}>
156
</Typography>
<Typography variant="body2" color="text.secondary">
</Typography>
</Box>
</Grid>
</Grid>
{pipelineStatus === 'error' && (
<Alert severity="error" sx={{ mt: 2 }}>
Pipeline
</Alert>
)}
</CardContent>
</ConfigCard>
</Grid>
</Grid>
</PageContainer>
);
};
export default PipelineConfig;

32
src/routes/index.tsx Normal file
View File

@@ -0,0 +1,32 @@
import { Routes, Route, Navigate } from 'react-router-dom';
import MainLayout from '../components/Layout/MainLayout';
import Login from '../pages/Login';
import Home from '../pages/Home';
import KnowledgeBaseList from '../pages/KnowledgeBaseList';
import PipelineConfig from '../pages/PipelineConfig';
import Dashboard from '../pages/Dashboard';
import ModelsResources from '../pages/ModelsResources';
import MCP from '../pages/MCP';
const AppRoutes = () => {
return (
<Routes>
<Route path="/login" element={<Login />} />
{/* 使用MainLayout作为受保护路由的布局 */}
<Route path="/" element={<MainLayout />}>
<Route index element={<Home />} />
<Route path="kb-list" element={<KnowledgeBaseList />} />
<Route path="pipeline-config" element={<PipelineConfig />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="models-resources" element={<ModelsResources />} />
<Route path="mcp" element={<MCP />} />
</Route>
{/* 处理未匹配的路由 */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
);
};
export default AppRoutes;

164
src/theme/index.ts Normal file
View File

@@ -0,0 +1,164 @@
import { createTheme } from '@mui/material/styles';
// Company branding colors extracted from web_prototype CSS
const brandColors = {
primary: '#E20074',
primaryHover: '#C40062',
background: '#F0F0F0',
surface: '#FFFFFF',
text: '#222',
textSecondary: '#555',
border: '#E2E2E2',
borderStrong: '#CFCFCF',
success: '#1E9E59',
danger: '#D22C32',
warning: '#D89200',
};
export const theme = createTheme({
palette: {
primary: {
main: brandColors.primary,
dark: brandColors.primaryHover,
contrastText: '#FFFFFF',
},
secondary: {
main: brandColors.primary,
dark: brandColors.primaryHover,
},
background: {
default: brandColors.background,
paper: brandColors.surface,
},
text: {
primary: brandColors.text,
secondary: brandColors.textSecondary,
},
success: {
main: brandColors.success,
},
error: {
main: brandColors.danger,
},
warning: {
main: brandColors.warning,
},
divider: brandColors.border,
},
typography: {
fontFamily: '"Segoe UI", system-ui, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif',
h1: {
fontWeight: 700,
letterSpacing: '0.5px',
},
h2: {
fontWeight: 600,
letterSpacing: '0.5px',
},
h3: {
fontWeight: 600,
letterSpacing: '0.3px',
},
body1: {
fontSize: '0.875rem',
},
body2: {
fontSize: '0.75rem',
},
button: {
fontWeight: 600,
letterSpacing: '0.3px',
textTransform: 'none',
},
},
shape: {
borderRadius: 10,
},
shadows: [
'none',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 4px 12px -2px rgba(226,0,116,0.45)',
'0 6px 14px -2px rgba(226,0,116,0.5)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
'0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
],
components: {
MuiButton: {
styleOverrides: {
root: {
borderRadius: '8px',
padding: '0.65rem 1.1rem',
fontSize: '0.8rem',
fontWeight: 600,
letterSpacing: '0.3px',
},
contained: {
boxShadow: '0 4px 10px -2px rgba(226,0,116,0.45)',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: '0 6px 14px -2px rgba(226,0,116,0.5)',
},
},
},
},
MuiCard: {
styleOverrides: {
root: {
borderRadius: '10px',
border: `1px solid ${brandColors.border}`,
boxShadow: '0 2px 4px rgba(0,0,0,0.08), 0 4px 12px rgba(0,0,0,0.06)',
},
},
},
MuiTextField: {
styleOverrides: {
root: {
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: brandColors.primary,
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: brandColors.primary,
boxShadow: `0 0 0 3px rgba(226,0,116,0.25)`,
},
},
},
},
},
MuiLinearProgress: {
styleOverrides: {
root: {
height: '8px',
borderRadius: '4px',
backgroundColor: '#ECECEC',
},
bar: {
borderRadius: '4px',
background: `linear-gradient(90deg, ${brandColors.primary}, #FF4DA8)`,
},
},
},
},
});
export default theme;