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:
@@ -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
602
pnpm-lock.yaml
generated
@@ -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: {}
|
||||
|
||||
42
src/App.tsx
42
src/App.tsx
@@ -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;
|
||||
|
||||
73
src/components/Layout/Header.tsx
Normal file
73
src/components/Layout/Header.tsx
Normal 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;
|
||||
39
src/components/Layout/MainLayout.tsx
Normal file
39
src/components/Layout/MainLayout.tsx
Normal 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;
|
||||
73
src/components/Layout/Sidebar.tsx
Normal file
73
src/components/Layout/Sidebar.tsx
Normal 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
369
src/pages/Dashboard.tsx
Normal 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
218
src/pages/Home.tsx
Normal 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;
|
||||
265
src/pages/KnowledgeBaseList.tsx
Normal file
265
src/pages/KnowledgeBaseList.tsx
Normal 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
262
src/pages/Login.tsx
Normal 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
516
src/pages/MCP.tsx
Normal 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;
|
||||
540
src/pages/ModelsResources.tsx
Normal file
540
src/pages/ModelsResources.tsx
Normal 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;
|
||||
349
src/pages/PipelineConfig.tsx
Normal file
349
src/pages/PipelineConfig.tsx
Normal 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
32
src/routes/index.tsx
Normal 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
164
src/theme/index.ts
Normal 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;
|
||||
Reference in New Issue
Block a user