philschmid HF staff commited on
Commit
c297c27
1 Parent(s): eef269a
Files changed (5) hide show
  1. package-lock.json +484 -0
  2. package.json +1 -0
  3. src/App.tsx +122 -64
  4. src/components/ui/select.tsx +143 -0
  5. src/lib/data.ts +47 -0
package-lock.json CHANGED
@@ -9,6 +9,7 @@
9
  "version": "0.0.0",
10
  "dependencies": {
11
  "@radix-ui/react-checkbox": "^1.1.1",
 
12
  "class-variance-authority": "^0.7.0",
13
  "clsx": "^2.1.1",
14
  "lucide-react": "^0.399.0",
@@ -881,6 +882,40 @@
881
  "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
882
  }
883
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
884
  "node_modules/@humanwhocodes/config-array": {
885
  "version": "0.11.14",
886
  "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@@ -1063,11 +1098,38 @@
1063
  "node": ">=14"
1064
  }
1065
  },
 
 
 
 
 
1066
  "node_modules/@radix-ui/primitive": {
1067
  "version": "1.1.0",
1068
  "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
1069
  "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
1070
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1071
  "node_modules/@radix-ui/react-checkbox": {
1072
  "version": "1.1.1",
1073
  "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz",
@@ -1097,6 +1159,31 @@
1097
  }
1098
  }
1099
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1100
  "node_modules/@radix-ui/react-compose-refs": {
1101
  "version": "1.1.0",
1102
  "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
@@ -1125,6 +1212,155 @@
1125
  }
1126
  }
1127
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1128
  "node_modules/@radix-ui/react-presence": {
1129
  "version": "1.1.0",
1130
  "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz",
@@ -1170,6 +1406,48 @@
1170
  }
1171
  }
1172
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1173
  "node_modules/@radix-ui/react-slot": {
1174
  "version": "1.1.0",
1175
  "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
@@ -1218,6 +1496,23 @@
1218
  }
1219
  }
1220
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1221
  "node_modules/@radix-ui/react-use-layout-effect": {
1222
  "version": "1.1.0",
1223
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
@@ -1246,6 +1541,23 @@
1246
  }
1247
  }
1248
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1249
  "node_modules/@radix-ui/react-use-size": {
1250
  "version": "1.1.0",
1251
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
@@ -1263,6 +1575,33 @@
1263
  }
1264
  }
1265
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1266
  "node_modules/@rollup/rollup-android-arm-eabi": {
1267
  "version": "4.18.0",
1268
  "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
@@ -1901,6 +2240,17 @@
1901
  "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
1902
  "dev": true
1903
  },
 
 
 
 
 
 
 
 
 
 
 
1904
  "node_modules/array-union": {
1905
  "version": "2.1.0",
1906
  "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -2328,6 +2678,11 @@
2328
  "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
2329
  "dev": true
2330
  },
 
 
 
 
 
2331
  "node_modules/didyoumean": {
2332
  "version": "1.2.2",
2333
  "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -2913,6 +3268,14 @@
2913
  "node": ">=6.9.0"
2914
  }
2915
  },
 
 
 
 
 
 
 
 
2916
  "node_modules/glob": {
2917
  "version": "7.2.3",
2918
  "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
@@ -3081,6 +3444,14 @@
3081
  "node": ">=12"
3082
  }
3083
  },
 
 
 
 
 
 
 
 
3084
  "node_modules/is-binary-path": {
3085
  "version": "2.1.0",
3086
  "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -3841,6 +4212,51 @@
3841
  "node": ">=0.10.0"
3842
  }
3843
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3844
  "node_modules/react-smooth": {
3845
  "version": "4.0.1",
3846
  "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
@@ -3855,6 +4271,28 @@
3855
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
3856
  }
3857
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3858
  "node_modules/react-transition-group": {
3859
  "version": "4.4.5",
3860
  "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -4382,6 +4820,11 @@
4382
  "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
4383
  "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
4384
  },
 
 
 
 
 
4385
  "node_modules/type-check": {
4386
  "version": "0.4.0",
4387
  "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -4464,6 +4907,47 @@
4464
  "punycode": "^2.1.0"
4465
  }
4466
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4467
  "node_modules/util-deprecate": {
4468
  "version": "1.0.2",
4469
  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 
9
  "version": "0.0.0",
10
  "dependencies": {
11
  "@radix-ui/react-checkbox": "^1.1.1",
12
+ "@radix-ui/react-select": "^2.1.1",
13
  "class-variance-authority": "^0.7.0",
14
  "clsx": "^2.1.1",
15
  "lucide-react": "^0.399.0",
 
882
  "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
883
  }
884
  },
885
+ "node_modules/@floating-ui/core": {
886
+ "version": "1.6.4",
887
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.4.tgz",
888
+ "integrity": "sha512-a4IowK4QkXl4SCWTGUR0INAfEOX3wtsYw3rKK5InQEHMGObkR8Xk44qYQD9P4r6HHw0iIfK6GUKECmY8sTkqRA==",
889
+ "dependencies": {
890
+ "@floating-ui/utils": "^0.2.4"
891
+ }
892
+ },
893
+ "node_modules/@floating-ui/dom": {
894
+ "version": "1.6.7",
895
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.7.tgz",
896
+ "integrity": "sha512-wmVfPG5o2xnKDU4jx/m4w5qva9FWHcnZ8BvzEe90D/RpwsJaTAVYPEPdQ8sbr/N8zZTAHlZUTQdqg8ZUbzHmng==",
897
+ "dependencies": {
898
+ "@floating-ui/core": "^1.6.0",
899
+ "@floating-ui/utils": "^0.2.4"
900
+ }
901
+ },
902
+ "node_modules/@floating-ui/react-dom": {
903
+ "version": "2.1.1",
904
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.1.tgz",
905
+ "integrity": "sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg==",
906
+ "dependencies": {
907
+ "@floating-ui/dom": "^1.0.0"
908
+ },
909
+ "peerDependencies": {
910
+ "react": ">=16.8.0",
911
+ "react-dom": ">=16.8.0"
912
+ }
913
+ },
914
+ "node_modules/@floating-ui/utils": {
915
+ "version": "0.2.4",
916
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.4.tgz",
917
+ "integrity": "sha512-dWO2pw8hhi+WrXq1YJy2yCuWoL20PddgGaqTgVe4cOS9Q6qklXCiA1tJEqX6BEwRNSCP84/afac9hd4MS+zEUA=="
918
+ },
919
  "node_modules/@humanwhocodes/config-array": {
920
  "version": "0.11.14",
921
  "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
 
1098
  "node": ">=14"
1099
  }
