Refactor model selection and info display
Browse files- Introduced ModelInfo component to encapsulate model details and stats.
- Updated App component to utilize ModelInfo and streamline model fetching logic.
- Enhanced ModelSelector with sorting functionality and improved UI using Headless UI Listbox.
- Implemented PipelineSelector with a similar UI enhancement.
- Removed redundant model info fetching from ZeroShotClassification.
- Updated ModelContext to manage selected quantization state.
- Enhanced getModelInfo function to include compatibility checks and supported quantizations.
- Updated types to reflect new model info structure and quantization types.
- package.json +1 -0
- pnpm-lock.yaml +196 -0
- src/App.tsx +29 -77
- src/components/ModelInfo.tsx +164 -0
- src/components/ModelSelector.tsx +198 -115
- src/components/PipelineSelector.tsx +74 -7
- src/components/ZeroShotClassification.tsx +0 -31
- src/contexts/ModelContext.tsx +6 -1
- src/lib/huggingface.ts +74 -12
- src/types.ts +24 -0
package.json
CHANGED
@@ -3,6 +3,7 @@
|
|
3 |
"version": "0.1.0",
|
4 |
"private": true,
|
5 |
"dependencies": {
|
|
|
6 |
"@huggingface/transformers": "^3.6.1",
|
7 |
"@testing-library/dom": "^10.4.0",
|
8 |
"@testing-library/jest-dom": "^6.6.3",
|
|
|
3 |
"version": "0.1.0",
|
4 |
"private": true,
|
5 |
"dependencies": {
|
6 |
+
"@headlessui/react": "^2.2.4",
|
7 |
"@huggingface/transformers": "^3.6.1",
|
8 |
"@testing-library/dom": "^10.4.0",
|
9 |
"@testing-library/jest-dom": "^6.6.3",
|
pnpm-lock.yaml
CHANGED
@@ -8,6 +8,9 @@ importers:
|
|
8 |
|
9 |
.:
|
10 |
dependencies:
|
|
|
|
|
|
|
11 |
'@huggingface/transformers':
|
12 |
specifier: ^3.6.1
|
13 |
version: 3.6.1
|
@@ -917,6 +920,34 @@ packages:
|
|
917 |
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
|
918 |
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
919 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
920 |
'@huggingface/[email protected]':
|
921 |
resolution: {integrity: sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw==}
|
922 |
engines: {node: '>=18'}
|
@@ -1233,6 +1264,43 @@ packages:
|
|
1233 |
'@protobufjs/[email protected]':
|
1234 |
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
1235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1236 |
'@rollup/[email protected]':
|
1237 |
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
1238 |
engines: {node: '>= 10.0.0'}
|
@@ -1335,6 +1403,18 @@ packages:
|
|
1335 |
resolution: {integrity: sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==}
|
1336 |
engines: {node: '>=10'}
|
1337 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1338 |
'@testing-library/[email protected]':
|
1339 |
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
|
1340 |
engines: {node: '>=18'}
|
@@ -2076,6 +2156,10 @@ packages:
|
|
2076 | |
2077 |
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
|
2078 |
|
|
|
|
|
|
|
|
|
2079 | |
2080 |
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
2081 |
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
@@ -5377,6 +5461,9 @@ packages:
|
|
5377 | |
5378 |
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
5379 |
|
|
|
|
|
|
|
5380 | |
5381 |
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
|
5382 |
engines: {node: '>=14.0.0'}
|
@@ -5619,6 +5706,11 @@ packages:
|
|
5619 | |
5620 |
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
5621 |
|
|
|
|
|
|
|
|
|
|
|
5622 | |
5623 |
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
5624 |
|
@@ -6951,6 +7043,41 @@ snapshots:
|
|
6951 |
|
6952 |
'@eslint/[email protected]': {}
|
6953 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6954 |
'@huggingface/[email protected]': {}
|
6955 |
|
6956 |
'@huggingface/[email protected]':
|
@@ -7331,6 +7458,55 @@ snapshots:
|
|
7331 |
|
7332 |
'@protobufjs/[email protected]': {}
|
7333 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7334 |
'@rollup/[email protected](@babel/[email protected])(@types/[email protected])([email protected])':
|
7335 |
dependencies:
|
7336 |
'@babel/core': 7.28.0
|
@@ -7453,6 +7629,18 @@ snapshots:
|
|
7453 |
transitivePeerDependencies:
|
7454 |
- supports-color
|
7455 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7456 |
'@testing-library/[email protected]':
|
7457 |
dependencies:
|
7458 |
'@babel/code-frame': 7.27.1
|
@@ -8376,6 +8564,8 @@ snapshots:
|
|
8376 |
strip-ansi: 6.0.1
|
8377 |
wrap-ansi: 7.0.0
|
8378 |
|
|
|
|
|
8379 | |
8380 |
|
8381 | |
@@ -12287,6 +12477,8 @@ snapshots:
|
|
12287 |
|
12288 | |
12289 |
|
|
|
|
|
12290 | |
12291 |
dependencies:
|
12292 |
'@alloc/quick-lru': 5.2.0
|
@@ -12539,6 +12731,10 @@ snapshots:
|
|
12539 |
querystringify: 2.2.0
|
12540 |
requires-port: 1.0.0
|
12541 |
|
|
|
|
|
|
|
|
|
12542 | |
12543 |
|
12544 |
|
|
8 |
|
9 |
.:
|
10 |
dependencies:
|
11 |
+
'@headlessui/react':
|
12 |
+
specifier: ^2.2.4
|
13 |
+
version: 2.2.4([email protected]([email protected]))([email protected])
|
14 |
'@huggingface/transformers':
|
15 |
specifier: ^3.6.1
|
16 |
version: 3.6.1
|
|
|
920 |
resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==}
|
921 |
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
|
922 |
|
923 |
+
'@floating-ui/[email protected]':
|
924 |
+
resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==}
|
925 |
+
|
926 |
+
'@floating-ui/[email protected]':
|
927 |
+
resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==}
|
928 |
+
|
929 |
+
'@floating-ui/[email protected]':
|
930 |
+
resolution: {integrity: sha512-JbbpPhp38UmXDDAu60RJmbeme37Jbgsm7NrHGgzYYFKmblzRUh6Pa641dII6LsjwF4XlScDrde2UAzDo/b9KPw==}
|
931 |
+
peerDependencies:
|
932 |
+
react: '>=16.8.0'
|
933 |
+
react-dom: '>=16.8.0'
|
934 |
+
|
935 |
+
'@floating-ui/[email protected]':
|
936 |
+
resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==}
|
937 |
+
peerDependencies:
|
938 |
+
react: '>=16.8.0'
|
939 |
+
react-dom: '>=16.8.0'
|
940 |
+
|
941 |
+
'@floating-ui/[email protected]':
|
942 |
+
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
|
943 |
+
|
944 |
+
'@headlessui/[email protected]':
|
945 |
+
resolution: {integrity: sha512-lz+OGcAH1dK93rgSMzXmm1qKOJkBUqZf1L4M8TWLNplftQD3IkoEDdUFNfAn4ylsN6WOTVtWaLmvmaHOUk1dTA==}
|
946 |
+
engines: {node: '>=10'}
|
947 |
+
peerDependencies:
|
948 |
+
react: ^18 || ^19 || ^19.0.0-rc
|
949 |
+
react-dom: ^18 || ^19 || ^19.0.0-rc
|
950 |
+
|
951 |
'@huggingface/[email protected]':
|
952 |
resolution: {integrity: sha512-3WXbMFaPkk03LRCM0z0sylmn8ddDm4ubjU7X+Hg4M2GOuMklwoGAFXp9V2keq7vltoB/c7McE5aHUVVddAewsw==}
|
953 |
engines: {node: '>=18'}
|
|
|
1264 |
'@protobufjs/[email protected]':
|
1265 |
resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==}
|
1266 |
|
1267 |
+
'@react-aria/[email protected]':
|
1268 |
+
resolution: {integrity: sha512-JpFtXmWQ0Oca7FcvkqgjSyo6xEP7v3oQOLUId6o0xTvm4AD5W0mU2r3lYrbhsJ+XxdUUX4AVR5473sZZ85kU4A==}
|
1269 |
+
peerDependencies:
|
1270 |
+
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1271 |
+
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1272 |
+
|
1273 |
+
'@react-aria/[email protected]':
|
1274 |
+
resolution: {integrity: sha512-J1bhlrNtjPS/fe5uJQ+0c7/jiXniwa4RQlP+Emjfc/iuqpW2RhbF9ou5vROcLzWIyaW8tVMZ468J68rAs/aZ5A==}
|
1275 |
+
peerDependencies:
|
1276 |
+
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1277 |
+
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1278 |
+
|
1279 |
+
'@react-aria/[email protected]':
|
1280 |
+
resolution: {integrity: sha512-2P5thfjfPy/np18e5wD4WPt8ydNXhij1jwA8oehxZTFqlgVMGXzcWKxTb4RtJrLFsqPO7RUQTiY8QJk0M4Vy2g==}
|
1281 |
+
engines: {node: '>= 12'}
|
1282 |
+
peerDependencies:
|
1283 |
+
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1284 |
+
|
1285 |
+
'@react-aria/[email protected]':
|
1286 |
+
resolution: {integrity: sha512-yXMFVJ73rbQ/yYE/49n5Uidjw7kh192WNN9PNQGV0Xoc7EJUlSOxqhnpHmYTyO0EotJ8fdM1fMH8durHjUSI8g==}
|
1287 |
+
peerDependencies:
|
1288 |
+
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1289 |
+
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1290 |
+
|
1291 |
+
'@react-stately/[email protected]':
|
1292 |
+
resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==}
|
1293 |
+
|
1294 |
+
'@react-stately/[email protected]':
|
1295 |
+
resolution: {integrity: sha512-cWvjGAocvy4abO9zbr6PW6taHgF24Mwy/LbQ4TC4Aq3tKdKDntxyD+sh7AkSRfJRT2ccMVaHVv2+FfHThd3PKQ==}
|
1296 |
+
peerDependencies:
|
1297 |
+
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1298 |
+
|
1299 |
+
'@react-types/[email protected]':
|
1300 |
+
resolution: {integrity: sha512-COIazDAx1ncDg046cTJ8SFYsX8aS3lB/08LDnbkH/SkdYrFPWDlXMrO/sUam8j1WWM+PJ+4d1mj7tODIKNiFog==}
|
1301 |
+
peerDependencies:
|
1302 |
+
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
|
1303 |
+
|
1304 |
'@rollup/[email protected]':
|
1305 |
resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==}
|
1306 |
engines: {node: '>= 10.0.0'}
|
|
|
1403 |
resolution: {integrity: sha512-DOBOK255wfQxguUta2INKkzPj6AIS6iafZYiYmHn6W3pHlycSRRlvWKCfLDG10fXfLWqE3DJHgRUOyJYmARa7g==}
|
1404 |
engines: {node: '>=10'}
|
1405 |
|
1406 |
+
'@swc/[email protected]':
|
1407 |
+
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
|
1408 |
+
|
1409 |
+
'@tanstack/[email protected]':
|
1410 |
+
resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==}
|
1411 |
+
peerDependencies:
|
1412 |
+
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
1413 |
+
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
1414 |
+
|
1415 |
+
'@tanstack/[email protected]':
|
1416 |
+
resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==}
|
1417 |
+
|
1418 |
'@testing-library/[email protected]':
|
1419 |
resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==}
|
1420 |
engines: {node: '>=18'}
|
|
|
2156 | |
2157 |
resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==}
|
2158 |
|
2159 | |
2160 |
+
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
2161 |
+
engines: {node: '>=6'}
|
2162 |
+
|
2163 | |
2164 |
resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
|
2165 |
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
|
|
5461 | |
5462 |
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
5463 |
|
5464 | |
5465 |
+
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
|
5466 |
+
|
5467 | |
5468 |
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
|
5469 |
engines: {node: '>=14.0.0'}
|
|
|
5706 | |
5707 |
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
|
5708 |
|
5709 | |
5710 |
+
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
|
5711 |
+
peerDependencies:
|
5712 |
+
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
|
5713 |
+
|
5714 | |
5715 |
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
5716 |
|
|
|
7043 |
|
7044 |
'@eslint/[email protected]': {}
|
7045 |
|
7046 |
+
'@floating-ui/[email protected]':
|
7047 |
+
dependencies:
|
7048 |
+
'@floating-ui/utils': 0.2.10
|
7049 |
+
|
7050 |
+
'@floating-ui/[email protected]':
|
7051 |
+
dependencies:
|
7052 |
+
'@floating-ui/core': 1.7.2
|
7053 |
+
'@floating-ui/utils': 0.2.10
|
7054 |
+
|
7055 |
+
'@floating-ui/[email protected]([email protected]([email protected]))([email protected])':
|
7056 |
+
dependencies:
|
7057 |
+
'@floating-ui/dom': 1.7.2
|
7058 |
+
react: 19.1.0
|
7059 |
+
react-dom: 19.1.0([email protected])
|
7060 |
+
|
7061 |
+
'@floating-ui/[email protected]([email protected]([email protected]))([email protected])':
|
7062 |
+
dependencies:
|
7063 |
+
'@floating-ui/react-dom': 2.1.4([email protected]([email protected]))([email protected])
|
7064 |
+
'@floating-ui/utils': 0.2.10
|
7065 |
+
react: 19.1.0
|
7066 |
+
react-dom: 19.1.0([email protected])
|
7067 |
+
tabbable: 6.2.0
|
7068 |
+
|
7069 |
+
'@floating-ui/[email protected]': {}
|
7070 |
+
|
7071 |
+
'@headlessui/[email protected]([email protected]([email protected]))([email protected])':
|
7072 |
+
dependencies:
|
7073 |
+
'@floating-ui/react': 0.26.28([email protected]([email protected]))([email protected])
|
7074 |
+
'@react-aria/focus': 3.20.5([email protected]([email protected]))([email protected])
|
7075 |
+
'@react-aria/interactions': 3.25.3([email protected]([email protected]))([email protected])
|
7076 |
+
'@tanstack/react-virtual': 3.13.12([email protected]([email protected]))([email protected])
|
7077 |
+
react: 19.1.0
|
7078 |
+
react-dom: 19.1.0([email protected])
|
7079 |
+
use-sync-external-store: 1.5.0([email protected])
|
7080 |
+
|
7081 |
'@huggingface/[email protected]': {}
|
7082 |
|
7083 |
'@huggingface/[email protected]':
|
|
|
7458 |
|
7459 |
'@protobufjs/[email protected]': {}
|
7460 |
|
7461 |
+
'@react-aria/[email protected]([email protected]([email protected]))([email protected])':
|
7462 |
+
dependencies:
|
7463 |
+
'@react-aria/interactions': 3.25.3([email protected]([email protected]))([email protected])
|
7464 |
+
'@react-aria/utils': 3.29.1([email protected]([email protected]))([email protected])
|
7465 |
+
'@react-types/shared': 3.30.0([email protected])
|
7466 |
+
'@swc/helpers': 0.5.17
|
7467 |
+
clsx: 2.1.1
|
7468 |
+
react: 19.1.0
|
7469 |
+
react-dom: 19.1.0([email protected])
|
7470 |
+
|
7471 |
+
'@react-aria/[email protected]([email protected]([email protected]))([email protected])':
|
7472 |
+
dependencies:
|
7473 |
+
'@react-aria/ssr': 3.9.9([email protected])
|
7474 |
+
'@react-aria/utils': 3.29.1([email protected]([email protected]))([email protected])
|
7475 |
+
'@react-stately/flags': 3.1.2
|
7476 |
+
'@react-types/shared': 3.30.0([email protected])
|
7477 |
+
'@swc/helpers': 0.5.17
|
7478 |
+
react: 19.1.0
|
7479 |
+
react-dom: 19.1.0([email protected])
|
7480 |
+
|
7481 |
+
'@react-aria/[email protected]([email protected])':
|
7482 |
+
dependencies:
|
7483 |
+
'@swc/helpers': 0.5.17
|
7484 |
+
react: 19.1.0
|
7485 |
+
|
7486 |
+
'@react-aria/[email protected]([email protected]([email protected]))([email protected])':
|
7487 |
+
dependencies:
|
7488 |
+
'@react-aria/ssr': 3.9.9([email protected])
|
7489 |
+
'@react-stately/flags': 3.1.2
|
7490 |
+
'@react-stately/utils': 3.10.7([email protected])
|
7491 |
+
'@react-types/shared': 3.30.0([email protected])
|
7492 |
+
'@swc/helpers': 0.5.17
|
7493 |
+
clsx: 2.1.1
|
7494 |
+
react: 19.1.0
|
7495 |
+
react-dom: 19.1.0([email protected])
|
7496 |
+
|
7497 |
+
'@react-stately/[email protected]':
|
7498 |
+
dependencies:
|
7499 |
+
'@swc/helpers': 0.5.17
|
7500 |
+
|
7501 |
+
'@react-stately/[email protected]([email protected])':
|
7502 |
+
dependencies:
|
7503 |
+
'@swc/helpers': 0.5.17
|
7504 |
+
react: 19.1.0
|
7505 |
+
|
7506 |
+
'@react-types/[email protected]([email protected])':
|
7507 |
+
dependencies:
|
7508 |
+
react: 19.1.0
|
7509 |
+
|
7510 |
'@rollup/[email protected](@babel/[email protected])(@types/[email protected])([email protected])':
|
7511 |
dependencies:
|
7512 |
'@babel/core': 7.28.0
|
|
|
7629 |
transitivePeerDependencies:
|
7630 |
- supports-color
|
7631 |
|
7632 |
+
'@swc/[email protected]':
|
7633 |
+
dependencies:
|
7634 |
+
tslib: 2.8.1
|
7635 |
+
|
7636 |
+
'@tanstack/[email protected]([email protected]([email protected]))([email protected])':
|
7637 |
+
dependencies:
|
7638 |
+
'@tanstack/virtual-core': 3.13.12
|
7639 |
+
react: 19.1.0
|
7640 |
+
react-dom: 19.1.0([email protected])
|
7641 |
+
|
7642 |
+
'@tanstack/[email protected]': {}
|
7643 |
+
|
7644 |
'@testing-library/[email protected]':
|
7645 |
dependencies:
|
7646 |
'@babel/code-frame': 7.27.1
|
|
|
8564 |
strip-ansi: 6.0.1
|
8565 |
wrap-ansi: 7.0.0
|
8566 |
|
8567 |
+
[email protected]: {}
|
8568 |
+
|
8569 | |
8570 |
|
8571 | |
|
|
12477 |
|
12478 | |
12479 |
|
12480 |
+
[email protected]: {}
|
12481 |
+
|
12482 | |
12483 |
dependencies:
|
12484 |
'@alloc/quick-lru': 5.2.0
|
|
|
12731 |
querystringify: 2.2.0
|
12732 |
requires-port: 1.0.0
|
12733 |
|
12734 | |
12735 |
+
dependencies:
|
12736 |
+
react: 19.1.0
|
12737 |
+
|
12738 | |
12739 |
|
12740 |
src/App.tsx
CHANGED
@@ -5,31 +5,22 @@ import TextClassification from './components/TextClassification'
|
|
5 |
import Header from './Header'
|
6 |
import Footer from './Footer'
|
7 |
import { useModel } from './contexts/ModelContext'
|
8 |
-
import {
|
9 |
-
import { getModelsByPipeline, getModelSize } from './lib/huggingface'
|
10 |
import ModelSelector from './components/ModelSelector'
|
|
|
11 |
|
12 |
function App() {
|
13 |
-
const { pipeline, setPipeline, progress, status, modelInfo, setModels } =
|
|
|
14 |
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
} else if (num >= 1000) {
|
21 |
-
return (num / 1000).toFixed(1) + 'K'
|
22 |
}
|
23 |
-
|
24 |
-
}
|
25 |
-
|
26 |
-
useEffect(() => {
|
27 |
-
const fetchModels = async () => {
|
28 |
-
const fetchedModels = await getModelsByPipeline(pipeline);
|
29 |
-
setModels(fetchedModels);
|
30 |
-
};
|
31 |
-
fetchModels();
|
32 |
-
}, [setModels, pipeline]);
|
33 |
|
34 |
return (
|
35 |
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
@@ -40,66 +31,27 @@ function App() {
|
|
40 |
<div className="mb-8">
|
41 |
<div className="bg-white rounded-lg shadow-sm border p-6">
|
42 |
<div className="flex items-center justify-between mb-4">
|
43 |
-
|
44 |
-
{modelInfo.name && (
|
45 |
-
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-3 rounded-lg border border-blue-200 space-y-2">
|
46 |
-
{/* Model Name Row */}
|
47 |
-
<div className="flex items-center space-x-2">
|
48 |
-
<Bot className="w-4 h-4 text-blue-600" />
|
49 |
-
<span
|
50 |
-
className="text-sm font-medium text-gray-700 truncate max-w-100"
|
51 |
-
title={modelInfo.name}
|
52 |
-
>
|
53 |
-
{modelInfo.name.split('/').pop()}
|
54 |
-
</span>
|
55 |
-
</div>
|
56 |
-
|
57 |
-
{/* Stats Row */}
|
58 |
-
<div className="flex items-center justify-self-end space-x-4 text-xs text-gray-600">
|
59 |
-
{modelInfo.likes > 0 && (
|
60 |
-
<div className="flex items-center space-x-1">
|
61 |
-
<Heart className="w-3 h-3 text-red-500" />
|
62 |
-
<span>{formatNumber(modelInfo.likes)}</span>
|
63 |
-
</div>
|
64 |
-
)}
|
65 |
-
|
66 |
-
{modelInfo.downloads > 0 && (
|
67 |
-
<div className="flex items-center space-x-1">
|
68 |
-
<Download className="w-3 h-3 text-green-500" />
|
69 |
-
<span>{formatNumber(modelInfo.downloads)}</span>
|
70 |
-
</div>
|
71 |
-
)}
|
72 |
-
|
73 |
-
{modelInfo.parameters > 0 && (
|
74 |
-
<div className="flex items-center space-x-1">
|
75 |
-
<Cpu className="w-3 h-3 text-purple-500" />
|
76 |
-
<span>{formatNumber(modelInfo.parameters)}</span>
|
77 |
-
</div>
|
78 |
-
)}
|
79 |
-
|
80 |
-
{modelInfo.parameters > 0 && (
|
81 |
-
<div className="flex items-center space-x-1">
|
82 |
-
<DatabaseIcon className="w-3 h-3 text-purple-500" />
|
83 |
-
<span>
|
84 |
-
{`~${getModelSize(
|
85 |
-
modelInfo.parameters,
|
86 |
-
'INT8'
|
87 |
-
).toFixed(1)}MB`}
|
88 |
-
</span>
|
89 |
-
</div>
|
90 |
-
)}
|
91 |
-
</div>
|
92 |
-
</div>
|
93 |
-
)}
|
94 |
</div>
|
95 |
|
96 |
-
<div className="
|
97 |
-
<
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
</div>
|
102 |
-
<ModelSelector />
|
103 |
|
104 |
{/* Model Loading Progress */}
|
105 |
{status === 'progress' && (
|
|
|
5 |
import Header from './Header'
|
6 |
import Footer from './Footer'
|
7 |
import { useModel } from './contexts/ModelContext'
|
8 |
+
import { getModelsByPipeline } from './lib/huggingface'
|
|
|
9 |
import ModelSelector from './components/ModelSelector'
|
10 |
+
import ModelInfo from './components/ModelInfo'
|
11 |
|
12 |
function App() {
|
13 |
+
const { pipeline, setPipeline, progress, status, modelInfo, setModels } =
|
14 |
+
useModel()
|
15 |
|
16 |
+
useEffect(() => {
|
17 |
+
const fetchModels = async () => {
|
18 |
+
const fetchedModels = await getModelsByPipeline(pipeline)
|
19 |
+
setModels(fetchedModels)
|
20 |
+
console.log(fetchedModels)
|
|
|
|
|
21 |
}
|
22 |
+
fetchModels()
|
23 |
+
}, [setModels, pipeline])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
return (
|
26 |
<div className="min-h-screen bg-gradient-to-br from-blue-50 to-indigo-100">
|
|
|
31 |
<div className="mb-8">
|
32 |
<div className="bg-white rounded-lg shadow-sm border p-6">
|
33 |
<div className="flex items-center justify-between mb-4">
|
34 |
+
<ModelInfo />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
35 |
</div>
|
36 |
|
37 |
+
<div className="grid grid-cols-1 xl:grid-cols-2 gap-4 items-start">
|
38 |
+
<div className="space-y-2">
|
39 |
+
<span className="text-lg font-semibold text-gray-900 block">
|
40 |
+
Choose a Pipeline
|
41 |
+
</span>
|
42 |
+
<PipelineSelector
|
43 |
+
pipeline={pipeline}
|
44 |
+
setPipeline={setPipeline}
|
45 |
+
/>
|
46 |
+
</div>
|
47 |
+
|
48 |
+
<div className="space-y-2">
|
49 |
+
<span className="text-lg font-semibold text-gray-900 block">
|
50 |
+
Select Model
|
51 |
+
</span>
|
52 |
+
<ModelSelector />
|
53 |
+
</div>
|
54 |
</div>
|
|
|
55 |
|
56 |
{/* Model Loading Progress */}
|
57 |
{status === 'progress' && (
|
src/components/ModelInfo.tsx
ADDED
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Bot, Heart, Download, Cpu, DatabaseIcon, CheckCircle, XCircle, ExternalLink, ChevronDown } from 'lucide-react'
|
2 |
+
import { getModelSize } from '../lib/huggingface'
|
3 |
+
import { useModel } from '../contexts/ModelContext'
|
4 |
+
import { useEffect } from 'react'
|
5 |
+
import { QuantizationType } from '../types'
|
6 |
+
|
7 |
+
const ModelInfo = () => {
|
8 |
+
const formatNumber = (num: number) => {
|
9 |
+
if (num >= 1000000000) {
|
10 |
+
return (num / 1000000000).toFixed(1) + 'B'
|
11 |
+
} else if (num >= 1000000) {
|
12 |
+
return (num / 1000000).toFixed(1) + 'M'
|
13 |
+
} else if (num >= 1000) {
|
14 |
+
return (num / 1000).toFixed(1) + 'K'
|
15 |
+
}
|
16 |
+
return num.toString()
|
17 |
+
}
|
18 |
+
|
19 |
+
const { modelInfo, selectedQuantization, setSelectedQuantization } = useModel()
|
20 |
+
|
21 |
+
// Set default quantization when model changes
|
22 |
+
useEffect(() => {
|
23 |
+
if (modelInfo.isCompatible && modelInfo.supportedQuantizations.length > 0) {
|
24 |
+
const quantizations = modelInfo.supportedQuantizations
|
25 |
+
let defaultQuant: QuantizationType = 'fp32'
|
26 |
+
|
27 |
+
if (quantizations.includes('int8')) {
|
28 |
+
defaultQuant = 'int8'
|
29 |
+
} else if (quantizations.includes('q8')) {
|
30 |
+
defaultQuant = 'q8'
|
31 |
+
} else if (quantizations.includes('q4')) {
|
32 |
+
defaultQuant = 'q4'
|
33 |
+
}
|
34 |
+
|
35 |
+
setSelectedQuantization(defaultQuant)
|
36 |
+
}
|
37 |
+
}, [modelInfo.supportedQuantizations, modelInfo.isCompatible, setSelectedQuantization])
|
38 |
+
|
39 |
+
if (!modelInfo.name) {
|
40 |
+
return null
|
41 |
+
}
|
42 |
+
|
43 |
+
return (
|
44 |
+
<div className="bg-gradient-to-r from-blue-50 to-indigo-50 px-4 py-3 rounded-lg border border-blue-200 space-y-3">
|
45 |
+
{/* Model Name Row */}
|
46 |
+
<div className="flex items-center space-x-2">
|
47 |
+
<Bot className="w-4 h-4 text-blue-600" />
|
48 |
+
<a
|
49 |
+
href={`https://huggingface.co/${modelInfo.name}`}
|
50 |
+
target="_blank"
|
51 |
+
rel="noopener noreferrer"
|
52 |
+
className="text-sm font-medium text-gray-700 truncate max-w-100 hover:underline"
|
53 |
+
title={modelInfo.name}
|
54 |
+
>
|
55 |
+
<ExternalLink className="w-3 h-3 inline-block mr-1" />
|
56 |
+
{modelInfo.name}
|
57 |
+
</a>
|
58 |
+
{/* Compatibility Status */}
|
59 |
+
{typeof modelInfo.isCompatible === 'boolean' && (
|
60 |
+
<div className="flex items-center space-x-1">
|
61 |
+
{modelInfo.isCompatible ? (
|
62 |
+
<>
|
63 |
+
<CheckCircle className="w-4 h-4 text-green-500" />
|
64 |
+
</>
|
65 |
+
) : (
|
66 |
+
<>
|
67 |
+
<XCircle className="w-4 h-4 text-red-500" />
|
68 |
+
</>
|
69 |
+
)}
|
70 |
+
</div>
|
71 |
+
)}
|
72 |
+
</div>
|
73 |
+
|
74 |
+
{/* Base Model Link */}
|
75 |
+
{modelInfo.baseId && (
|
76 |
+
<div className="flex items-center space-x-2 ml-6">
|
77 |
+
<a
|
78 |
+
href={`https://huggingface.co/${modelInfo.baseId}`}
|
79 |
+
target="_blank"
|
80 |
+
rel="noopener noreferrer"
|
81 |
+
className="text-xs text-gray-600 truncate max-w-100 hover:underline"
|
82 |
+
title={`Base model: ${modelInfo.baseId}`}
|
83 |
+
>
|
84 |
+
<ExternalLink className="w-3 h-3 inline-block mr-1" />
|
85 |
+
{modelInfo.baseId}
|
86 |
+
</a>
|
87 |
+
</div>
|
88 |
+
)}
|
89 |
+
|
90 |
+
|
91 |
+
{/* Stats Row */}
|
92 |
+
<div className="flex items-center justify-self-end space-x-4 text-xs text-gray-600">
|
93 |
+
{modelInfo.likes > 0 && (
|
94 |
+
<div className="flex items-center space-x-1">
|
95 |
+
<Heart className="w-3 h-3 text-red-500" />
|
96 |
+
<span>{formatNumber(modelInfo.likes)}</span>
|
97 |
+
</div>
|
98 |
+
)}
|
99 |
+
|
100 |
+
{modelInfo.downloads > 0 && (
|
101 |
+
<div className="flex items-center space-x-1">
|
102 |
+
<Download className="w-3 h-3 text-green-500" />
|
103 |
+
<span>{formatNumber(modelInfo.downloads)}</span>
|
104 |
+
</div>
|
105 |
+
)}
|
106 |
+
|
107 |
+
{modelInfo.parameters > 0 && (
|
108 |
+
<div className="flex items-center space-x-1">
|
109 |
+
<Cpu className="w-3 h-3 text-purple-500" />
|
110 |
+
<span>{formatNumber(modelInfo.parameters)}</span>
|
111 |
+
</div>
|
112 |
+
)}
|
113 |
+
|
114 |
+
{modelInfo.parameters > 0 && (
|
115 |
+
<div className="flex items-center space-x-1">
|
116 |
+
<DatabaseIcon className="w-3 h-3 text-purple-500" />
|
117 |
+
<span>
|
118 |
+
{`~${getModelSize(modelInfo.parameters, selectedQuantization).toFixed(1)}MB`}
|
119 |
+
</span>
|
120 |
+
</div>
|
121 |
+
)}
|
122 |
+
</div>
|
123 |
+
|
124 |
+
{/* Separator */}
|
125 |
+
{modelInfo.isCompatible && modelInfo.supportedQuantizations.length > 0 && (
|
126 |
+
<hr className="border-gray-200" />
|
127 |
+
)}
|
128 |
+
|
129 |
+
{/* Quantization Dropdown */}
|
130 |
+
{modelInfo.isCompatible && modelInfo.supportedQuantizations.length > 0 && (
|
131 |
+
<div className="flex items-center space-x-2">
|
132 |
+
<span className="text-xs text-gray-600 font-medium">Quantization:</span>
|
133 |
+
<div className="relative">
|
134 |
+
<select
|
135 |
+
value={selectedQuantization || ''}
|
136 |
+
onChange={(e) => setSelectedQuantization(e.target.value as QuantizationType)}
|
137 |
+
className="appearance-none bg-white border border-gray-300 rounded-md px-3 py-1 pr-8 text-xs text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
138 |
+
>
|
139 |
+
<option value="">Select quantization</option>
|
140 |
+
{modelInfo.supportedQuantizations.map((quant) => (
|
141 |
+
<option key={quant} value={quant}>
|
142 |
+
{quant}
|
143 |
+
</option>
|
144 |
+
))}
|
145 |
+
</select>
|
146 |
+
<ChevronDown className="absolute right-2 top-1/2 transform -translate-y-1/2 w-3 h-3 text-gray-400 pointer-events-none" />
|
147 |
+
</div>
|
148 |
+
</div>
|
149 |
+
)}
|
150 |
+
|
151 |
+
{/* Incompatibility Message */}
|
152 |
+
{modelInfo.isCompatible === false && modelInfo.incompatibilityReason && (
|
153 |
+
<div className="bg-red-50 border border-red-200 rounded-md px-3 py-2">
|
154 |
+
<p className="text-sm text-red-700">
|
155 |
+
<span className="font-medium">Incompatible:</span>{' '}
|
156 |
+
{modelInfo.incompatibilityReason}
|
157 |
+
</p>
|
158 |
+
</div>
|
159 |
+
)}
|
160 |
+
</div>
|
161 |
+
)
|
162 |
+
}
|
163 |
+
|
164 |
+
export default ModelInfo
|
src/components/ModelSelector.tsx
CHANGED
@@ -1,14 +1,15 @@
|
|
1 |
import React, { useEffect, useState } from 'react'
|
|
|
2 |
import { useModel } from '../contexts/ModelContext'
|
3 |
import { getModelInfo } from '../lib/huggingface'
|
4 |
-
import { Heart, Download, ChevronDown } from 'lucide-react'
|
|
|
|
|
5 |
|
6 |
const ModelSelector: React.FC = () => {
|
7 |
-
const { models, setModelInfo, modelInfo } = useModel()
|
8 |
-
const [
|
9 |
-
const [
|
10 |
-
Record<string, { likes: number; downloads: number; createdAt: string }>
|
11 |
-
>({})
|
12 |
|
13 |
const formatNumber = (num: number) => {
|
14 |
if (num >= 1000000000) {
|
@@ -21,39 +22,49 @@ const ModelSelector: React.FC = () => {
|
|
21 |
return num.toString()
|
22 |
}
|
23 |
|
24 |
-
//
|
25 |
-
const
|
26 |
-
|
27 |
-
|
28 |
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
|
|
|
|
|
|
|
|
|
|
41 |
|
42 |
-
|
43 |
-
|
|
|
|
|
|
|
|
|
44 |
try {
|
45 |
const modelInfoResponse = await getModelInfo(modelId)
|
|
|
46 |
let parameters = 0
|
47 |
if (modelInfoResponse.safetensors) {
|
48 |
const safetensors = modelInfoResponse.safetensors
|
49 |
parameters =
|
|
|
50 |
safetensors.parameters.F16 ||
|
51 |
safetensors.parameters.F32 ||
|
52 |
safetensors.parameters.total ||
|
53 |
0
|
54 |
}
|
55 |
|
56 |
-
// Transform ModelInfoResponse to ModelInfo
|
57 |
const modelInfo = {
|
58 |
id: modelId,
|
59 |
name: modelInfoResponse.id || modelId,
|
@@ -61,20 +72,14 @@ const ModelSelector: React.FC = () => {
|
|
61 |
parameters,
|
62 |
likes: modelInfoResponse.likes || 0,
|
63 |
downloads: modelInfoResponse.downloads || 0,
|
64 |
-
createdAt: modelInfoResponse.createdAt || ''
|
|
|
|
|
|
|
|
|
65 |
}
|
66 |
|
67 |
-
|
68 |
-
setModelStats((prev) => ({
|
69 |
-
...prev,
|
70 |
-
[modelId]: {
|
71 |
-
likes: modelInfoResponse.likes || 0,
|
72 |
-
downloads: modelInfoResponse.downloads || 0,
|
73 |
-
createdAt: modelInfoResponse.createdAt || ''
|
74 |
-
}
|
75 |
-
}))
|
76 |
-
|
77 |
-
console.log(modelInfoResponse)
|
78 |
|
79 |
setModelInfo(modelInfo)
|
80 |
} catch (error) {
|
@@ -82,97 +87,175 @@ const ModelSelector: React.FC = () => {
|
|
82 |
}
|
83 |
}
|
84 |
|
85 |
-
//
|
86 |
useEffect(() => {
|
87 |
-
models.
|
88 |
-
|
89 |
-
|
90 |
-
}
|
91 |
-
})
|
92 |
-
}, [models])
|
93 |
-
|
94 |
-
// Only fetch full info when a model is actually selected
|
95 |
-
useEffect(() => {
|
96 |
-
if (!modelInfo.id) return
|
97 |
-
// Only fetch if we don't already have the full info
|
98 |
-
if (!modelStats[modelInfo.id]) {
|
99 |
-
fetchModelAndSetInfo(modelInfo.id)
|
100 |
}
|
101 |
-
}, [
|
102 |
|
103 |
const handleModelSelect = (modelId: string) => {
|
104 |
-
|
105 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
106 |
}
|
107 |
|
|
|
|
|
108 |
return (
|
109 |
<div className="relative">
|
110 |
-
{
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
(modelStats[model.id].likes > 0 ||
|
140 |
-
modelStats[model.id].downloads > 0) && (
|
141 |
-
<div className="flex items-center space-x-3 text-xs text-gray-500 flex-shrink-0">
|
142 |
-
{modelStats[model.id].likes > 0 && (
|
143 |
-
<div className="flex items-center space-x-1">
|
144 |
-
<Heart className="w-3 h-3 text-red-500" />
|
145 |
-
<span>
|
146 |
-
{formatNumber(modelStats[model.id].likes)}
|
147 |
-
</span>
|
148 |
-
</div>
|
149 |
-
)}
|
150 |
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
</div>
|
158 |
)}
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
</span>
|
163 |
-
)}
|
164 |
-
</div>
|
165 |
-
)}
|
166 |
</div>
|
167 |
-
</
|
168 |
-
|
169 |
</div>
|
170 |
-
|
171 |
-
|
172 |
-
{/* Click outside to close */}
|
173 |
-
{isOpen && (
|
174 |
-
<div className="fixed inset-0 z-0" onClick={() => setIsOpen(false)} />
|
175 |
-
)}
|
176 |
</div>
|
177 |
)
|
178 |
}
|
|
|
1 |
import React, { useEffect, useState } from 'react'
|
2 |
+
import { Listbox, ListboxButton, ListboxOption, ListboxOptions, Transition } from '@headlessui/react'
|
3 |
import { useModel } from '../contexts/ModelContext'
|
4 |
import { getModelInfo } from '../lib/huggingface'
|
5 |
+
import { Heart, Download, ChevronDown, Check, ArrowUpDown } from 'lucide-react'
|
6 |
+
|
7 |
+
type SortOption = 'likes' | 'downloads' | 'createdAt' | 'name'
|
8 |
|
9 |
const ModelSelector: React.FC = () => {
|
10 |
+
const { models, setModelInfo, modelInfo, pipeline } = useModel()
|
11 |
+
const [sortBy, setSortBy] = useState<SortOption>('likes')
|
12 |
+
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc')
|
|
|
|
|
13 |
|
14 |
const formatNumber = (num: number) => {
|
15 |
if (num >= 1000000000) {
|
|
|
22 |
return num.toString()
|
23 |
}
|
24 |
|
25 |
+
// Sort models based on current sort criteria
|
26 |
+
const sortedModels = React.useMemo(() => {
|
27 |
+
return [...models].sort((a, b) => {
|
28 |
+
let comparison = 0
|
29 |
|
30 |
+
switch (sortBy) {
|
31 |
+
case 'downloads':
|
32 |
+
comparison = (a.downloads || 0) - (b.downloads || 0)
|
33 |
+
break
|
34 |
+
case 'createdAt':
|
35 |
+
const dateA = new Date(a.createdAt || '').getTime()
|
36 |
+
const dateB = new Date(b.createdAt || '').getTime()
|
37 |
+
comparison = dateA - dateB
|
38 |
+
break
|
39 |
+
case 'name':
|
40 |
+
comparison = a.id.localeCompare(b.id)
|
41 |
+
break
|
42 |
+
case 'likes':
|
43 |
+
default:
|
44 |
+
comparison = (a.likes || 0) - (b.likes || 0)
|
45 |
+
break
|
46 |
+
}
|
47 |
|
48 |
+
return sortOrder === 'desc' ? -comparison : comparison
|
49 |
+
})
|
50 |
+
}, [models, sortBy, sortOrder])
|
51 |
+
|
52 |
+
// Function to fetch detailed model info and set as selected
|
53 |
+
const fetchAndSetModelInfo = async (modelId: string) => {
|
54 |
try {
|
55 |
const modelInfoResponse = await getModelInfo(modelId)
|
56 |
+
|
57 |
let parameters = 0
|
58 |
if (modelInfoResponse.safetensors) {
|
59 |
const safetensors = modelInfoResponse.safetensors
|
60 |
parameters =
|
61 |
+
safetensors.parameters.BF16 ||
|
62 |
safetensors.parameters.F16 ||
|
63 |
safetensors.parameters.F32 ||
|
64 |
safetensors.parameters.total ||
|
65 |
0
|
66 |
}
|
67 |
|
|
|
68 |
const modelInfo = {
|
69 |
id: modelId,
|
70 |
name: modelInfoResponse.id || modelId,
|
|
|
72 |
parameters,
|
73 |
likes: modelInfoResponse.likes || 0,
|
74 |
downloads: modelInfoResponse.downloads || 0,
|
75 |
+
createdAt: modelInfoResponse.createdAt || '',
|
76 |
+
isCompatible: modelInfoResponse.isCompatible,
|
77 |
+
incompatibilityReason: modelInfoResponse.incompatibilityReason,
|
78 |
+
supportedQuantizations: modelInfoResponse.supportedQuantizations,
|
79 |
+
baseId: modelInfoResponse.baseId
|
80 |
}
|
81 |
|
82 |
+
console.log('Fetched model info:', modelInfoResponse)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
|
84 |
setModelInfo(modelInfo)
|
85 |
} catch (error) {
|
|
|
87 |
}
|
88 |
}
|
89 |
|
90 |
+
// Update modelInfo to first model when pipeline changes
|
91 |
useEffect(() => {
|
92 |
+
if (models.length > 0) {
|
93 |
+
const firstModel = models[0]
|
94 |
+
fetchAndSetModelInfo(firstModel.id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
}
|
96 |
+
}, [pipeline, models])
|
97 |
|
98 |
const handleModelSelect = (modelId: string) => {
|
99 |
+
fetchAndSetModelInfo(modelId)
|
100 |
+
}
|
101 |
+
|
102 |
+
const handleSortChange = (newSortBy: SortOption) => {
|
103 |
+
if (sortBy === newSortBy) {
|
104 |
+
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
|
105 |
+
} else {
|
106 |
+
setSortBy(newSortBy)
|
107 |
+
setSortOrder('desc')
|
108 |
+
}
|
109 |
}
|
110 |
|
111 |
+
const selectedModel = models.find(model => model.id === modelInfo.id) || models[0]
|
112 |
+
|
113 |
return (
|
114 |
<div className="relative">
|
115 |
+
<Listbox value={selectedModel} onChange={(model) => handleModelSelect(model.id)}>
|
116 |
+
<div className="relative">
|
117 |
+
<ListboxButton className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white text-left flex items-center justify-between">
|
118 |
+
<div className="flex items-center justify-between w-full">
|
119 |
+
<div className="flex flex-col flex-1 min-w-0">
|
120 |
+
<span className="truncate font-medium">{modelInfo.id || 'Select a model'}</span>
|
121 |
+
</div>
|
122 |
+
|
123 |
+
<div className="flex items-center space-x-3">
|
124 |
+
{selectedModel && (selectedModel.likes > 0 || selectedModel.downloads > 0) && (
|
125 |
+
<div className="flex items-center space-x-3 text-xs text-gray-500">
|
126 |
+
{selectedModel.likes > 0 && (
|
127 |
+
<div className="flex items-center space-x-1">
|
128 |
+
<Heart className="w-3 h-3 text-red-500" />
|
129 |
+
<span>{formatNumber(selectedModel.likes)}</span>
|
130 |
+
</div>
|
131 |
+
)}
|
132 |
+
{selectedModel.downloads > 0 && (
|
133 |
+
<div className="flex items-center space-x-1">
|
134 |
+
<Download className="w-3 h-3 text-green-500" />
|
135 |
+
<span>{formatNumber(selectedModel.downloads)}</span>
|
136 |
+
</div>
|
137 |
+
)}
|
138 |
+
</div>
|
139 |
+
)}
|
140 |
+
<ChevronDown className="w-4 h-4 ui-open:rotate-180 transition-transform flex-shrink-0" />
|
141 |
+
</div>
|
142 |
+
</div>
|
143 |
+
</ListboxButton>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
144 |
|
145 |
+
<Transition
|
146 |
+
enter="transition duration-100 ease-out"
|
147 |
+
enterFrom="transform scale-95 opacity-0"
|
148 |
+
enterTo="transform scale-100 opacity-100"
|
149 |
+
leave="transition duration-75 ease-out"
|
150 |
+
leaveFrom="transform scale-100 opacity-100"
|
151 |
+
leaveTo="transform scale-95 opacity-0"
|
152 |
+
>
|
153 |
+
<ListboxOptions className="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-md shadow-lg max-h-60 overflow-hidden focus:outline-none">
|
154 |
+
{/* Sort Controls - Always Visible */}
|
155 |
+
<div className="px-3 py-2 border-b border-gray-200 bg-gray-50 sticky top-0 z-10">
|
156 |
+
<div className="flex items-center space-x-2 text-xs">
|
157 |
+
<span className="text-gray-600 font-medium">Sort by:</span>
|
158 |
+
<button
|
159 |
+
onClick={() => handleSortChange('name')}
|
160 |
+
className={`px-2 py-1 rounded flex items-center space-x-1 ${
|
161 |
+
sortBy === 'name' ? 'bg-blue-100 text-blue-700' : 'text-gray-600 hover:bg-gray-100'
|
162 |
+
}`}
|
163 |
+
>
|
164 |
+
<span>Name</span>
|
165 |
+
{sortBy === 'name' && <ArrowUpDown className="w-3 h-3" />}
|
166 |
+
</button>
|
167 |
+
<button
|
168 |
+
onClick={() => handleSortChange('likes')}
|
169 |
+
className={`px-2 py-1 rounded flex items-center space-x-1 ${
|
170 |
+
sortBy === 'likes' ? 'bg-blue-100 text-blue-700' : 'text-gray-600 hover:bg-gray-100'
|
171 |
+
}`}
|
172 |
+
>
|
173 |
+
<Heart className="w-3 h-3" />
|
174 |
+
<span>Likes</span>
|
175 |
+
{sortBy === 'likes' && <ArrowUpDown className="w-3 h-3" />}
|
176 |
+
</button>
|
177 |
+
<button
|
178 |
+
onClick={() => handleSortChange('downloads')}
|
179 |
+
className={`px-2 py-1 rounded flex items-center space-x-1 ${
|
180 |
+
sortBy === 'downloads' ? 'bg-blue-100 text-blue-700' : 'text-gray-600 hover:bg-gray-100'
|
181 |
+
}`}
|
182 |
+
>
|
183 |
+
<Download className="w-3 h-3" />
|
184 |
+
<span>Downloads</span>
|
185 |
+
{sortBy === 'downloads' && <ArrowUpDown className="w-3 h-3" />}
|
186 |
+
</button>
|
187 |
+
<button
|
188 |
+
onClick={() => handleSortChange('createdAt')}
|
189 |
+
className={`px-2 py-1 rounded flex items-center space-x-1 ${
|
190 |
+
sortBy === 'createdAt' ? 'bg-blue-100 text-blue-700' : 'text-gray-600 hover:bg-gray-100'
|
191 |
+
}`}
|
192 |
+
>
|
193 |
+
<span>Date</span>
|
194 |
+
{sortBy === 'createdAt' && <ArrowUpDown className="w-3 h-3" />}
|
195 |
+
</button>
|
196 |
+
</div>
|
197 |
+
</div>
|
198 |
+
|
199 |
+
{/* Model Options - Scrollable */}
|
200 |
+
<div className="overflow-auto max-h-48">
|
201 |
+
{sortedModels.map((model) => {
|
202 |
+
const hasStats = model.likes > 0 || model.downloads > 0
|
203 |
+
|
204 |
+
return (
|
205 |
+
<ListboxOption
|
206 |
+
key={model.id}
|
207 |
+
value={model}
|
208 |
+
className={({ active, selected }) =>
|
209 |
+
`px-3 py-2 cursor-pointer border-b border-gray-100 last:border-b-0 ${
|
210 |
+
active ? 'bg-gray-50' : ''
|
211 |
+
} ${selected ? 'bg-blue-50' : ''}`
|
212 |
+
}
|
213 |
+
>
|
214 |
+
{({ selected }) => (
|
215 |
+
<div className="flex items-center justify-between">
|
216 |
+
<div className="flex items-center flex-1 mr-2">
|
217 |
+
<span className="text-sm font-medium truncate">
|
218 |
+
{model.id}
|
219 |
+
</span>
|
220 |
+
{selected && (
|
221 |
+
<Check className="w-4 h-4 text-blue-600 ml-2 flex-shrink-0" />
|
222 |
+
)}
|
223 |
+
</div>
|
224 |
+
|
225 |
+
{/* Stats Display */}
|
226 |
+
{hasStats && (
|
227 |
+
<div className="flex items-center space-x-3 text-xs text-gray-500 flex-shrink-0">
|
228 |
+
{model.likes > 0 && (
|
229 |
+
<div className="flex items-center space-x-1">
|
230 |
+
<Heart className="w-3 h-3 text-red-500" />
|
231 |
+
<span>{formatNumber(model.likes)}</span>
|
232 |
+
</div>
|
233 |
+
)}
|
234 |
+
|
235 |
+
{model.downloads > 0 && (
|
236 |
+
<div className="flex items-center space-x-1">
|
237 |
+
<Download className="w-3 h-3 text-green-500" />
|
238 |
+
<span>{formatNumber(model.downloads)}</span>
|
239 |
+
</div>
|
240 |
+
)}
|
241 |
+
|
242 |
+
{model.createdAt && (
|
243 |
+
<span className="text-xs text-gray-400">
|
244 |
+
{model.createdAt.split('T')[0]}
|
245 |
+
</span>
|
246 |
+
)}
|
247 |
+
</div>
|
248 |
+
)}
|
249 |
</div>
|
250 |
)}
|
251 |
+
</ListboxOption>
|
252 |
+
)
|
253 |
+
})}
|
|
|
|
|
|
|
|
|
254 |
</div>
|
255 |
+
</ListboxOptions>
|
256 |
+
</Transition>
|
257 |
</div>
|
258 |
+
</Listbox>
|
|
|
|
|
|
|
|
|
|
|
259 |
</div>
|
260 |
)
|
261 |
}
|
src/components/PipelineSelector.tsx
CHANGED
@@ -1,4 +1,12 @@
|
|
1 |
import React from 'react';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
const pipelines = [
|
4 |
'zero-shot-classification',
|
@@ -21,14 +29,73 @@ const PipelineSelector: React.FC<PipelineSelectorProps> = ({
|
|
21 |
pipeline,
|
22 |
setPipeline
|
23 |
}) => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
return (
|
25 |
-
<
|
26 |
-
{
|
27 |
-
<
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
);
|
33 |
};
|
34 |
|
|
|
1 |
import React from 'react';
|
2 |
+
import {
|
3 |
+
Listbox,
|
4 |
+
ListboxOption,
|
5 |
+
ListboxButton,
|
6 |
+
ListboxOptions,
|
7 |
+
Transition
|
8 |
+
} from '@headlessui/react'
|
9 |
+
import { ChevronDown, Check } from 'lucide-react';
|
10 |
|
11 |
const pipelines = [
|
12 |
'zero-shot-classification',
|
|
|
29 |
pipeline,
|
30 |
setPipeline
|
31 |
}) => {
|
32 |
+
const selectedPipeline = pipeline;
|
33 |
+
|
34 |
+
const formatPipelineName = (pipelineId: string) => {
|
35 |
+
return pipelineId
|
36 |
+
.split('-')
|
37 |
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
38 |
+
.join(' ');
|
39 |
+
};
|
40 |
+
|
41 |
return (
|
42 |
+
<div className="relative">
|
43 |
+
<Listbox value={selectedPipeline} onChange={setPipeline}>
|
44 |
+
<div className="relative">
|
45 |
+
<ListboxButton className="relative w-full cursor-default rounded-lg bg-white py-2 pl-3 pr-10 text-left shadow-md focus:outline-none focus-visible:border-indigo-500 focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-orange-300 sm:text-sm border border-gray-300">
|
46 |
+
<span className="block truncate font-medium">
|
47 |
+
{formatPipelineName(selectedPipeline)}
|
48 |
+
</span>
|
49 |
+
<span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
|
50 |
+
<ChevronDown
|
51 |
+
className="h-5 w-5 text-gray-400 ui-open:rotate-180 transition-transform"
|
52 |
+
aria-hidden="true"
|
53 |
+
/>
|
54 |
+
</span>
|
55 |
+
</ListboxButton>
|
56 |
+
|
57 |
+
<Transition
|
58 |
+
enter="transition duration-100 ease-out"
|
59 |
+
enterFrom="transform scale-95 opacity-0"
|
60 |
+
enterTo="transform scale-100 opacity-100"
|
61 |
+
leave="transition duration-75 ease-out"
|
62 |
+
leaveFrom="transform scale-100 opacity-100"
|
63 |
+
leaveTo="transform scale-95 opacity-0"
|
64 |
+
>
|
65 |
+
<ListboxOptions className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm">
|
66 |
+
{pipelines.map((p) => (
|
67 |
+
<ListboxOption
|
68 |
+
key={p}
|
69 |
+
className={({ active }) =>
|
70 |
+
`relative cursor-default select-none py-2 px-4 ${
|
71 |
+
active ? 'bg-amber-100 text-amber-900' : 'text-gray-900'
|
72 |
+
}`
|
73 |
+
}
|
74 |
+
value={p}
|
75 |
+
>
|
76 |
+
{({ selected }) => (
|
77 |
+
<div className="flex items-center justify-between">
|
78 |
+
<span
|
79 |
+
className={`block truncate ${
|
80 |
+
selected ? 'font-medium' : 'font-normal'
|
81 |
+
}`}
|
82 |
+
>
|
83 |
+
{formatPipelineName(p)}
|
84 |
+
</span>
|
85 |
+
{selected && (
|
86 |
+
<span className="flex items-center text-amber-600">
|
87 |
+
<Check className="h-5 w-5" aria-hidden="true" />
|
88 |
+
</span>
|
89 |
+
)}
|
90 |
+
</div>
|
91 |
+
)}
|
92 |
+
</ListboxOption>
|
93 |
+
))}
|
94 |
+
</ListboxOptions>
|
95 |
+
</Transition>
|
96 |
+
</div>
|
97 |
+
</Listbox>
|
98 |
+
</div>
|
99 |
);
|
100 |
};
|
101 |
|
src/components/ZeroShotClassification.tsx
CHANGED
@@ -52,37 +52,6 @@ function ZeroShotClassification() {
|
|
52 |
)
|
53 |
|
54 |
const { setProgress, status, setStatus, modelInfo, setModelInfo } = useModel()
|
55 |
-
useEffect(() => {
|
56 |
-
const modelName = 'lxyuan/distilbert-base-multilingual-cased-sentiments-student'
|
57 |
-
const fetchModelInfo = async () => {
|
58 |
-
try {
|
59 |
-
const modelInfoResponse = await getModelInfo(modelName)
|
60 |
-
console.log(modelInfoResponse)
|
61 |
-
let parameters = 0
|
62 |
-
if (modelInfoResponse.safetensors) {
|
63 |
-
const safetensors = modelInfoResponse.safetensors
|
64 |
-
parameters =
|
65 |
-
safetensors.parameters.F16 ||
|
66 |
-
safetensors.parameters.F32 ||
|
67 |
-
safetensors.parameters.total ||
|
68 |
-
0
|
69 |
-
}
|
70 |
-
setModelInfo({
|
71 |
-
id: modelInfoResponse.id,
|
72 |
-
name: modelName,
|
73 |
-
architecture: modelInfoResponse.config?.architectures[0] ?? '',
|
74 |
-
parameters,
|
75 |
-
likes: modelInfoResponse.likes,
|
76 |
-
downloads: modelInfoResponse.downloads,
|
77 |
-
createdAt: modelInfoResponse.createdAt,
|
78 |
-
})
|
79 |
-
} catch (error) {
|
80 |
-
console.error('Error fetching model info:', error)
|
81 |
-
}
|
82 |
-
}
|
83 |
-
|
84 |
-
fetchModelInfo()
|
85 |
-
}, [setModelInfo])
|
86 |
|
87 |
// Create a reference to the worker object.
|
88 |
const worker = useRef<Worker | null>(null)
|
|
|
52 |
)
|
53 |
|
54 |
const { setProgress, status, setStatus, modelInfo, setModelInfo } = useModel()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
55 |
|
56 |
// Create a reference to the worker object.
|
57 |
const worker = useRef<Worker | null>(null)
|
src/contexts/ModelContext.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
import React, { createContext, useContext, useEffect, useState } from 'react'
|
2 |
-
import { ModelInfo, ModelInfoResponse } from '../types'
|
3 |
|
4 |
interface ModelContextType {
|
5 |
progress: number
|
@@ -12,6 +12,8 @@ interface ModelContextType {
|
|
12 |
setPipeline: (pipeline: string) => void
|
13 |
models: ModelInfoResponse[]
|
14 |
setModels: (models: ModelInfoResponse[]) => void
|
|
|
|
|
15 |
}
|
16 |
|
17 |
const ModelContext = createContext<ModelContextType | undefined>(undefined)
|
@@ -22,6 +24,7 @@ export function ModelProvider({ children }: { children: React.ReactNode }) {
|
|
22 |
const [modelInfo, setModelInfo] = useState<ModelInfo>({} as ModelInfo)
|
23 |
const [models, setModels] = useState<ModelInfoResponse[]>([] as ModelInfoResponse[])
|
24 |
const [pipeline, setPipeline] = useState<string>('zero-shot-classification')
|
|
|
25 |
|
26 |
// set progress to 0 when model is changed
|
27 |
useEffect(() => {
|
@@ -41,6 +44,8 @@ export function ModelProvider({ children }: { children: React.ReactNode }) {
|
|
41 |
setModels,
|
42 |
pipeline,
|
43 |
setPipeline,
|
|
|
|
|
44 |
}}
|
45 |
>
|
46 |
{children}
|
|
|
1 |
import React, { createContext, useContext, useEffect, useState } from 'react'
|
2 |
+
import { ModelInfo, ModelInfoResponse, QuantizationType } from '../types'
|
3 |
|
4 |
interface ModelContextType {
|
5 |
progress: number
|
|
|
12 |
setPipeline: (pipeline: string) => void
|
13 |
models: ModelInfoResponse[]
|
14 |
setModels: (models: ModelInfoResponse[]) => void
|
15 |
+
selectedQuantization: QuantizationType
|
16 |
+
setSelectedQuantization: (quantization: QuantizationType) => void
|
17 |
}
|
18 |
|
19 |
const ModelContext = createContext<ModelContextType | undefined>(undefined)
|
|
|
24 |
const [modelInfo, setModelInfo] = useState<ModelInfo>({} as ModelInfo)
|
25 |
const [models, setModels] = useState<ModelInfoResponse[]>([] as ModelInfoResponse[])
|
26 |
const [pipeline, setPipeline] = useState<string>('zero-shot-classification')
|
27 |
+
const [selectedQuantization, setSelectedQuantization] = useState<QuantizationType>('int8')
|
28 |
|
29 |
// set progress to 0 when model is changed
|
30 |
useEffect(() => {
|
|
|
44 |
setModels,
|
45 |
pipeline,
|
46 |
setPipeline,
|
47 |
+
selectedQuantization,
|
48 |
+
setSelectedQuantization,
|
49 |
}}
|
50 |
>
|
51 |
{children}
|
src/lib/huggingface.ts
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
-
import {
|
2 |
-
import { ModelInfoResponse } from "../types"
|
3 |
|
4 |
const getModelInfo = async (modelName: string): Promise<ModelInfoResponse> => {
|
5 |
const token = process.env.REACT_APP_HUGGINGFACE_TOKEN
|
@@ -23,7 +22,70 @@ const getModelInfo = async (modelName: string): Promise<ModelInfoResponse> => {
|
|
23 |
if (!response.ok) {
|
24 |
throw new Error(`Failed to fetch model info: ${response.statusText}`)
|
25 |
}
|
26 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
27 |
}
|
28 |
|
29 |
const getModelsByPipeline = async (
|
@@ -57,8 +119,6 @@ const getModelsByPipeline = async (
|
|
57 |
return models.slice(0, 10)
|
58 |
}
|
59 |
|
60 |
-
// Define the possible quantization types for clarity and type safety
|
61 |
-
type QuantizationType = 'FP32' | 'FP16' | 'INT8' | 'Q4'
|
62 |
function getModelSize(
|
63 |
parameters: number,
|
64 |
quantization: QuantizationType
|
@@ -66,20 +126,23 @@ function getModelSize(
|
|
66 |
let bytesPerParameter: number
|
67 |
|
68 |
switch (quantization) {
|
69 |
-
case '
|
70 |
// 32-bit floating point uses 4 bytes
|
71 |
bytesPerParameter = 4
|
72 |
break
|
73 |
-
case '
|
74 |
bytesPerParameter = 2
|
75 |
break
|
76 |
-
case '
|
|
|
|
|
|
|
77 |
bytesPerParameter = 1
|
78 |
break
|
79 |
-
case '
|
|
|
80 |
bytesPerParameter = 0.5
|
81 |
-
|
82 |
-
return theoreticalSize
|
83 |
}
|
84 |
|
85 |
// There are 1,024 * 1,024 bytes in a megabyte
|
@@ -91,4 +154,3 @@ function getModelSize(
|
|
91 |
|
92 |
|
93 |
export { getModelInfo, getModelSize, getModelsByPipeline }
|
94 |
-
|
|
|
1 |
+
import { ModelInfoResponse, QuantizationType } from "../types"
|
|
|
2 |
|
3 |
const getModelInfo = async (modelName: string): Promise<ModelInfoResponse> => {
|
4 |
const token = process.env.REACT_APP_HUGGINGFACE_TOKEN
|
|
|
22 |
if (!response.ok) {
|
23 |
throw new Error(`Failed to fetch model info: ${response.statusText}`)
|
24 |
}
|
25 |
+
|
26 |
+
const modelData: ModelInfoResponse = await response.json()
|
27 |
+
|
28 |
+
const requiredFiles = [
|
29 |
+
'config.json',
|
30 |
+
'tokenizer.json',
|
31 |
+
'tokenizer_config.json',
|
32 |
+
]
|
33 |
+
|
34 |
+
const siblingFiles = modelData.siblings?.map(s => s.rfilename) || []
|
35 |
+
const isCompatible =
|
36 |
+
requiredFiles.every((file) => siblingFiles.includes(file)) &&
|
37 |
+
siblingFiles.some((file) => file.endsWith('.onnx') && file.startsWith('onnx/'))
|
38 |
+
const incompatibilityReason = isCompatible
|
39 |
+
? ''
|
40 |
+
: `Missing required files: ${requiredFiles
|
41 |
+
.filter(file => !siblingFiles.includes(file))
|
42 |
+
.join(', ')}`
|
43 |
+
const supportedQuantizations = siblingFiles
|
44 |
+
.filter((file) => file.endsWith('.onnx') && file.includes('_'))
|
45 |
+
.map((file) => file.split('/')[1].split('_')[1].split('.')[0])
|
46 |
+
.filter((q) => q !== 'quantized')
|
47 |
+
const uniqueSupportedQuantizations = Array.from(new Set(supportedQuantizations))
|
48 |
+
uniqueSupportedQuantizations.sort((a, b) => {
|
49 |
+
const getNumericValue = (str: string) => {
|
50 |
+
const match = str.match(/(\d+)/)
|
51 |
+
return match ? parseInt(match[1]) : Infinity
|
52 |
+
}
|
53 |
+
return getNumericValue(a) - getNumericValue(b)
|
54 |
+
})
|
55 |
+
|
56 |
+
// If there's a base model, fetch its info and merge with compatibility data
|
57 |
+
const baseModel = modelData.cardData?.base_model ?? modelData.modelId
|
58 |
+
if (baseModel && !modelData.safetensors) {
|
59 |
+
const baseModelResponse = await fetch(
|
60 |
+
`https://huggingface.co/api/models/${baseModel}`,
|
61 |
+
{
|
62 |
+
method: 'GET',
|
63 |
+
headers: {
|
64 |
+
Authorization: `Bearer ${token}`
|
65 |
+
}
|
66 |
+
}
|
67 |
+
)
|
68 |
+
|
69 |
+
if (baseModelResponse.ok) {
|
70 |
+
const baseModelData: ModelInfoResponse = await baseModelResponse.json()
|
71 |
+
|
72 |
+
return {
|
73 |
+
...baseModelData,
|
74 |
+
id: modelData.id,
|
75 |
+
baseId: baseModel,
|
76 |
+
isCompatible,
|
77 |
+
incompatibilityReason,
|
78 |
+
supportedQuantizations: uniqueSupportedQuantizations as QuantizationType[]
|
79 |
+
}
|
80 |
+
}
|
81 |
+
}
|
82 |
+
|
83 |
+
return {
|
84 |
+
...modelData,
|
85 |
+
isCompatible,
|
86 |
+
incompatibilityReason,
|
87 |
+
supportedQuantizations: uniqueSupportedQuantizations as QuantizationType[]
|
88 |
+
}
|
89 |
}
|
90 |
|
91 |
const getModelsByPipeline = async (
|
|
|
119 |
return models.slice(0, 10)
|
120 |
}
|
121 |
|
|
|
|
|
122 |
function getModelSize(
|
123 |
parameters: number,
|
124 |
quantization: QuantizationType
|
|
|
126 |
let bytesPerParameter: number
|
127 |
|
128 |
switch (quantization) {
|
129 |
+
case 'fp32':
|
130 |
// 32-bit floating point uses 4 bytes
|
131 |
bytesPerParameter = 4
|
132 |
break
|
133 |
+
case 'fp16':
|
134 |
bytesPerParameter = 2
|
135 |
break
|
136 |
+
case 'int8':
|
137 |
+
case 'bnb8':
|
138 |
+
case 'uint8':
|
139 |
+
case 'q8':
|
140 |
bytesPerParameter = 1
|
141 |
break
|
142 |
+
case 'bnb4':
|
143 |
+
case 'q4':
|
144 |
bytesPerParameter = 0.5
|
145 |
+
break
|
|
|
146 |
}
|
147 |
|
148 |
// There are 1,024 * 1,024 bytes in a megabyte
|
|
|
154 |
|
155 |
|
156 |
export { getModelInfo, getModelSize, getModelsByPipeline }
|
|
src/types.ts
CHANGED
@@ -27,6 +27,14 @@ export interface TextClassificationWorkerInput {
|
|
27 |
|
28 |
export type AppStatus = 'idle' | 'loading' | 'processing'
|
29 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
export interface ModelInfo {
|
31 |
id: string
|
32 |
name: string
|
@@ -35,6 +43,10 @@ export interface ModelInfo {
|
|
35 |
likes: number
|
36 |
downloads: number
|
37 |
createdAt: string
|
|
|
|
|
|
|
|
|
38 |
}
|
39 |
|
40 |
|
@@ -48,6 +60,10 @@ export interface ModelInfoResponse {
|
|
48 |
lastModified: string
|
49 |
pipeline_tag: string
|
50 |
tags: string[]
|
|
|
|
|
|
|
|
|
51 |
transformersInfo: {
|
52 |
pipeline_tag: string
|
53 |
auto_model: string
|
@@ -55,11 +71,19 @@ export interface ModelInfoResponse {
|
|
55 |
}
|
56 |
safetensors?: {
|
57 |
parameters: {
|
|
|
58 |
F16?: number
|
59 |
F32?: number
|
60 |
total?: number
|
61 |
}
|
62 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
likes: number
|
64 |
downloads: number
|
65 |
}
|
|
|
27 |
|
28 |
export type AppStatus = 'idle' | 'loading' | 'processing'
|
29 |
|
30 |
+
type q8 = 'q8' | 'int8' | 'bnb8' | 'uint8'
|
31 |
+
type q4 = 'q4' | 'bnb4'
|
32 |
+
type fp16 = 'fp16'
|
33 |
+
type fp32 = 'fp32'
|
34 |
+
|
35 |
+
export type QuantizationType = q8 | q4 | fp16 | fp32
|
36 |
+
|
37 |
+
|
38 |
export interface ModelInfo {
|
39 |
id: string
|
40 |
name: string
|
|
|
43 |
likes: number
|
44 |
downloads: number
|
45 |
createdAt: string
|
46 |
+
isCompatible?: boolean
|
47 |
+
incompatibilityReason?: string
|
48 |
+
supportedQuantizations: QuantizationType[]
|
49 |
+
baseId?: string
|
50 |
}
|
51 |
|
52 |
|
|
|
60 |
lastModified: string
|
61 |
pipeline_tag: string
|
62 |
tags: string[]
|
63 |
+
cardData?: {
|
64 |
+
base_model: string
|
65 |
+
}
|
66 |
+
baseId?: string
|
67 |
transformersInfo: {
|
68 |
pipeline_tag: string
|
69 |
auto_model: string
|
|
|
71 |
}
|
72 |
safetensors?: {
|
73 |
parameters: {
|
74 |
+
BF16?: number
|
75 |
F16?: number
|
76 |
F32?: number
|
77 |
total?: number
|
78 |
}
|
79 |
}
|
80 |
+
siblings?: {
|
81 |
+
rfilename: string
|
82 |
+
}[]
|
83 |
+
modelId?: string
|
84 |
+
isCompatible: boolean
|
85 |
+
incompatibilityReason?: string
|
86 |
+
supportedQuantizations: QuantizationType[]
|
87 |
likes: number
|
88 |
downloads: number
|
89 |
}
|