diff --git a/package.json b/package.json
index 92980b6..4ce7a09 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c8af5be..7762c4c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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: {}
diff --git a/src/App.tsx b/src/App.tsx
index 3d7ded3..50e65f3 100644
--- a/src/App.tsx
+++ b/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 (
- <>
-
- Vite + React
-
-
-
- Edit src/App.tsx and save to test HMR
-
-
-
- Click on the Vite and React logos to learn more
-
- >
- )
+
+
+
+
+
+
+ );
}
-export default App
+export default App;
diff --git a/src/components/Layout/Header.tsx b/src/components/Layout/Header.tsx
new file mode 100644
index 0000000..a704839
--- /dev/null
+++ b/src/components/Layout/Header.tsx
@@ -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 (
+
+ RAG Dashboard
+
+
+
+
+
+
+ );
+};
+
+export default Header;
\ No newline at end of file
diff --git a/src/components/Layout/MainLayout.tsx b/src/components/Layout/MainLayout.tsx
new file mode 100644
index 0000000..91f4027
--- /dev/null
+++ b/src/components/Layout/MainLayout.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default MainLayout;
\ No newline at end of file
diff --git a/src/components/Layout/Sidebar.tsx b/src/components/Layout/Sidebar.tsx
new file mode 100644
index 0000000..271fe09
--- /dev/null
+++ b/src/components/Layout/Sidebar.tsx
@@ -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 (
+
+ RAGflow Prototype
+
+ {navItems.map((item) => (
+
+
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default Sidebar;
\ No newline at end of file
diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx
new file mode 100644
index 0000000..f675e66
--- /dev/null
+++ b/src/pages/Dashboard.tsx
@@ -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 (
+
+
+
+ 运营监控
+
+
+ 时间范围
+
+
+
+
+ {/* 关键指标卡片 */}
+
+
+
+
+
+
+
+ 总查询数
+
+
+ {mockMetrics.totalQueries.value.toLocaleString()}
+
+
+ {mockMetrics.totalQueries.trend === 'up' ?
+ :
+
+ }
+ {mockMetrics.totalQueries.change}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 平均响应时间
+
+
+ {mockMetrics.avgResponseTime.value}
+
+
+ {mockMetrics.avgResponseTime.trend === 'up' ?
+ :
+
+ }
+ {mockMetrics.avgResponseTime.change}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 成功率
+
+
+ {mockMetrics.successRate.value}
+
+
+ {mockMetrics.successRate.trend === 'up' ?
+ :
+
+ }
+ {mockMetrics.successRate.change}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 活跃用户
+
+
+ {mockMetrics.activeUsers.value.toLocaleString()}
+
+
+ {mockMetrics.activeUsers.trend === 'up' ?
+ :
+
+ }
+ {mockMetrics.activeUsers.change}
+
+
+
+
+
+
+
+
+
+ {/* 系统状态 */}
+
+
+
+
+
+ 系统状态
+
+
+
+ CPU 使用率
+ 45%
+
+
+
+
+
+ 内存使用率
+ 67%
+
+
+
+
+
+ 存储使用率
+ 23%
+
+
+
+
+
+
+
+
+
+
+
+ 知识库状态
+
+
+
+ 产品文档库
+
+
+
+ 客服FAQ
+
+
+
+ 法律合规
+
+
+
+ 培训资料
+
+
+
+
+
+
+
+
+ {/* 最近查询记录 */}
+
+
+
+ 最近查询记录
+
+
+
+
+
+ 查询内容
+ 用户
+ 知识库
+ 状态
+ 响应时间
+ 时间
+
+
+
+ {mockRecentQueries.map((query) => (
+
+
+
+ {query.query}
+
+
+ {query.user}
+ {query.knowledgeBase}
+
+
+
+ {query.responseTime}
+
+
+ {query.timestamp}
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+};
+
+export default Dashboard;
\ No newline at end of file
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
new file mode 100644
index 0000000..27431bf
--- /dev/null
+++ b/src/pages/Home.tsx
@@ -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 (
+
+
+ {/* Knowledge Base Status Card */}
+
+
+
+ Knowledge Base Status
+
+
+ Documents
+ 4,218
+
+
+ Sources
+ 17
+
+
+ Vectors
+ 1.2M
+
+
+
+
+
+ Sync Progress
+ 62%
+
+
+
+
+
+
+
+
+
+ {/* Recent Activity Card */}
+
+
+
+
+ Recent Activity (latest 24h)
+
+
+ 152 user queries processed
+ 87 new documents ingested
+ 4 pipeline adjustments
+
+
+ Latency stable at p95 820ms
+
+
+
+
+
+ {/* Model Overview Card */}
+
+
+
+ Model Overview
+
+ Embedding Model: text-embedding-3-large
+ Generator: gpt-4o-mini
+ Reranker: cross-encoder-v2
+ Chunking: 512 tokens
+ Retriever Top-K: 8
+
+ Healthy
+
+
+
+
+ {/* Recent RAG Queries Table */}
+
+
+
+
+ Recent RAG Queries (latest 5)
+
+
+
+
+
+ Query
+ Latency (ms)
+ Source
+ Time
+ Status
+
+
+
+ {recentQueries.map((row, index) => (
+
+ {row.query}
+ {row.latency}
+ {row.source}
+ {row.time}
+
+ {row.status}
+
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Home;
\ No newline at end of file
diff --git a/src/pages/KnowledgeBaseList.tsx b/src/pages/KnowledgeBaseList.tsx
new file mode 100644
index 0000000..2cb1364
--- /dev/null
+++ b/src/pages/KnowledgeBaseList.tsx
@@ -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);
+ const [selectedKB, setSelectedKB] = useState(null);
+
+ const handleMenuClick = (event: React.MouseEvent, 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 (
+
+
+
+ 知识库管理
+
+ }
+ sx={{ borderRadius: '6px' }}
+ >
+ 新建知识库
+
+
+
+
+ setSearchTerm(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{ width: '400px' }}
+ />
+
+
+
+ {filteredKBs.map((kb) => (
+
+
+
+
+
+
+
+ {kb.name}
+
+
+
+
+ handleMenuClick(e, kb.id)}
+ >
+
+
+
+
+
+
+ {kb.description}
+
+
+
+
+ {kb.documents}
+ 文档数量
+
+
+ {kb.size}
+ 存储大小
+
+
+
+
+ 最后更新: {kb.lastUpdated}
+
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default KnowledgeBaseList;
\ No newline at end of file
diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx
new file mode 100644
index 0000000..f9bdb85
--- /dev/null
+++ b/src/pages/Login.tsx
@@ -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 (
+
+
+ T
+
+
+
+
+ Servicename
+
+ Enter Login
Username
+
+
+
+ setUsername(e.target.value)}
+ error={error}
+ required
+ />
+
+
+ setRememberUser(e.target.checked)}
+ id="rememberUser"
+ />
+ }
+ label="Remember username"
+ />
+
+ Forgot your username or password?
+
+
+
+
+
+ {isSubmitting ? 'Processing...' : 'Next'}
+
+
+ Cancel
+
+
+
+
+
+ Do you need help?
+
+ No account? Sign up or log in with your social network account.
+
+
+
+
+
+ f
+
+
+ t
+
+
+
+
+
+
+
+ );
+};
+
+export default Login;
\ No newline at end of file
diff --git a/src/pages/MCP.tsx b/src/pages/MCP.tsx
new file mode 100644
index 0000000..5e916a6
--- /dev/null
+++ b/src/pages/MCP.tsx
@@ -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);
+ const [selectedServer, setSelectedServer] = useState(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, 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 (
+
+
+
+
+ MCP 协议管理
+
+
+ Model Context Protocol - 模型上下文协议服务器管理
+
+
+ }
+ onClick={handleAddServer}
+ sx={{ borderRadius: '6px' }}
+ >
+ 添加服务器
+
+
+
+
+
+
+
+
+
+
+
+ {tabValue === 0 && (
+ <>
+ {/* 状态概览 */}
+
+
+
+
+
+
+
+ 连接服务器
+
+
+ {connectedServers}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 总服务器数
+
+
+ {mockMCPServers.length}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 可用工具
+
+
+ {totalTools}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 平均延迟
+
+
+ 45ms
+
+
+
+
+
+
+
+
+
+ {/* 服务器列表 */}
+
+
+
+ MCP 服务器列表
+
+
+
+
+
+ 服务器名称
+ URL
+ 状态
+ 版本
+ 工具数量
+ 最后心跳
+ 操作
+
+
+
+ {mockMCPServers.map((server) => (
+
+
+
+
+ {server.name}
+
+
+ {server.description}
+
+
+
+
+
+ {server.url}
+
+
+
+
+
+
+
+
+
+
+ {server.tools.length} 个工具
+
+
+
+
+ {server.lastPing}
+
+
+
+ handleMenuClick(e, server.id)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+
+ >
+ )}
+
+ {tabValue === 1 && (
+
+
+
+ 工具配置
+
+
+ 配置各个 MCP 服务器提供的工具和权限设置
+
+
+ {mockMCPServers.map((server) => (
+
+
+
+
+ {server.name}
+
+
+ {server.tools.map((tool, index) => (
+ }
+ label={
+
+ {tool}
+
+ }
+ />
+ ))}
+
+
+
+
+ ))}
+
+
+
+ )}
+
+ {tabValue === 2 && (
+
+
+
+
+
+
+ 安全监控
+
+
+
+ SSL/TLS 加密
+
+
+
+ 身份验证
+
+
+
+ 访问控制
+
+
+
+ 审计日志
+
+
+
+
+
+
+
+
+
+
+
+
+ 性能监控
+
+
+
+
+ 平均响应时间
+ 45ms
+
+
+
+ 过去24小时内的平均值
+
+
+
+
+
+ 成功率
+
+ 99.2%
+
+
+
+
+ 过去24小时内的成功率
+
+
+
+
+
+
+
+
+ )}
+
+ {/* 菜单 */}
+
+
+ {/* 添加服务器对话框 */}
+
+
+ );
+};
+
+export default MCP;
\ No newline at end of file
diff --git a/src/pages/ModelsResources.tsx b/src/pages/ModelsResources.tsx
new file mode 100644
index 0000000..43889fc
--- /dev/null
+++ b/src/pages/ModelsResources.tsx
@@ -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);
+ const [selectedItem, setSelectedItem] = useState(null);
+
+ const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
+ setTabValue(newValue);
+ };
+
+ const handleMenuClick = (event: React.MouseEvent, 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 (
+
+
+
+ 模型与资源
+
+ }
+ sx={{ borderRadius: '6px' }}
+ >
+ 添加模型
+
+
+
+
+
+
+
+
+
+
+
+ setSearchTerm(e.target.value)}
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ sx={{ width: '400px' }}
+ />
+
+
+ {tabValue === 0 && (
+ <>
+ {/* 模型概览卡片 */}
+
+
+
+
+
+
+
+ 活跃模型
+
+
+ {mockModels.filter(m => m.status === 'active').length}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 总请求数
+
+
+ 57.9K
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 总成本
+
+
+ $323.68
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 平均延迟
+
+
+ 0.8s
+
+
+
+
+
+
+
+
+
+ {/* 模型列表 */}
+
+
+
+ 模型列表
+
+
+
+
+
+ 模型名称
+ 类型
+ 提供商
+ 状态
+ 使用率
+ 请求数
+ 成本
+ 延迟
+ 操作
+
+
+
+ {filteredModels.map((model) => (
+
+
+
+ {model.name}
+
+
+
+
+
+ {model.provider}
+
+
+
+
+
+ 80 ? 'error' : model.usage > 60 ? 'warning' : 'primary'}
+ />
+
+ {model.usage}%
+
+
+
+ {model.requests}
+ {model.cost}
+ {model.latency}
+
+ handleMenuClick(e, model.id)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+
+ >
+ )}
+
+ {tabValue === 1 && (
+ <>
+ {/* 资源概览卡片 */}
+
+
+
+
+
+
+
+ GPU 使用率
+
+
+ 85%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ CPU 使用率
+
+
+ 45%
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 存储使用率
+
+
+ 67%
+
+
+
+
+
+
+
+
+
+ {/* 资源列表 */}
+
+
+
+ 计算资源
+
+
+
+
+
+ 资源名称
+ 类型
+ 规格
+ 使用率
+ 状态
+ 位置
+ 操作
+
+
+
+ {filteredResources.map((resource) => (
+
+
+
+ {resource.name}
+
+
+
+
+
+ {resource.specs}
+
+
+ 80 ? 'error' : resource.usage > 60 ? 'warning' : 'primary'}
+ />
+
+ {resource.usage}%
+
+
+
+
+
+
+ {resource.location}
+
+ handleMenuClick(e, resource.id)}
+ >
+
+
+
+
+ ))}
+
+
+
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default ModelsResources;
\ No newline at end of file
diff --git a/src/pages/PipelineConfig.tsx b/src/pages/PipelineConfig.tsx
new file mode 100644
index 0000000..27d28df
--- /dev/null
+++ b/src/pages/PipelineConfig.tsx
@@ -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 (
+
+
+
+
+ RAG Pipeline 配置
+
+
+
+ {pipelineStatus === 'running' ? '运行中' :
+ pipelineStatus === 'stopped' ? '已停止' : '错误'}
+
+ {pipelineStatus === 'running' && (
+
+ )}
+
+
+
+ }
+ onClick={handleSaveConfig}
+ >
+ 保存配置
+
+ {pipelineStatus === 'running' ? (
+ }
+ onClick={handleStopPipeline}
+ >
+ 停止
+
+ ) : (
+ }
+ onClick={handleStartPipeline}
+ >
+ 启动
+
+ )}
+
+
+
+
+
+
+
+
+
+ 基础配置
+
+
+
+ handleConfigChange('enabled', e.target.checked)}
+ />
+ }
+ label="启用 RAG Pipeline"
+ />
+
+
+
+ 文档分块大小
+ 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"
+ />
+
+
+
+ 分块重叠 (%)
+ handleConfigChange('chunkOverlap', value)}
+ min={0}
+ max={50}
+ step={5}
+ valueLabelDisplay="on"
+ />
+
+
+
+
+ 嵌入模型
+
+
+
+
+
+
+
+
+
+
+
+ 检索与生成配置
+
+
+
+ handleConfigChange('retrievalTopK', parseInt(e.target.value))}
+ inputProps={{ min: 1, max: 20 }}
+ />
+
+
+
+ 生成温度
+ 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"
+ />
+
+
+
+ handleConfigChange('maxTokens', parseInt(e.target.value))}
+ inputProps={{ min: 256, max: 4096 }}
+ />
+
+
+
+ handleConfigChange('systemPrompt', e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+ Pipeline 状态监控
+
+
+
+
+
+
+ 1,234
+
+
+ 已处理文档
+
+
+
+
+
+
+ 98.5%
+
+
+ 成功率
+
+
+
+
+
+
+ 2.3s
+
+
+ 平均响应时间
+
+
+
+
+
+
+ 156
+
+
+ 今日查询数
+
+
+
+
+
+ {pipelineStatus === 'error' && (
+
+ Pipeline 运行出现错误,请检查配置和日志。
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default PipelineConfig;
\ No newline at end of file
diff --git a/src/routes/index.tsx b/src/routes/index.tsx
new file mode 100644
index 0000000..6d28e14
--- /dev/null
+++ b/src/routes/index.tsx
@@ -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 (
+
+ } />
+
+ {/* 使用MainLayout作为受保护路由的布局 */}
+ }>
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+ {/* 处理未匹配的路由 */}
+ } />
+
+ );
+};
+
+export default AppRoutes;
\ No newline at end of file
diff --git a/src/theme/index.ts b/src/theme/index.ts
new file mode 100644
index 0000000..32af04a
--- /dev/null
+++ b/src/theme/index.ts
@@ -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;
\ No newline at end of file