1100
  },
1101
+ "node_modules/@radix-ui/number": {
1102
+ "version": "1.1.0",
1103
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz",
1104
+ "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ=="
1105
+ },
1106
  "node_modules/@radix-ui/primitive": {
1107
  "version": "1.1.0",
1108
  "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz",
1109
  "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA=="
1110
  },
1111
+ "node_modules/@radix-ui/react-arrow": {
1112
+ "version": "1.1.0",
1113
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz",
1114
+ "integrity": "sha512-FmlW1rCg7hBpEBwFbjHwCW6AmWLQM6g/v0Sn8XbP9NvmSZ2San1FpQeyPtufzOMSIx7Y4dzjlHoifhp+7NkZhw==",
1115
+ "dependencies": {
1116
+ "@radix-ui/react-primitive": "2.0.0"
1117
+ },
1118
+ "peerDependencies": {
1119
+ "@types/react": "*",
1120
+ "@types/react-dom": "*",
1121
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1122
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1123
+ },
1124
+ "peerDependenciesMeta": {
1125
+ "@types/react": {
1126
+ "optional": true
1127
+ },
1128
+ "@types/react-dom": {
1129
+ "optional": true
1130
+ }
1131
+ }
1132
+ },
1133
  "node_modules/@radix-ui/react-checkbox": {
1134
  "version": "1.1.1",
1135
  "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.1.1.tgz",
 
1159
  }
1160
  }
1161
  },
1162
+ "node_modules/@radix-ui/react-collection": {
1163
+ "version": "1.1.0",
1164
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",
1165
+ "integrity": "sha512-GZsZslMJEyo1VKm5L1ZJY8tGDxZNPAoUeQUIbKeJfoi7Q4kmig5AsgLMYYuyYbfjd8fBmFORAIwYAkXMnXZgZw==",
1166
+ "dependencies": {
1167
+ "@radix-ui/react-compose-refs": "1.1.0",
1168
+ "@radix-ui/react-context": "1.1.0",
1169
+ "@radix-ui/react-primitive": "2.0.0",
1170
+ "@radix-ui/react-slot": "1.1.0"
1171
+ },
1172
+ "peerDependencies": {
1173
+ "@types/react": "*",
1174
+ "@types/react-dom": "*",
1175
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1176
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1177
+ },
1178
+ "peerDependenciesMeta": {
1179
+ "@types/react": {
1180
+ "optional": true
1181
+ },
1182
+ "@types/react-dom": {
1183
+ "optional": true
1184
+ }
1185
+ }
1186
+ },
1187
  "node_modules/@radix-ui/react-compose-refs": {
1188
  "version": "1.1.0",
1189
  "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
 
1212
  }
1213
  }
1214
  },
1215
+ "node_modules/@radix-ui/react-direction": {
1216
+ "version": "1.1.0",
1217
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz",
1218
+ "integrity": "sha512-BUuBvgThEiAXh2DWu93XsT+a3aWrGqolGlqqw5VU1kG7p/ZH2cuDlM1sRLNnY3QcBS69UIz2mcKhMxDsdewhjg==",
1219
+ "peerDependencies": {
1220
+ "@types/react": "*",
1221
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1222
+ },
1223
+ "peerDependenciesMeta": {
1224
+ "@types/react": {
1225
+ "optional": true
1226
+ }
1227
+ }
1228
+ },
1229
+ "node_modules/@radix-ui/react-dismissable-layer": {
1230
+ "version": "1.1.0",
1231
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.0.tgz",
1232
+ "integrity": "sha512-/UovfmmXGptwGcBQawLzvn2jOfM0t4z3/uKffoBlj724+n3FvBbZ7M0aaBOmkp6pqFYpO4yx8tSVJjx3Fl2jig==",
1233
+ "dependencies": {
1234
+ "@radix-ui/primitive": "1.1.0",
1235
+ "@radix-ui/react-compose-refs": "1.1.0",
1236
+ "@radix-ui/react-primitive": "2.0.0",
1237
+ "@radix-ui/react-use-callback-ref": "1.1.0",
1238
+ "@radix-ui/react-use-escape-keydown": "1.1.0"
1239
+ },
1240
+ "peerDependencies": {
1241
+ "@types/react": "*",
1242
+ "@types/react-dom": "*",
1243
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1244
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1245
+ },
1246
+ "peerDependenciesMeta": {
1247
+ "@types/react": {
1248
+ "optional": true
1249
+ },
1250
+ "@types/react-dom": {
1251
+ "optional": true
1252
+ }
1253
+ }
1254
+ },
1255
+ "node_modules/@radix-ui/react-focus-guards": {
1256
+ "version": "1.1.0",
1257
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.0.tgz",
1258
+ "integrity": "sha512-w6XZNUPVv6xCpZUqb/yN9DL6auvpGX3C/ee6Hdi16v2UUy25HV2Q5bcflsiDyT/g5RwbPQ/GIT1vLkeRb+ITBw==",
1259
+ "peerDependencies": {
1260
+ "@types/react": "*",
1261
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1262
+ },
1263
+ "peerDependenciesMeta": {
1264
+ "@types/react": {
1265
+ "optional": true
1266
+ }
1267
+ }
1268
+ },
1269
+ "node_modules/@radix-ui/react-focus-scope": {
1270
+ "version": "1.1.0",
1271
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz",
1272
+ "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==",
1273
+ "dependencies": {
1274
+ "@radix-ui/react-compose-refs": "1.1.0",
1275
+ "@radix-ui/react-primitive": "2.0.0",
1276
+ "@radix-ui/react-use-callback-ref": "1.1.0"
1277
+ },
1278
+ "peerDependencies": {
1279
+ "@types/react": "*",
1280
+ "@types/react-dom": "*",
1281
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1282
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1283
+ },
1284
+ "peerDependenciesMeta": {
1285
+ "@types/react": {
1286
+ "optional": true
1287
+ },
1288
+ "@types/react-dom": {
1289
+ "optional": true
1290
+ }
1291
+ }
1292
+ },
1293
+ "node_modules/@radix-ui/react-id": {
1294
+ "version": "1.1.0",
1295
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz",
1296
+ "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==",
1297
+ "dependencies": {
1298
+ "@radix-ui/react-use-layout-effect": "1.1.0"
1299
+ },
1300
+ "peerDependencies": {
1301
+ "@types/react": "*",
1302
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1303
+ },
1304
+ "peerDependenciesMeta": {
1305
+ "@types/react": {
1306
+ "optional": true
1307
+ }
1308
+ }
1309
+ },
1310
+ "node_modules/@radix-ui/react-popper": {
1311
+ "version": "1.2.0",
1312
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz",
1313
+ "integrity": "sha512-ZnRMshKF43aBxVWPWvbj21+7TQCvhuULWJ4gNIKYpRlQt5xGRhLx66tMp8pya2UkGHTSlhpXwmjqltDYHhw7Vg==",
1314
+ "dependencies": {
1315
+ "@floating-ui/react-dom": "^2.0.0",
1316
+ "@radix-ui/react-arrow": "1.1.0",
1317
+ "@radix-ui/react-compose-refs": "1.1.0",
1318
+ "@radix-ui/react-context": "1.1.0",
1319
+ "@radix-ui/react-primitive": "2.0.0",
1320
+ "@radix-ui/react-use-callback-ref": "1.1.0",
1321
+ "@radix-ui/react-use-layout-effect": "1.1.0",
1322
+ "@radix-ui/react-use-rect": "1.1.0",
1323
+ "@radix-ui/react-use-size": "1.1.0",
1324
+ "@radix-ui/rect": "1.1.0"
1325
+ },
1326
+ "peerDependencies": {
1327
+ "@types/react": "*",
1328
+ "@types/react-dom": "*",
1329
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1330
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1331
+ },
1332
+ "peerDependenciesMeta": {
1333
+ "@types/react": {
1334
+ "optional": true
1335
+ },
1336
+ "@types/react-dom": {
1337
+ "optional": true
1338
+ }
1339
+ }
1340
+ },
1341
+ "node_modules/@radix-ui/react-portal": {
1342
+ "version": "1.1.1",
1343
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.1.tgz",
1344
+ "integrity": "sha512-A3UtLk85UtqhzFqtoC8Q0KvR2GbXF3mtPgACSazajqq6A41mEQgo53iPzY4i6BwDxlIFqWIhiQ2G729n+2aw/g==",
1345
+ "dependencies": {
1346
+ "@radix-ui/react-primitive": "2.0.0",
1347
+ "@radix-ui/react-use-layout-effect": "1.1.0"
1348
+ },
1349
+ "peerDependencies": {
1350
+ "@types/react": "*",
1351
+ "@types/react-dom": "*",
1352
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1353
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1354
+ },
1355
+ "peerDependenciesMeta": {
1356
+ "@types/react": {
1357
+ "optional": true
1358
+ },
1359
+ "@types/react-dom": {
1360
+ "optional": true
1361
+ }
1362
+ }
1363
+ },
1364
  "node_modules/@radix-ui/react-presence": {
1365
  "version": "1.1.0",
1366
  "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.0.tgz",
 
1406
  }
1407
  }
1408
  },
1409
+ "node_modules/@radix-ui/react-select": {
1410
+ "version": "2.1.1",
1411
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.1.1.tgz",
1412
+ "integrity": "sha512-8iRDfyLtzxlprOo9IicnzvpsO1wNCkuwzzCM+Z5Rb5tNOpCdMvcc2AkzX0Fz+Tz9v6NJ5B/7EEgyZveo4FBRfQ==",
1413
+ "dependencies": {
1414
+ "@radix-ui/number": "1.1.0",
1415
+ "@radix-ui/primitive": "1.1.0",
1416
+ "@radix-ui/react-collection": "1.1.0",
1417
+ "@radix-ui/react-compose-refs": "1.1.0",
1418
+ "@radix-ui/react-context": "1.1.0",
1419
+ "@radix-ui/react-direction": "1.1.0",
1420
+ "@radix-ui/react-dismissable-layer": "1.1.0",
1421
+ "@radix-ui/react-focus-guards": "1.1.0",
1422
+ "@radix-ui/react-focus-scope": "1.1.0",
1423
+ "@radix-ui/react-id": "1.1.0",
1424
+ "@radix-ui/react-popper": "1.2.0",
1425
+ "@radix-ui/react-portal": "1.1.1",
1426
+ "@radix-ui/react-primitive": "2.0.0",
1427
+ "@radix-ui/react-slot": "1.1.0",
1428
+ "@radix-ui/react-use-callback-ref": "1.1.0",
1429
+ "@radix-ui/react-use-controllable-state": "1.1.0",
1430
+ "@radix-ui/react-use-layout-effect": "1.1.0",
1431
+ "@radix-ui/react-use-previous": "1.1.0",
1432
+ "@radix-ui/react-visually-hidden": "1.1.0",
1433
+ "aria-hidden": "^1.1.1",
1434
+ "react-remove-scroll": "2.5.7"
1435
+ },
1436
+ "peerDependencies": {
1437
+ "@types/react": "*",
1438
+ "@types/react-dom": "*",
1439
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1440
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1441
+ },
1442
+ "peerDependenciesMeta": {
1443
+ "@types/react": {
1444
+ "optional": true
1445
+ },
1446
+ "@types/react-dom": {
1447
+ "optional": true
1448
+ }
1449
+ }
1450
+ },
1451
  "node_modules/@radix-ui/react-slot": {
1452
  "version": "1.1.0",
1453
  "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
 
1496
  }
1497
  }
1498
  },
1499
+ "node_modules/@radix-ui/react-use-escape-keydown": {
1500
+ "version": "1.1.0",
1501
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz",
1502
+ "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==",
1503
+ "dependencies": {
1504
+ "@radix-ui/react-use-callback-ref": "1.1.0"
1505
+ },
1506
+ "peerDependencies": {
1507
+ "@types/react": "*",
1508
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1509
+ },
1510
+ "peerDependenciesMeta": {
1511
+ "@types/react": {
1512
+ "optional": true
1513
+ }
1514
+ }
1515
+ },
1516
  "node_modules/@radix-ui/react-use-layout-effect": {
1517
  "version": "1.1.0",
1518
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz",
 
1541
  }
1542
  }
1543
  },
1544
+ "node_modules/@radix-ui/react-use-rect": {
1545
+ "version": "1.1.0",
1546
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.0.tgz",
1547
+ "integrity": "sha512-0Fmkebhr6PiseyZlYAOtLS+nb7jLmpqTrJyv61Pe68MKYW6OWdRE2kI70TaYY27u7H0lajqM3hSMMLFq18Z7nQ==",
1548
+ "dependencies": {
1549
+ "@radix-ui/rect": "1.1.0"
1550
+ },
1551
+ "peerDependencies": {
1552
+ "@types/react": "*",
1553
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1554
+ },
1555
+ "peerDependenciesMeta": {
1556
+ "@types/react": {
1557
+ "optional": true
1558
+ }
1559
+ }
1560
+ },
1561
  "node_modules/@radix-ui/react-use-size": {
1562
  "version": "1.1.0",
1563
  "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.0.tgz",
 
1575
  }
1576
  }
1577
  },
1578
+ "node_modules/@radix-ui/react-visually-hidden": {
1579
+ "version": "1.1.0",
1580
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.1.0.tgz",
1581
+ "integrity": "sha512-N8MDZqtgCgG5S3aV60INAB475osJousYpZ4cTJ2cFbMpdHS5Y6loLTH8LPtkj2QN0x93J30HT/M3qJXM0+lyeQ==",
1582
+ "dependencies": {
1583
+ "@radix-ui/react-primitive": "2.0.0"
1584
+ },
1585
+ "peerDependencies": {
1586
+ "@types/react": "*",
1587
+ "@types/react-dom": "*",
1588
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
1589
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
1590
+ },
1591
+ "peerDependenciesMeta": {
1592
+ "@types/react": {
1593
+ "optional": true
1594
+ },
1595
+ "@types/react-dom": {
1596
+ "optional": true
1597
+ }
1598
+ }
1599
+ },
1600
+ "node_modules/@radix-ui/rect": {
1601
+ "version": "1.1.0",
1602
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.0.tgz",
1603
+ "integrity": "sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg=="
1604
+ },
1605
  "node_modules/@rollup/rollup-android-arm-eabi": {
1606
  "version": "4.18.0",
1607
  "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz",
 
2240
  "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
2241
  "dev": true
2242
  },
2243
+ "node_modules/aria-hidden": {
2244
+ "version": "1.2.4",
2245
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.4.tgz",
2246
+ "integrity": "sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==",
2247
+ "dependencies": {
2248
+ "tslib": "^2.0.0"
2249
+ },
2250
+ "engines": {
2251
+ "node": ">=10"
2252
+ }
2253
+ },
2254
  "node_modules/array-union": {
2255
  "version": "2.1.0",
2256
  "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
 
2678
  "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
2679
  "dev": true
2680
  },
2681
+ "node_modules/detect-node-es": {
2682
+ "version": "1.1.0",
2683
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
2684
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="
2685
+ },
2686
  "node_modules/didyoumean": {
2687
  "version": "1.2.2",
2688
  "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
 
3268
  "node": ">=6.9.0"
3269
  }
3270
  },
3271
+ "node_modules/get-nonce": {
3272
+ "version": "1.0.1",
3273
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
3274
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
3275
+ "engines": {
3276
+ "node": ">=6"
3277
+ }
3278
+ },
3279
  "node_modules/glob": {
3280
  "version": "7.2.3",
3281
  "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
 
3444
  "node": ">=12"
3445
  }
3446
  },
3447
+ "node_modules/invariant": {
3448
+ "version": "2.2.4",
3449
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
3450
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
3451
+ "dependencies": {
3452
+ "loose-envify": "^1.0.0"
3453
+ }
3454
+ },
3455
  "node_modules/is-binary-path": {
3456
  "version": "2.1.0",
3457
  "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
 
4212
  "node": ">=0.10.0"
4213
  }
4214
  },
4215
+ "node_modules/react-remove-scroll": {
4216
+ "version": "2.5.7",
4217
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz",
4218
+ "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==",
4219
+ "dependencies": {
4220
+ "react-remove-scroll-bar": "^2.3.4",
4221
+ "react-style-singleton": "^2.2.1",
4222
+ "tslib": "^2.1.0",
4223
+ "use-callback-ref": "^1.3.0",
4224
+ "use-sidecar": "^1.1.2"
4225
+ },
4226
+ "engines": {
4227
+ "node": ">=10"
4228
+ },
4229
+ "peerDependencies": {
4230
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
4231
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
4232
+ },
4233
+ "peerDependenciesMeta": {
4234
+ "@types/react": {
4235
+ "optional": true
4236
+ }
4237
+ }
4238
+ },
4239
+ "node_modules/react-remove-scroll-bar": {
4240
+ "version": "2.3.6",
4241
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz",
4242
+ "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==",
4243
+ "dependencies": {
4244
+ "react-style-singleton": "^2.2.1",
4245
+ "tslib": "^2.0.0"
4246
+ },
4247
+ "engines": {
4248
+ "node": ">=10"
4249
+ },
4250
+ "peerDependencies": {
4251
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
4252
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
4253
+ },
4254
+ "peerDependenciesMeta": {
4255
+ "@types/react": {
4256
+ "optional": true
4257
+ }
4258
+ }
4259
+ },
4260
  "node_modules/react-smooth": {
4261
  "version": "4.0.1",
4262
  "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz",
 
4271
  "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
4272
  }
4273
  },
4274
+ "node_modules/react-style-singleton": {
4275
+ "version": "2.2.1",
4276
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz",
4277
+ "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==",
4278
+ "dependencies": {
4279
+ "get-nonce": "^1.0.0",
4280
+ "invariant": "^2.2.4",
4281
+ "tslib": "^2.0.0"
4282
+ },
4283
+ "engines": {
4284
+ "node": ">=10"
4285
+ },
4286
+ "peerDependencies": {
4287
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
4288
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
4289
+ },
4290
+ "peerDependenciesMeta": {
4291
+ "@types/react": {
4292
+ "optional": true
4293
+ }
4294
+ }
4295
+ },
4296
  "node_modules/react-transition-group": {
4297
  "version": "4.4.5",
4298
  "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
 
4820
  "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
4821
  "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="
4822
  },
4823
+ "node_modules/tslib": {
4824
+ "version": "2.6.3",
4825
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
4826
+ "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
4827
+ },
4828
  "node_modules/type-check": {
4829
  "version": "0.4.0",
4830
  "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
 
4907
  "punycode": "^2.1.0"
4908
  }
4909
  },
4910
+ "node_modules/use-callback-ref": {
4911
+ "version": "1.3.2",
4912
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz",
4913
+ "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==",
4914
+ "dependencies": {
4915
+ "tslib": "^2.0.0"
4916
+ },
4917
+ "engines": {
4918
+ "node": ">=10"
4919
+ },
4920
+ "peerDependencies": {
4921
+ "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0",
4922
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
4923
+ },
4924
+ "peerDependenciesMeta": {
4925
+ "@types/react": {
4926
+ "optional": true
4927
+ }
4928
+ }
4929
+ },
4930
+ "node_modules/use-sidecar": {
4931
+ "version": "1.1.2",
4932
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz",
4933
+ "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==",
4934
+ "dependencies": {
4935
+ "detect-node-es": "^1.1.0",
4936
+ "tslib": "^2.0.0"
4937
+ },
4938
+ "engines": {
4939
+ "node": ">=10"
4940
+ },
4941
+ "peerDependencies": {
4942
+ "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0",
4943
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
4944
+ },
4945
+ "peerDependenciesMeta": {
4946
+ "@types/react": {
4947
+ "optional": true
4948
+ }
4949
+ }
4950
+ },
4951
  "node_modules/util-deprecate": {
4952
  "version": "1.0.2",
4953
  "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
package.json CHANGED
@@ -11,6 +11,7 @@
11
  },
12
  "dependencies": {
13
  "@radix-ui/react-checkbox": "^1.1.1",
 
14
  "class-variance-authority": "^0.7.0",
15
  "clsx": "^2.1.1",
16
  "lucide-react": "^0.399.0",
 
11
  },
12
  "dependencies": {
13
  "@radix-ui/react-checkbox": "^1.1.1",
14
+ "@radix-ui/react-select": "^2.1.1",
15
  "class-variance-authority": "^0.7.0",
16
  "clsx": "^2.1.1",
17
  "lucide-react": "^0.399.0",
src/App.tsx CHANGED
@@ -3,6 +3,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3
  import { Checkbox } from '@/components/ui/checkbox'
4
  import { Input } from '@/components/ui/input'
5
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
 
6
  import { mockData } from './lib/data'
7
 
8
  export interface Model {
@@ -22,11 +23,13 @@ const App: React.FC = () => {
22
  const [comparisonModels, setComparisonModels] = useState<string[]>([])
23
  const [inputTokens, setInputTokens] = useState<number>(1)
24
  const [outputTokens, setOutputTokens] = useState<number>(1)
 
 
25
 
26
  useEffect(() => {
27
  setData(mockData)
28
  // Set default comparison models
29
- setComparisonModels(['GPT-4o', 'Claude 3.5 (Sonnet)', 'Gemini 1.5 Pro'])
30
  }, [])
31
 
32
  const calculatePrice = (price: number, tokens: number): number => {
@@ -37,6 +40,16 @@ const App: React.FC = () => {
37
  return (((modelPrice - comparisonPrice) / comparisonPrice) * 100).toFixed(2)
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
40
  return (
41
  <Card className="w-full max-w-6xl mx-auto">
42
  <CardHeader>
@@ -76,29 +89,67 @@ const App: React.FC = () => {
76
 
77
  <div>
78
  <div className="flex flex-wrap gap-4">
79
- {data
80
- .flatMap((provider) => provider.models)
81
- .map((model) => (
82
- <div key={model.name} className="flex items-center space-x-2">
83
  <Checkbox
84
- id={model.name}
85
- checked={comparisonModels.includes(model.name)}
86
  onCheckedChange={(checked) => {
87
  if (checked) {
88
- setComparisonModels((prev) => [...prev, model.name])
89
  } else {
90
- setComparisonModels((prev) => prev.filter((m) => m !== model.name))
 
 
91
  }
92
  }}
93
  />
94
- <label htmlFor={model.name} className="text-sm font-medium text-gray-700">
95
- {model.name}
 
 
 
96
  </label>
97
  </div>
98
- ))}
 
99
  </div>
100
  </div>
101
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  <p className="italic text-sm text-muted-foreground">
103
  Note: If you use Amazon Bedrock or Azure prices for Anthropic, Cohere or OpenAI should be the same.
104
  </p>
@@ -124,58 +175,65 @@ const App: React.FC = () => {
124
  </TableRow>
125
  </TableHeader>
126
  <TableBody>
127
- {data.flatMap((provider) =>
128
- provider.models.map((model) => (
129
- <TableRow key={`${provider.provider}-${model.name}`}>
130
- <TableCell>
131
- <a href={provider.uri} className="underline">
132
- {provider.provider}
133
- </a>
134
- </TableCell>
135
- <TableCell>{model.name}</TableCell>
136
- <TableCell>${model.inputPrice.toFixed(2)}</TableCell>
137
- <TableCell>${model.outputPrice.toFixed(2)}</TableCell>
138
- <TableCell className="font-bold border border-r-1 border-r-slate-600 border-l-0">
139
- $
140
- {(
141
- calculatePrice(model.inputPrice, inputTokens) + calculatePrice(model.outputPrice, outputTokens)
142
- ).toFixed(2)}
143
- </TableCell>
144
- {comparisonModels.flatMap((comparisonModel) => {
145
- const comparisonModelData = data.flatMap((p) => p.models).find((m) => m.name === comparisonModel)!
146
- return [
147
- <TableCell
148
- key={`${comparisonModel}-input`}
149
- className={`${
150
- parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) < 0
151
- ? 'bg-green-100'
152
- : parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) > 0
153
- ? 'bg-red-100'
154
- : ''
155
- }`}
156
- >
157
- {model.name === comparisonModel
158
- ? '0.00%'
159
- : `${calculateComparison(model.inputPrice, comparisonModelData.inputPrice)}%`}
160
- </TableCell>,
161
- <TableCell
162
- key={`${comparisonModel}-output`}
163
- className={`${
164
- parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) < 0
165
- ? 'bg-green-100'
166
- : parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) > 0
167
- ? 'bg-red-100'
168
- : ''
169
- }`}
170
- >
171
- {model.name === comparisonModel
172
- ? '0.00%'
173
- : `${calculateComparison(model.outputPrice, comparisonModelData.outputPrice)}%`}
174
- </TableCell>,
175
- ]
176
- })}
177
- </TableRow>
178
- ))
 
 
 
 
 
 
 
179
  )}
180
  </TableBody>
181
  </Table>
 
3
  import { Checkbox } from '@/components/ui/checkbox'
4
  import { Input } from '@/components/ui/input'
5
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'
6
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
7
  import { mockData } from './lib/data'
8
 
9
  export interface Model {
 
23
  const [comparisonModels, setComparisonModels] = useState<string[]>([])
24
  const [inputTokens, setInputTokens] = useState<number>(1)
25
  const [outputTokens, setOutputTokens] = useState<number>(1)
26
+ const [selectedProvider, setSelectedProvider] = useState<string>('All')
27
+ const [selectedModel, setSelectedModel] = useState<string>('All')
28
 
29
  useEffect(() => {
30
  setData(mockData)
31
  // Set default comparison models
32
+ setComparisonModels(['OpenAI:GPT-4o', 'Anthropic:Claude 3.5 (Sonnet)', 'Google Vertex AI:Gemini 1.5 Pro'])
33
  }, [])
34
 
35
  const calculatePrice = (price: number, tokens: number): number => {
 
40
  return (((modelPrice - comparisonPrice) / comparisonPrice) * 100).toFixed(2)
41
  }
42
 
43
+ const filteredData = data.filter((provider) => {
44
+ if (selectedProvider !== 'All' && provider.provider !== selectedProvider) {
45
+ return false
46
+ }
47
+ if (selectedModel !== 'All') {
48
+ return provider.models.some((model) => model.name === selectedModel)
49
+ }
50
+ return true
51
+ })
52
+
53
  return (
54
  <Card className="w-full max-w-6xl mx-auto">
55
  <CardHeader>
 
89
 
90
  <div>
91
  <div className="flex flex-wrap gap-4">
92
+ {data.flatMap((provider) =>
93
+ provider.models.map((model) => (
94
+ <div key={`${provider.provider}:${model.name}`} className="flex items-center space-x-2">
 
95
  <Checkbox
96
+ id={`${provider.provider}:${model.name}`}
97
+ checked={comparisonModels.includes(`${provider.provider}:${model.name}`)}
98
  onCheckedChange={(checked) => {
99
  if (checked) {
100
+ setComparisonModels((prev) => [...prev, `${provider.provider}:${model.name}`])
101
  } else {
102
+ setComparisonModels((prev) =>
103
+ prev.filter((m) => m !== `${provider.provider}:${model.name}`)
104
+ )
105
  }
106
  }}
107
  />
108
+ <label
109
+ htmlFor={`${provider.provider}:${model.name}`}
110
+ className="text-sm font-medium text-gray-700"
111
+ >
112
+ {provider.provider}: {model.name}
113
  </label>
114
  </div>
115
+ ))
116
+ )}
117
  </div>
118
  </div>
119
  </div>
120
+
121
+ <div className="flex gap-4 mb-4">
122
+ <Select onValueChange={(value) => setSelectedProvider(value)} defaultValue="All">
123
+ <SelectTrigger className="w-[180px]">
124
+ <SelectValue placeholder="Select Provider" />
125
+ </SelectTrigger>
126
+ <SelectContent>
127
+ <SelectItem value="All">All Providers</SelectItem>
128
+ {data.map((provider) => (
129
+ <SelectItem key={provider.provider} value={provider.provider}>
130
+ {provider.provider}
131
+ </SelectItem>
132
+ ))}
133
+ </SelectContent>
134
+ </Select>
135
+
136
+ <Select onValueChange={(value) => setSelectedModel(value)} defaultValue="All">
137
+ <SelectTrigger className="w-[180px]">
138
+ <SelectValue placeholder="Select Model" />
139
+ </SelectTrigger>
140
+ <SelectContent>
141
+ <SelectItem value="All">All Models</SelectItem>
142
+ {data
143
+ .flatMap((provider) => provider.models)
144
+ .map((model) => (
145
+ <SelectItem key={model.name} value={model.name}>
146
+ {model.name}
147
+ </SelectItem>
148
+ ))}
149
+ </SelectContent>
150
+ </Select>
151
+ </div>
152
+
153
  <p className="italic text-sm text-muted-foreground">
154
  Note: If you use Amazon Bedrock or Azure prices for Anthropic, Cohere or OpenAI should be the same.
155
  </p>
 
175
  </TableRow>
176
  </TableHeader>
177
  <TableBody>
178
+ {filteredData.flatMap((provider) =>
179
+ provider.models
180
+ .filter((model) => selectedModel === 'All' || model.name === selectedModel)
181
+ .map((model) => (
182
+ <TableRow key={`${provider.provider}-${model.name}`}>
183
+ <TableCell>
184
+ <a href={provider.uri} className="underline">
185
+ {provider.provider}
186
+ </a>
187
+ </TableCell>
188
+ <TableCell>{model.name}</TableCell>
189
+ <TableCell>${model.inputPrice.toFixed(2)}</TableCell>
190
+ <TableCell>${model.outputPrice.toFixed(2)}</TableCell>
191
+ <TableCell className="font-bold border border-r-1 border-r-slate-600 border-l-0">
192
+ $
193
+ {(
194
+ calculatePrice(model.inputPrice, inputTokens) +
195
+ calculatePrice(model.outputPrice, outputTokens)
196
+ ).toFixed(2)}
197
+ </TableCell>
198
+ {comparisonModels.flatMap((comparisonModel) => {
199
+ const [comparisonProvider, comparisonModelName] = comparisonModel.split(':')
200
+ const comparisonModelData = data
201
+ .find((p) => p.provider === comparisonProvider)
202
+ ?.models.find((m) => m.name === comparisonModelName)!
203
+ return [
204
+ <TableCell
205
+ key={`${comparisonModel}-input`}
206
+ className={`${
207
+ parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) < 0
208
+ ? 'bg-green-100'
209
+ : parseFloat(calculateComparison(model.inputPrice, comparisonModelData.inputPrice)) > 0
210
+ ? 'bg-red-100'
211
+ : ''
212
+ }`}
213
+ >
214
+ {`${provider.provider}:${model.name}` === comparisonModel
215
+ ? '0.00%'
216
+ : `${calculateComparison(model.inputPrice, comparisonModelData.inputPrice)}%`}
217
+ </TableCell>,
218
+ <TableCell
219
+ key={`${comparisonModel}-output`}
220
+ className={`${
221
+ parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) < 0
222
+ ? 'bg-green-100'
223
+ : parseFloat(calculateComparison(model.outputPrice, comparisonModelData.outputPrice)) >
224
+ 0
225
+ ? 'bg-red-100'
226
+ : ''
227
+ }`}
228
+ >
229
+ {`${provider.provider}:${model.name}` === comparisonModel
230
+ ? '0.00%'
231
+ : `${calculateComparison(model.outputPrice, comparisonModelData.outputPrice)}%`}
232
+ </TableCell>,
233
+ ]
234
+ })}
235
+ </TableRow>
236
+ ))
237
  )}
238
  </TableBody>
239
  </Table>
src/components/ui/select.tsx ADDED
@@ -0,0 +1,143 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as React from 'react'
2
+ import * as SelectPrimitive from '@radix-ui/react-select'
3
+ import { Check, ChevronDown, ChevronUp } from 'lucide-react'
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const Select = SelectPrimitive.Root
8
+
9
+ const SelectGroup = SelectPrimitive.Group
10
+
11
+ const SelectValue = SelectPrimitive.Value
12
+
13
+ const SelectTrigger = React.forwardRef<
14
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
15
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
16
+ >(({ className, children, ...props }, ref) => (
17
+ <SelectPrimitive.Trigger
18
+ ref={ref}
19
+ className={cn(
20
+ 'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ {children}
26
+ <SelectPrimitive.Icon asChild>
27
+ <ChevronDown className="h-4 w-4 opacity-50" />
28
+ </SelectPrimitive.Icon>
29
+ </SelectPrimitive.Trigger>
30
+ ))
31
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
32
+
33
+ const SelectScrollUpButton = React.forwardRef<
34
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
35
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
36
+ >(({ className, ...props }, ref) => (
37
+ <SelectPrimitive.ScrollUpButton
38
+ ref={ref}
39
+ className={cn('flex cursor-default items-center justify-center py-1', className)}
40
+ {...props}
41
+ >
42
+ <ChevronUp className="h-4 w-4" />
43
+ </SelectPrimitive.ScrollUpButton>
44
+ ))
45
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
46
+
47
+ const SelectScrollDownButton = React.forwardRef<
48
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
49
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
50
+ >(({ className, ...props }, ref) => (
51
+ <SelectPrimitive.ScrollDownButton
52
+ ref={ref}
53
+ className={cn('flex cursor-default items-center justify-center py-1', className)}
54
+ {...props}
55
+ >
56
+ <ChevronDown className="h-4 w-4" />
57
+ </SelectPrimitive.ScrollDownButton>
58
+ ))
59
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
60
+
61
+ const SelectContent = React.forwardRef<
62
+ React.ElementRef<typeof SelectPrimitive.Content>,
63
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
64
+ >(({ className, children, position = 'popper', ...props }, ref) => (
65
+ <SelectPrimitive.Portal>
66
+ <SelectPrimitive.Content
67
+ ref={ref}
68
+ className={cn(
69
+ 'relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
70
+ position === 'popper' &&
71
+ 'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
72
+ className
73
+ )}
74
+ position={position}
75
+ {...props}
76
+ >
77
+ <SelectScrollUpButton />
78
+ <SelectPrimitive.Viewport
79
+ className={cn(
80
+ 'p-1',
81
+ position === 'popper' &&
82
+ 'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]'
83
+ )}
84
+ >
85
+ {children}
86
+ </SelectPrimitive.Viewport>
87
+ <SelectScrollDownButton />
88
+ </SelectPrimitive.Content>
89
+ </SelectPrimitive.Portal>
90
+ ))
91
+ SelectContent.displayName = SelectPrimitive.Content.displayName
92
+
93
+ const SelectLabel = React.forwardRef<
94
+ React.ElementRef<typeof SelectPrimitive.Label>,
95
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
96
+ >(({ className, ...props }, ref) => (
97
+ <SelectPrimitive.Label ref={ref} className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)} {...props} />
98
+ ))
99
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
100
+
101
+ const SelectItem = React.forwardRef<
102
+ React.ElementRef<typeof SelectPrimitive.Item>,
103
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
104
+ >(({ className, children, ...props }, ref) => (
105
+ <SelectPrimitive.Item
106
+ ref={ref}
107
+ className={cn(
108
+ 'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
109
+ className
110
+ )}
111
+ {...props}
112
+ >
113
+ <span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
114
+ <SelectPrimitive.ItemIndicator>
115
+ <Check className="h-4 w-4" />
116
+ </SelectPrimitive.ItemIndicator>
117
+ </span>
118
+
119
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
120
+ </SelectPrimitive.Item>
121
+ ))
122
+ SelectItem.displayName = SelectPrimitive.Item.displayName
123
+
124
+ const SelectSeparator = React.forwardRef<
125
+ React.ElementRef<typeof SelectPrimitive.Separator>,
126
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
127
+ >(({ className, ...props }, ref) => (
128
+ <SelectPrimitive.Separator ref={ref} className={cn('-mx-1 my-1 h-px bg-muted', className)} {...props} />
129
+ ))
130
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
131
+
132
+ export {
133
+ Select,
134
+ SelectGroup,
135
+ SelectValue,
136
+ SelectTrigger,
137
+ SelectContent,
138
+ SelectLabel,
139
+ SelectItem,
140
+ SelectSeparator,
141
+ SelectScrollUpButton,
142
+ SelectScrollDownButton,
143
+ }
src/lib/data.ts CHANGED
@@ -71,4 +71,51 @@ export const mockData: Provider[] = [
71
  { name: 'Llama 3 70b', inputPrice: 0.9, outputPrice: 0.9 },
72
  ],
73
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  ]
 
71
  { name: 'Llama 3 70b', inputPrice: 0.9, outputPrice: 0.9 },
72
  ],
73
  },
74
+ {
75
+ provider: 'Replicate',
76
+ uri: 'https://replicate.com/pricing',
77
+ models: [
78
+ { name: 'Llama 3 70b', inputPrice: 0.65, outputPrice: 2.75 },
79
+ { name: 'Mixtral 8x7B', inputPrice: 0.30, outputPrice: 1.00 },
80
+ ],
81
+ },
82
+ {
83
+ provider: 'IBM WatsonX',
84
+ uri: 'https://www.ibm.com/products/watsonx-ai/foundation-models',
85
+ models: [
86
+ { name: 'Llama 3 70b', inputPrice: 1.80, outputPrice: 0.65 },
87
+ ],
88
+ },
89
+ {
90
+ provider: 'Groq',
91
+ uri: 'https://wow.groq.com/',
92
+ models: [
93
+ { name: 'Llama 3 70b', inputPrice: 0.59, outputPrice: 0.79 },
94
+ { name: 'Mixtral 8x7B', inputPrice: 0.24, outputPrice: 0.24 },
95
+ ],
96
+ },
97
+ {
98
+ provider: 'Fireworks',
99
+ uri: 'https://fireworks.ai/pricing',
100
+ models: [
101
+ { name: 'Llama 3 70b', inputPrice: 0.90, outputPrice: 0.90 },
102
+ { name: 'Mixtral 8x7B', inputPrice: 0.50, outputPrice: 0.50 },
103
+ ],
104
+ },
105
+ {
106
+ provider: '01.ai',
107
+ uri: 'https://platform.01.ai/docs#models-and-pricing',
108
+ models: [
109
+ { name: 'Yi-Large', inputPrice: 3.00, outputPrice: 3.00 },
110
+ ],
111
+ },
112
+ {
113
+ provider: 'Perplexity',
114
+ uri: 'https://docs.perplexity.ai/docs/pricing',
115
+ models: [
116
+ { name: 'Llama 3 70b', inputPrice: 1.0, outputPrice: 1.0 },
117
+ { name: 'Mixtral 8x7B', inputPrice: 0.60, outputPrice: 0.60 },
118
+ ],
119
+ }
120
+
121
  ]