add knip as a dev dependency, remove some unused files

This commit is contained in:
yuneng-jiang 2026-02-07 15:51:21 -08:00
parent 5de7fe2897
commit 54828e3783
14 changed files with 524 additions and 1323 deletions

View File

@ -0,0 +1,18 @@
{
"$schema": "https://unpkg.com/knip@5/schema.json",
"entry": ["scripts/**/*.ts"],
"project": [
"src/**/*.{ts,tsx}",
"tests/**/*.{ts,tsx}",
"scripts/**/*.ts",
"e2e_tests/**/*.ts"
],
"playwright": {
"config": "e2e_tests/playwright.config.ts",
"entry": [
"e2e_tests/**/*.spec.ts",
"e2e_tests/**/*.setup.ts",
"e2e_tests/globalSetup.ts"
]
}
}

View File

@ -59,10 +59,11 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-unused-imports": "^4.2.0",
"jsdom": "^27.0.0",
"knip": "^5.83.1",
"postcss": "^8.4.33",
"prettier": "3.2.5",
"tailwindcss": "^3.4.1",
"typescript": "5.3.3",
"typescript": "^5.3.3",
"vite": "^7.1.11",
"vitest": "^3.2.4"
},
@ -2044,6 +2045,306 @@
"node": ">=12.4.0"
}
},
"node_modules/@oxc-resolver/binding-android-arm-eabi": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.17.0.tgz",
"integrity": "sha512-kVnY21v0GyZ/+LG6EIO48wK3mE79BUuakHUYLIqobO/Qqq4mJsjuYXMSn3JtLcKZpN1HDVit4UHpGJHef1lrlw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@oxc-resolver/binding-android-arm64": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.17.0.tgz",
"integrity": "sha512-Pf8e3XcsK9a8RHInoAtEcrwf2vp7V9bSturyUUYxw9syW6E7cGi7z9+6ADXxm+8KAevVfLA7pfBg8NXTvz/HOw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
]
},
"node_modules/@oxc-resolver/binding-darwin-arm64": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.17.0.tgz",
"integrity": "sha512-lVSgKt3biecofXVr8e1hnfX0IYMd4A6VCxmvOmHsFt5Zbmt0lkO4S2ap2bvQwYDYh5ghUNamC7M2L8K6vishhQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxc-resolver/binding-darwin-x64": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.17.0.tgz",
"integrity": "sha512-+/raxVJE1bo7R4fA9Yp0wm3slaCOofTEeUzM01YqEGcRDLHB92WRGjRhagMG2wGlvqFuSiTp81DwSbBVo/g6AQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
]
},
"node_modules/@oxc-resolver/binding-freebsd-x64": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.17.0.tgz",
"integrity": "sha512-x9Ks56n+n8h0TLhzA6sJXa2tGh3uvMGpBppg6PWf8oF0s5S/3p/J6k1vJJ9lIUtTmenfCQEGKnFokpRP4fLTLg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
]
},
"node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.17.0.tgz",
"integrity": "sha512-Wf3w07Ow9kXVJrS0zmsaFHKOGhXKXE8j1tNyy+qIYDsQWQ4UQZVx5SjlDTcqBnFerlp3Z3Is0RjmVzgoLG3qkA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-arm-musleabihf": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.17.0.tgz",
"integrity": "sha512-N0OKA1al1gQ5Gm7Fui1RWlXaHRNZlwMoBLn3TVtSXX+WbnlZoVyDqqOqFL8+pVEHhhxEA2LR8kmM0JO6FAk6dg==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-arm64-gnu": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.17.0.tgz",
"integrity": "sha512-wdcQ7Niad9JpjZIGEeqKJnTvczVunqlZ/C06QzR5zOQNeLVRScQ9S5IesKWUAPsJQDizV+teQX53nTK+Z5Iy+g==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-arm64-musl": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.17.0.tgz",
"integrity": "sha512-65B2/t39HQN5AEhkLsC+9yBD1iRUkKOIhfmJEJ7g6wQ9kylra7JRmNmALFjbsj0VJsoSQkpM8K07kUZuNJ9Kxw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-ppc64-gnu": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.17.0.tgz",
"integrity": "sha512-kExgm3TLK21dNMmcH+xiYGbc6BUWvT03PUZ2aYn8mUzGPeeORklBhg3iYcaBI3ZQHB25412X1Z6LLYNjt4aIaA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-riscv64-gnu": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.17.0.tgz",
"integrity": "sha512-1utUJC714/ydykZQE8c7QhpEyM4SaslMfRXxN9G61KYazr6ndt85LaubK3EZCSD50vVEfF4PVwFysCSO7LN9uA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-riscv64-musl": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.17.0.tgz",
"integrity": "sha512-mayiYOl3LMmtO2CLn4I5lhanfxEo0LAqlT/EQyFbu1ZN3RS+Xa7Q3JEM0wBpVIyfO/pqFrjvC5LXw/mHNDEL7A==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-s390x-gnu": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.17.0.tgz",
"integrity": "sha512-Ow/yI+CrUHxIIhn/Y1sP/xoRKbCC3x9O1giKr3G/pjMe+TCJ5ZmfqVWU61JWwh1naC8X5Xa7uyLnbzyYqPsHfg==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-x64-gnu": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.17.0.tgz",
"integrity": "sha512-Z4J7XlPMQOLPANyu6y3B3V417Md4LKH5bV6bhqgaG99qLHmU5LV2k9ErV14fSqoRc/GU/qOpqMdotxiJqN/YWg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-linux-x64-musl": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.17.0.tgz",
"integrity": "sha512-0effK+8lhzXsgsh0Ny2ngdnTPF30v6QQzVFApJ1Ctk315YgpGkghkelvrLYYgtgeFJFrzwmOJ2nDvCrUFKsS2Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
]
},
"node_modules/@oxc-resolver/binding-openharmony-arm64": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.17.0.tgz",
"integrity": "sha512-kFB48dRUW6RovAICZaxHKdtZe+e94fSTNA2OedXokzMctoU54NPZcv0vUX5PMqyikLIKJBIlW7laQidnAzNrDA==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openharmony"
]
},
"node_modules/@oxc-resolver/binding-wasm32-wasi": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.17.0.tgz",
"integrity": "sha512-a3elKSBLPT0OoRPxTkCIIc+4xnOELolEBkPyvdj01a6PSdSmyJ1NExWjWLaXnT6wBMblvKde5RmSwEi3j+jZpg==",
"cpu": [
"wasm32"
],
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@napi-rs/wasm-runtime": "^1.1.1"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@oxc-resolver/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
"integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.7.1",
"@emnapi/runtime": "^1.7.1",
"@tybys/wasm-util": "^0.10.1"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Brooooooklyn"
}
},
"node_modules/@oxc-resolver/binding-win32-arm64-msvc": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.17.0.tgz",
"integrity": "sha512-4eszUsSDb9YVx0RtYkPWkxxtSZIOgfeiX//nG5cwRRArg178w4RCqEF1kbKPud9HPrp1rXh7gE4x911OhvTnPg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oxc-resolver/binding-win32-ia32-msvc": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.17.0.tgz",
"integrity": "sha512-t946xTXMmR7yGH0KAe9rB055/X4EPIu93JUvjchl2cizR5QbuwkUV7vLS2BS6x6sfvDoQb6rWYnV1HCci6tBSg==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@oxc-resolver/binding-win32-x64-msvc": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.17.0.tgz",
"integrity": "sha512-pX6s2kMXLQg+hlqKk5UqOW09iLLxnTkvn8ohpYp2Mhsm2yzDPCx9dyOHiB/CQixLzTkLQgWWJykN4Z3UfRKW4Q==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
]
},
"node_modules/@playwright/test": {
"version": "1.58.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz",
@ -6266,6 +6567,16 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/fd-package-json": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz",
"integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"walk-up-path": "^4.0.0"
}
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
@ -6422,6 +6733,22 @@
"node": ">=0.4.x"
}
},
"node_modules/formatly": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz",
"integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==",
"dev": true,
"license": "MIT",
"dependencies": {
"fd-package-json": "^2.0.0"
},
"bin": {
"formatly": "bin/index.mjs"
},
"engines": {
"node": ">=18.3.0"
}
},
"node_modules/formdata-node": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
@ -7747,6 +8074,114 @@
"json-buffer": "3.0.1"
}
},
"node_modules/knip": {
"version": "5.83.1",
"resolved": "https://registry.npmjs.org/knip/-/knip-5.83.1.tgz",
"integrity": "sha512-av3ZG/Nui6S/BNL8Tmj12yGxYfTnwWnslouW97m40him7o8MwiMjZBY9TPvlEWUci45aVId0/HbgTwSKIDGpMw==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/webpro"
},
{
"type": "opencollective",
"url": "https://opencollective.com/knip"
}
],
"license": "ISC",
"dependencies": {
"@nodelib/fs.walk": "^1.2.3",
"fast-glob": "^3.3.3",
"formatly": "^0.3.0",
"jiti": "^2.6.0",
"js-yaml": "^4.1.1",
"minimist": "^1.2.8",
"oxc-resolver": "^11.15.0",
"picocolors": "^1.1.1",
"picomatch": "^4.0.1",
"smol-toml": "^1.5.2",
"strip-json-comments": "5.0.3",
"zod": "^4.1.11"
},
"bin": {
"knip": "bin/knip.js",
"knip-bun": "bin/knip-bun.js"
},
"engines": {
"node": ">=18.18.0"
},
"peerDependencies": {
"@types/node": ">=18",
"typescript": ">=5.0.4 <7"
}
},
"node_modules/knip/node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/knip/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/knip/node_modules/jiti": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "lib/jiti-cli.mjs"
}
},
"node_modules/knip/node_modules/picomatch": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/knip/node_modules/strip-json-comments": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
"integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/language-subtag-registry": {
"version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
@ -9109,6 +9544,38 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/oxc-resolver": {
"version": "11.17.0",
"resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.17.0.tgz",
"integrity": "sha512-R5P2Tw6th+nQJdNcZGfuppBS/sM0x1EukqYffmlfX2xXLgLGCCPwu4ruEr9Sx29mrpkHgITc130Qps2JR90NdQ==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/Boshen"
},
"optionalDependencies": {
"@oxc-resolver/binding-android-arm-eabi": "11.17.0",
"@oxc-resolver/binding-android-arm64": "11.17.0",
"@oxc-resolver/binding-darwin-arm64": "11.17.0",
"@oxc-resolver/binding-darwin-x64": "11.17.0",
"@oxc-resolver/binding-freebsd-x64": "11.17.0",
"@oxc-resolver/binding-linux-arm-gnueabihf": "11.17.0",
"@oxc-resolver/binding-linux-arm-musleabihf": "11.17.0",
"@oxc-resolver/binding-linux-arm64-gnu": "11.17.0",
"@oxc-resolver/binding-linux-arm64-musl": "11.17.0",
"@oxc-resolver/binding-linux-ppc64-gnu": "11.17.0",
"@oxc-resolver/binding-linux-riscv64-gnu": "11.17.0",
"@oxc-resolver/binding-linux-riscv64-musl": "11.17.0",
"@oxc-resolver/binding-linux-s390x-gnu": "11.17.0",
"@oxc-resolver/binding-linux-x64-gnu": "11.17.0",
"@oxc-resolver/binding-linux-x64-musl": "11.17.0",
"@oxc-resolver/binding-openharmony-arm64": "11.17.0",
"@oxc-resolver/binding-wasm32-wasi": "11.17.0",
"@oxc-resolver/binding-win32-arm64-msvc": "11.17.0",
"@oxc-resolver/binding-win32-ia32-msvc": "11.17.0",
"@oxc-resolver/binding-win32-x64-msvc": "11.17.0"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@ -11109,6 +11576,19 @@
"node": ">=18"
}
},
"node_modules/smol-toml": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.0.tgz",
"integrity": "sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">= 18"
},
"funding": {
"url": "https://github.com/sponsors/cyyynthia"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -12414,6 +12894,16 @@
"node": ">=18"
}
},
"node_modules/walk-up-path": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz",
"integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==",
"dev": true,
"license": "ISC",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/web-streams-polyfill": {
"version": "4.0.0-beta.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
@ -12650,6 +13140,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zwitch": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz",
@ -12659,21 +13159,6 @@
"type": "github",
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.2.33",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz",
"integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==",
"cpu": [
"ia32"
],
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
}
}
}

View File

@ -14,7 +14,9 @@
"format": "prettier --write .",
"format:check": "prettier --check .",
"e2e": "playwright test --config e2e_tests/playwright.config.ts",
"e2e:ui": "playwright test --ui --config e2e_tests/playwright.config.ts"
"e2e:ui": "playwright test --ui --config e2e_tests/playwright.config.ts",
"knip": "knip",
"knip:fix": "knip --fix"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.54.0",
@ -68,10 +70,11 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-unused-imports": "^4.2.0",
"jsdom": "^27.0.0",
"knip": "^5.83.1",
"postcss": "^8.4.33",
"prettier": "3.2.5",
"tailwindcss": "^3.4.1",
"typescript": "5.3.3",
"typescript": "^5.3.3",
"vite": "^7.1.11",
"vitest": "^3.2.4"
},

View File

@ -1,134 +0,0 @@
import { Select, SelectItem, Text } from "@tremor/react";
import React, { useState } from "react";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { Team } from "@/components/key_team_helpers/key_list";
interface FilterByContentProps {
setSelectedAPIKey: (key: any) => void;
keys: any[] | null;
teams: Team[] | null;
setSelectedCustomer: (customer: string | null) => void;
allEndUsers: any[];
}
const FilterByContent = ({
setSelectedAPIKey,
keys,
teams,
setSelectedCustomer,
allEndUsers,
}: FilterByContentProps) => {
const { premiumUser } = useAuthorized();
const [selectedTeamFilter, setSelectedTeamFilter] = useState<string | null>(null);
return (
<div>
<Text className="mb-1">Select API Key Name</Text>
{premiumUser ? (
<div>
<Select defaultValue="all-keys">
<SelectItem
key="all-keys"
value="all-keys"
onClick={() => {
setSelectedAPIKey(null);
}}
>
All Keys
</SelectItem>
{keys?.map((key: any, index: number) => {
if (key && key["key_alias"] !== null && key["key_alias"].length > 0) {
return (
<SelectItem
key={index}
value={String(index)}
onClick={() => {
setSelectedAPIKey(key);
}}
>
{key["key_alias"]}
</SelectItem>
);
}
return null;
})}
</Select>
<Text className="mt-1">Select Customer Name</Text>
<Select defaultValue="all-customers">
<SelectItem
key="all-customers"
value="all-customers"
onClick={() => {
setSelectedCustomer(null);
}}
>
All Customers
</SelectItem>
{allEndUsers?.map((user: any, index: number) => {
return (
<SelectItem
key={index}
value={user}
onClick={() => {
setSelectedCustomer(user);
}}
>
{user}
</SelectItem>
);
})}
</Select>
<Text className="mt-1">Select Team</Text>
<Select
className="w-64 relative z-50"
defaultValue="all"
value={selectedTeamFilter ?? "all"}
onValueChange={(value) => setSelectedTeamFilter(value === "all" ? null : value)}
>
<SelectItem value="all">All Teams</SelectItem>
{teams
?.filter((team) => team.team_id)
.map((team) => (
<SelectItem key={team.team_id} value={team.team_id}>
{team.team_alias
? `${team.team_alias} (${team.team_id.slice(0, 8)}...)`
: `Team ${team.team_id.slice(0, 8)}...`}
</SelectItem>
))}
</Select>
</div>
) : (
<div>
{/* ... existing non-premium user content ... */}
<Text className="mt-1">Select Team</Text>
<Select
className="w-64 relative z-50"
defaultValue="all"
value={selectedTeamFilter ?? "all"}
onValueChange={(value) => setSelectedTeamFilter(value === "all" ? null : value)}
>
<SelectItem value="all">All Teams</SelectItem>
{teams
?.filter((team) => team.team_id)
.map((team) => (
<SelectItem key={team.team_id} value={team.team_id}>
{team.team_alias
? `${team.team_alias} (${team.team_id.slice(0, 8)}...)`
: `Team ${team.team_id.slice(0, 8)}...`}
</SelectItem>
))}
</Select>
</div>
)}
</div>
);
};
export default FilterByContent;

View File

@ -1,474 +0,0 @@
import {
AreaChart,
BarChart,
Button,
Card,
Col,
DateRangePickerValue,
Grid,
Select,
SelectItem,
Subtitle,
Tab,
TabGroup,
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
TabList,
TabPanel,
TabPanels,
Text,
Title,
} from "@tremor/react";
import UsageDatePicker from "@/components/shared/usage_date_picker";
import { Popover } from "antd";
import { FilterIcon } from "@heroicons/react/outline";
import TimeToFirstToken from "@/components/model_metrics/time_to_first_token";
import React, { useEffect } from "react";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { Team } from "@/components/key_team_helpers/key_list";
import {
adminGlobalActivityExceptions,
adminGlobalActivityExceptionsPerDeployment,
modelExceptionsCall,
modelMetricsCall,
modelMetricsSlowResponsesCall,
streamingModelMetricsCall,
} from "@/components/networking";
import FilterByContent from "@/app/(dashboard)/models-and-endpoints/components/ModelAnalyticsTab/FilterByContent";
interface GlobalExceptionActivityData {
sum_num_rate_limit_exceptions: number;
daily_data: { date: string; num_rate_limit_exceptions: number }[];
}
interface ModelAnalyticsTabProps {
dateValue: DateRangePickerValue;
setDateValue: (dateValue: DateRangePickerValue) => void;
selectedModelGroup: string | null;
availableModelGroups: string[];
setShowAdvancedFilters: (showAdvancedFilters: boolean) => void;
modelMetrics: any[];
modelMetricsCategories: any[];
streamingModelMetrics: any[];
streamingModelMetricsCategories: any[];
customTooltip: any;
slowResponsesData: any[];
modelExceptions: any[];
globalExceptionData: GlobalExceptionActivityData;
allExceptions: any[];
globalExceptionPerDeployment: any[];
setSelectedAPIKey: (key: string | null) => void;
keys: any[] | null;
setSelectedCustomer: (selectedCustomer: string | null) => void;
teams: Team[] | null;
allEndUsers: any[];
selectedAPIKey: any;
selectedCustomer: string | null;
selectedTeam: string | null;
setSelectedModelGroup: (selectedModelGroup: string | null) => void;
setModelMetrics: (metrics: any) => void;
setModelMetricsCategories: (categories: any) => void;
setStreamingModelMetrics: (metrics: any) => void;
setStreamingModelMetricsCategories: (categories: any) => void;
setSlowResponsesData: (data: any) => void;
setModelExceptions: (exceptions: any) => void;
setAllExceptions: (exceptions: any) => void;
setGlobalExceptionData: (data: any) => void;
setGlobalExceptionPerDeployment: (data: any) => void;
}
const ModelAnalyticsTab = ({
dateValue,
setDateValue,
selectedModelGroup,
availableModelGroups,
setShowAdvancedFilters,
modelMetrics,
modelMetricsCategories,
streamingModelMetrics,
streamingModelMetricsCategories,
customTooltip,
slowResponsesData,
modelExceptions,
globalExceptionData,
allExceptions,
globalExceptionPerDeployment,
setSelectedAPIKey,
keys,
setSelectedCustomer,
teams,
allEndUsers,
selectedAPIKey,
selectedCustomer,
selectedTeam,
setSelectedModelGroup,
setModelMetrics,
setModelMetricsCategories,
setStreamingModelMetrics,
setStreamingModelMetricsCategories,
setSlowResponsesData,
setModelExceptions,
setAllExceptions,
setGlobalExceptionData,
setGlobalExceptionPerDeployment,
}: ModelAnalyticsTabProps) => {
const { accessToken, userId, userRole, premiumUser } = useAuthorized();
useEffect(() => {
updateModelMetrics(selectedModelGroup, dateValue.from, dateValue.to);
}, [selectedAPIKey, selectedCustomer, selectedTeam]);
const updateModelMetrics = async (
modelGroup: string | null,
startTime: Date | undefined,
endTime: Date | undefined,
) => {
console.log("Updating model metrics for group:", modelGroup);
if (!accessToken || !userId || !userRole || !startTime || !endTime) {
return;
}
console.log("inside updateModelMetrics - startTime:", startTime, "endTime:", endTime);
setSelectedModelGroup(modelGroup);
let selected_token = selectedAPIKey?.token;
if (selected_token === undefined) {
selected_token = null;
}
let selected_customer = selectedCustomer;
if (selected_customer === undefined) {
selected_customer = null;
}
try {
const modelMetricsResponse = await modelMetricsCall(
accessToken,
userId,
userRole,
modelGroup,
startTime.toISOString(),
endTime.toISOString(),
selected_token,
selected_customer,
);
console.log("Model metrics response:", modelMetricsResponse);
// Assuming modelMetricsResponse now contains the metric data for the specified model group
setModelMetrics(modelMetricsResponse.data);
setModelMetricsCategories(modelMetricsResponse.all_api_bases);
const streamingModelMetricsResponse = await streamingModelMetricsCall(
accessToken,
modelGroup,
startTime.toISOString(),
endTime.toISOString(),
);
// Assuming modelMetricsResponse now contains the metric data for the specified model group
setStreamingModelMetrics(streamingModelMetricsResponse.data);
setStreamingModelMetricsCategories(streamingModelMetricsResponse.all_api_bases);
const modelExceptionsResponse = await modelExceptionsCall(
accessToken,
userId,
userRole,
modelGroup,
startTime.toISOString(),
endTime.toISOString(),
selected_token,
selected_customer,
);
console.log("Model exceptions response:", modelExceptionsResponse);
setModelExceptions(modelExceptionsResponse.data);
setAllExceptions(modelExceptionsResponse.exception_types);
const slowResponses = await modelMetricsSlowResponsesCall(
accessToken,
userId,
userRole,
modelGroup,
startTime.toISOString(),
endTime.toISOString(),
selected_token,
selected_customer,
);
console.log("slowResponses:", slowResponses);
setSlowResponsesData(slowResponses);
if (modelGroup) {
const dailyExceptions = await adminGlobalActivityExceptions(
accessToken,
startTime?.toISOString().split("T")[0],
endTime?.toISOString().split("T")[0],
modelGroup,
);
setGlobalExceptionData(dailyExceptions);
const dailyExceptionsPerDeplyment = await adminGlobalActivityExceptionsPerDeployment(
accessToken,
startTime?.toISOString().split("T")[0],
endTime?.toISOString().split("T")[0],
modelGroup,
);
setGlobalExceptionPerDeployment(dailyExceptionsPerDeplyment);
}
} catch (error) {
console.error("Failed to fetch model metrics", error);
}
};
return (
<TabPanel>
<div className="mb-4 rounded-md border border-red-500 bg-red-50 p-4">
<Text className="font-semibold text-red-700">
This page is deprecated and will be removed in the future. Some functionality may not work as expected.
</Text>
</div>
<Grid numItems={4} className="mt-2 mb-2">
<Col>
<UsageDatePicker
value={dateValue}
className="mr-2"
onValueChange={(value) => {
setDateValue(value);
updateModelMetrics(selectedModelGroup, value.from, value.to);
}}
/>
</Col>
<Col className="ml-2">
<Text>Select Model Group</Text>
<Select
defaultValue={selectedModelGroup ? selectedModelGroup : availableModelGroups[0]}
value={selectedModelGroup ? selectedModelGroup : availableModelGroups[0]}
>
{availableModelGroups.map((group, idx) => (
<SelectItem
key={idx}
value={group}
onClick={() => updateModelMetrics(group, dateValue.from, dateValue.to)}
>
{group}
</SelectItem>
))}
</Select>
</Col>
<Col>
<Popover
trigger="click"
content={
<FilterByContent
allEndUsers={allEndUsers}
keys={keys}
setSelectedAPIKey={setSelectedAPIKey}
setSelectedCustomer={setSelectedCustomer}
teams={teams}
/>
}
overlayStyle={{
width: "20vw",
}}
>
<Button
icon={FilterIcon}
size="md"
variant="secondary"
className="mt-4 ml-2"
style={{
border: "none",
}}
onClick={() => setShowAdvancedFilters(true)}
></Button>
</Popover>
</Col>
</Grid>
<Grid numItems={2}>
<Col>
<Card className="mr-2 max-h-[400px] min-h-[400px]">
<TabGroup>
<TabList variant="line" defaultValue="1">
<Tab value="1">Avg. Latency per Token</Tab>
<Tab value="2">Time to first token</Tab>
</TabList>
<TabPanels>
<TabPanel>
<p className="text-gray-500 italic"> (seconds/token)</p>
<Text className="text-gray-500 italic mt-1 mb-1">
average Latency for successfull requests divided by the total tokens
</Text>
{modelMetrics && modelMetricsCategories && (
<AreaChart
title="Model Latency"
className="h-72"
data={modelMetrics}
showLegend={false}
index="date"
categories={modelMetricsCategories}
connectNulls={true}
customTooltip={customTooltip}
/>
)}
</TabPanel>
<TabPanel>
<TimeToFirstToken
modelMetrics={streamingModelMetrics}
modelMetricsCategories={streamingModelMetricsCategories}
customTooltip={customTooltip}
premiumUser={premiumUser}
/>
</TabPanel>
</TabPanels>
</TabGroup>
</Card>
</Col>
<Col>
<Card className="ml-2 max-h-[400px] min-h-[400px] overflow-y-auto">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>Deployment</TableHeaderCell>
<TableHeaderCell>Success Responses</TableHeaderCell>
<TableHeaderCell>
Slow Responses <p>Success Responses taking 600+s</p>
</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{slowResponsesData.map((metric, idx) => (
<TableRow key={idx}>
<TableCell>{metric.api_base}</TableCell>
<TableCell>{metric.total_count}</TableCell>
<TableCell>{metric.slow_count}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
</Col>
</Grid>
<Grid numItems={1} className="gap-2 w-full mt-2">
<Card>
<Title>All Exceptions for {selectedModelGroup}</Title>
<BarChart
className="h-60"
data={modelExceptions}
index="model"
categories={allExceptions}
stack={true}
yAxisWidth={30}
/>
</Card>
</Grid>
<Grid numItems={1} className="gap-2 w-full mt-2">
<Card>
<Title>All Up Rate Limit Errors (429) for {selectedModelGroup}</Title>
<Grid numItems={1}>
<Col>
<Subtitle
style={{
fontSize: "15px",
fontWeight: "normal",
color: "#535452",
}}
>
Num Rate Limit Errors {globalExceptionData.sum_num_rate_limit_exceptions}
</Subtitle>
<BarChart
className="h-40"
data={globalExceptionData.daily_data}
index="date"
colors={["rose"]}
categories={["num_rate_limit_exceptions"]}
onValueChange={(v) => console.log(v)}
/>
</Col>
<Col></Col>
</Grid>
</Card>
{premiumUser ? (
<>
{globalExceptionPerDeployment.map((globalActivity, index) => (
<Card key={index}>
<Title>{globalActivity.api_base ? globalActivity.api_base : "Unknown API Base"}</Title>
<Grid numItems={1}>
<Col>
<Subtitle
style={{
fontSize: "15px",
fontWeight: "normal",
color: "#535452",
}}
>
Num Rate Limit Errors (429) {globalActivity.sum_num_rate_limit_exceptions}
</Subtitle>
<BarChart
className="h-40"
data={globalActivity.daily_data}
index="date"
colors={["rose"]}
categories={["num_rate_limit_exceptions"]}
onValueChange={(v) => console.log(v)}
/>
</Col>
</Grid>
</Card>
))}
</>
) : (
<>
{globalExceptionPerDeployment &&
globalExceptionPerDeployment.length > 0 &&
globalExceptionPerDeployment.slice(0, 1).map((globalActivity, index) => (
<Card key={index}>
<Title> Rate Limit Errors by Deployment</Title>
<p className="mb-2 text-gray-500 italic text-[12px]">Upgrade to see exceptions for all deployments</p>
<Button variant="primary" className="mb-2">
<a href="https://forms.gle/W3U4PZpJGFHWtHyA9" target="_blank">
Get Free Trial
</a>
</Button>
<Card>
<Title>{globalActivity.api_base}</Title>
<Grid numItems={1}>
<Col>
<Subtitle
style={{
fontSize: "15px",
fontWeight: "normal",
color: "#535452",
}}
>
Num Rate Limit Errors {globalActivity.sum_num_rate_limit_exceptions}
</Subtitle>
<BarChart
className="h-40"
data={globalActivity.daily_data}
index="date"
colors={["rose"]}
categories={["num_rate_limit_exceptions"]}
onValueChange={(v) => console.log(v)}
/>
</Col>
</Grid>
</Card>
</Card>
))}
</>
)}
</Grid>
</TabPanel>
);
};
export default ModelAnalyticsTab;

View File

@ -1,153 +0,0 @@
import React from "react";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeaderCell,
TableRow,
Text,
Badge,
Icon,
Card,
} from "@tremor/react";
import { Tooltip } from "antd";
interface Column {
header: string;
accessor: string;
cellRenderer?: (value: any, row: any) => React.ReactNode;
width?: string;
style?: React.CSSProperties;
}
interface Action<T = any> {
icon?: React.ComponentType<any>;
onClick: (item: T) => void;
condition?: () => boolean;
tooltip?: string;
}
interface DeleteModalProps {
isOpen: boolean;
onConfirm: () => void;
onCancel: () => void;
title: string;
message: string;
}
interface DataTableProps {
data: any[];
columns: Column[];
actions?: Action[];
emptyMessage?: string;
deleteModal?: DeleteModalProps;
onItemClick?: (item: any) => void;
}
const DataTable: React.FC<DataTableProps> = ({
data,
columns,
actions,
emptyMessage = "No data available",
deleteModal,
onItemClick,
}) => {
const renderCell = (column: Column, row: any) => {
const value = row[column.accessor];
if (column.cellRenderer) {
return column.cellRenderer(value, row);
}
// Default cell rendering based on value type
if (Array.isArray(value)) {
return (
<div style={{ display: "flex", flexDirection: "column" }}>
{value.length === 0 ? (
<Badge size="xs" className="mb-1" color="red">
<Text>None</Text>
</Badge>
) : (
value.map((item: any, index: number) => (
<Badge key={index} size="xs" className="mb-1" color="blue">
<Text>{String(item).length > 30 ? `${String(item).slice(0, 30)}...` : item}</Text>
</Badge>
))
)}
</div>
);
}
return value?.toString() || "";
};
return (
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[40vh]">
<Table>
<TableHead>
<TableRow>
{columns.map((column, index) => (
<TableHeaderCell key={index}>{column.header}</TableHeaderCell>
))}
{actions && actions.length > 0 && <TableHeaderCell>Actions</TableHeaderCell>}
</TableRow>
</TableHead>
<TableBody>
{data && data.length > 0 ? (
data.map((row, rowIndex) => (
<TableRow key={rowIndex}>
{columns.map((column, colIndex) => (
<TableCell
key={colIndex}
style={{
maxWidth: column.width || "4px",
whiteSpace: "pre-wrap",
overflow: "hidden",
...column.style,
}}
>
{column.accessor === "id" ? (
<Tooltip title={row[column.accessor]}>{renderCell(column, row)}</Tooltip>
) : (
renderCell(column, row)
)}
</TableCell>
))}
{actions && actions.length > 0 && (
<TableCell>
{actions.map(
(action, actionIndex) =>
// @ts-ignore
action.condition?.(row) !== false && (
<Tooltip key={actionIndex} title={action.tooltip}>
<Icon
// @ts-ignore
icon={action.icon}
size="sm"
onClick={() => action.onClick(row)}
className="cursor-pointer mx-1"
/>
</Tooltip>
),
)}
</TableCell>
)}
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={columns.length + (actions ? 1 : 0)}>
<Text className="text-center">{emptyMessage}</Text>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</Card>
);
};
export default DataTable;
export type { Action, Column, DataTableProps, DeleteModalProps };

View File

@ -1,6 +0,0 @@
import { Organization } from "../networking";
export const defaultOrg = {
organization_id: "default_organization",
organization_alias: "Default Organization",
} as Organization;

View File

@ -1,59 +0,0 @@
import React from "react";
import { Select, TextInput } from "@tremor/react";
import { Form, Select as AntSelect } from "antd";
import TeamDropdown from "./team_dropdown";
import { getPossibleUserRoles } from "../networking";
import TextArea from "antd/es/input/TextArea";
interface UserFormProps {
form: any;
teams: any[] | null;
possibleUIRoles: null | Record<string, Record<string, string>>;
setPossibleUIRoles?: (roles: any) => void;
accessToken?: string;
}
const UserForm: React.FC<UserFormProps> = ({ form, teams, possibleUIRoles, setPossibleUIRoles, accessToken }) => {
React.useEffect(() => {
// Fetch roles if they're not available and we have a setter
if (!possibleUIRoles && setPossibleUIRoles && accessToken) {
getPossibleUserRoles(accessToken).then((roles) => {
setPossibleUIRoles(roles);
});
}
}, [possibleUIRoles, setPossibleUIRoles, accessToken]);
return (
<>
<Form.Item label="User Email" name="user_email" rules={[{ required: true, message: "Please input user email" }]}>
<TextInput placeholder="" />
</Form.Item>
<Form.Item label="User Role" name="user_role" rules={[{ required: true, message: "Please select a role" }]}>
<Select>
{possibleUIRoles &&
Object.entries(possibleUIRoles).map(([role, { ui_label, description }]) => (
<AntSelect.Option key={role} value={role} title={ui_label}>
<div className="flex">
{ui_label}{" "}
<p className="ml-2" style={{ color: "gray", fontSize: "12px" }}>
{description}
</p>
</div>
</AntSelect.Option>
))}
</Select>
</Form.Item>
<Form.Item label="Team" name="team_id">
<TeamDropdown teams={teams} />
</Form.Item>
<Form.Item label="Metadata" name="metadata">
<TextArea rows={4} placeholder="Enter metadata as JSON" />
</Form.Item>
</>
);
};
export default UserForm;

View File

@ -1,55 +0,0 @@
"use client";
import React, { useState } from "react";
import { Grid, Col, Icon } from "@tremor/react";
import { Title } from "@tremor/react";
import { Modal } from "antd";
import { modelDeleteCall } from "./networking";
import { TrashIcon } from "@heroicons/react/outline";
import NotificationsManager from "./molecules/notifications_manager";
interface DeleteModelProps {
modelID: string;
accessToken: string;
callback?: () => void;
}
const DeleteModelButton: React.FC<DeleteModelProps> = ({ modelID, accessToken, callback }) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const handleDelete = async () => {
try {
NotificationsManager.info("Making API Call");
setIsModalVisible(true);
const response = await modelDeleteCall(accessToken, modelID);
console.log("model delete Response:", response);
NotificationsManager.success(`Model ${modelID} deleted successfully`);
setIsModalVisible(false);
callback && setTimeout(callback, 4000); //added timeout of 4 seconds as deleted model is taking time to reflect in get models
} catch (error) {
console.error("Error deleting the model:", error);
}
};
return (
<div>
<Icon onClick={() => setIsModalVisible(true)} icon={TrashIcon} size="sm" />
<Modal open={isModalVisible} onOk={handleDelete} okType="danger" onCancel={() => setIsModalVisible(false)}>
<Grid numItems={1} className="gap-2 w-full">
<Title>Delete Model</Title>
<Col numColSpan={1}>
<p>Are you sure you want to delete this model? This action is irreversible.</p>
</Col>
<Col numColSpan={1}>
<p>
Model ID: <b>{modelID}</b>
</p>
</Col>
</Grid>
</Modal>
</div>
);
};
export default DeleteModelButton;

View File

@ -1,214 +0,0 @@
import React from "react";
import { Modal, Form, InputNumber } from "antd";
import { TextInput } from "@tremor/react";
import { Button as Button2 } from "antd";
import { modelUpdateCall } from "../networking";
import NotificationsManager from "../molecules/notifications_manager";
interface EditModelModalProps {
visible: boolean;
onCancel: () => void;
model: any;
onSubmit: (data: FormData) => void;
}
export const handleEditModelSubmit = async (
formValues: Record<string, any>,
accessToken: string | null,
setEditModalVisible: (visible: boolean) => void,
setSelectedModel: (model: any) => void,
) => {
// Call API to update team with teamId and values
console.log("handleEditSubmit:", formValues);
if (accessToken == null) {
return;
}
let newLiteLLMParams: Record<string, any> = {};
let model_info_model_id = null;
if (formValues.input_cost_per_token) {
// Convert from per 1M tokens to per token
formValues.input_cost_per_token = Number(formValues.input_cost_per_token) / 1_000_000;
}
if (formValues.output_cost_per_token) {
// Convert from per 1M tokens to per token
formValues.output_cost_per_token = Number(formValues.output_cost_per_token) / 1_000_000;
}
for (const [key, value] of Object.entries(formValues)) {
if (key !== "model_id") {
// Empty string means user wants to null the value
newLiteLLMParams[key] = value === "" ? null : value;
} else {
model_info_model_id = value === "" ? null : value;
}
}
let payload: {
litellm_params: Record<string, any> | undefined;
model_info: { id: any } | undefined;
} = {
litellm_params: Object.keys(newLiteLLMParams).length > 0 ? newLiteLLMParams : undefined,
model_info:
model_info_model_id !== undefined
? {
id: model_info_model_id,
}
: undefined,
};
console.log("handleEditSubmit payload:", payload);
try {
let newModelValue = await modelUpdateCall(accessToken, payload);
NotificationsManager.success("Model updated successfully, restart server to see updates");
setEditModalVisible(false);
setSelectedModel(null);
} catch (error) {
console.log(`Error occurred`);
}
};
const EditModelModal: React.FC<EditModelModalProps> = ({ visible, onCancel, model, onSubmit }) => {
const [form] = Form.useForm();
let litellm_params_to_edit: Record<string, any> = {};
let model_name = "";
let model_id = "";
if (model) {
litellm_params_to_edit = {
...model.litellm_params,
input_cost_per_token: model.litellm_params?.input_cost_per_token
? model.litellm_params.input_cost_per_token * 1_000_000
: undefined,
output_cost_per_token: model.litellm_params?.output_cost_per_token
? model.litellm_params.output_cost_per_token * 1_000_000
: undefined,
};
model_name = model.model_name;
let model_info = model.model_info;
if (model_info) {
model_id = model_info.id;
console.log(`model_id: ${model_id}`);
litellm_params_to_edit.model_id = model_id;
}
}
const handleOk = () => {
form
.validateFields()
.then((values) => {
const submissionValues = {
...values,
input_cost_per_token: values.input_cost_per_token
? Number(values.input_cost_per_token) / 1_000_000
: undefined,
output_cost_per_token: values.output_cost_per_token
? Number(values.output_cost_per_token) / 1_000_000
: undefined,
};
onSubmit(submissionValues);
form.resetFields();
})
.catch((error) => {
console.error("Validation failed:", error);
});
};
return (
<Modal
title={"Edit '" + model_name + "' LiteLLM Params"}
open={visible}
width={800}
footer={null}
onOk={handleOk}
onCancel={onCancel}
>
<Form
form={form}
onFinish={onSubmit}
initialValues={litellm_params_to_edit}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
>
<>
<Form.Item
label="Input Cost (per 1M tokens)"
name="input_cost_per_token"
tooltip="float (optional) - Input cost per 1 million tokens"
>
<TextInput />
</Form.Item>
<Form.Item
label="Output Cost (per 1M tokens)"
name="output_cost_per_token"
tooltip="float (optional) - Output cost per 1 million tokens"
>
<TextInput />
</Form.Item>
<Form.Item className="mt-8" label="api_base" name="api_base">
<TextInput />
</Form.Item>
<Form.Item className="mt-8" label="api_key" name="api_key">
<TextInput />
</Form.Item>
<Form.Item className="mt-8" label="custom_llm_provider" name="custom_llm_provider">
<TextInput />
</Form.Item>
<Form.Item className="mt-8" label="model" name="model">
<TextInput />
</Form.Item>
<Form.Item label="organization" name="organization" tooltip="OpenAI Organization ID">
<TextInput />
</Form.Item>
<Form.Item
label="tpm"
name="tpm"
tooltip="int (optional) - Tokens limit for this deployment: in tokens per minute (tpm). Find this information on your model/providers website"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="rpm"
name="rpm"
tooltip="int (optional) - Rate limit for this deployment: in requests per minute (rpm). Find this information on your model/providers website"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item label="max_retries" name="max_retries">
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="timeout"
name="timeout"
tooltip="int (optional) - Timeout in seconds for LLM requests (Defaults to 600 seconds)"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item
label="stream_timeout"
name="stream_timeout"
tooltip="int (optional) - Timeout for stream requests (seconds)"
>
<InputNumber min={0} step={1} />
</Form.Item>
<Form.Item label="model_id" name="model_id" hidden={true}></Form.Item>
</>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Save</Button2>
</div>
</Form>
</Modal>
);
};
export default EditModelModal;

View File

@ -1,23 +0,0 @@
import { Organization } from "../networking";
export const createOrgSearchFunction = (organizations: Organization[] | null) => {
return async (searchText: string): Promise<Array<{ label: string; value: string }>> => {
if (!organizations || !searchText.trim()) {
return [];
}
// Find organizations that match the search text by alias
const matchingOrgs: Array<{ label: string; value: string }> = [];
organizations.forEach((org) => {
if (org.organization_alias && org.organization_alias.toLowerCase().includes(searchText.toLowerCase())) {
matchingOrgs.push({
label: `${org.organization_alias} (${org.organization_id})`,
value: org.organization_id || "",
});
}
});
return matchingOrgs;
};
};

View File

@ -1,113 +0,0 @@
/**
* This component is used to add an admin to an organization.
*/
import React, { FC } from "react";
import { Button, Col, Text } from "@tremor/react";
import { Button as Button2, Select as Select2, Modal, Form, Input } from "antd";
import { Organization } from "@/components/organization/types";
interface AddOrgAdminProps {
userRole: string;
userID: string;
selectedOrganization?: Organization;
onMemberAdd?: (formValues: Record<string, any>) => void;
}
const is_org_admin = (organization: any, userID: string) => {
for (let i = 0; i < organization.members_with_roles.length; i++) {
let member = organization.members_with_roles[i];
if (member.user_id == userID && member.role == "admin") {
return true;
}
}
return false;
};
const AddOrgAdmin: FC<AddOrgAdminProps> = ({ userRole, userID, selectedOrganization, onMemberAdd }) => {
const [isAddMemberModalVisible, setIsAddMemberModalVisible] = React.useState(false);
const [form] = Form.useForm();
const handleMemberCancel = () => {
form.resetFields();
setIsAddMemberModalVisible(false);
};
const handleMemberOk = () => {
form.submit();
};
return (
<Col numColSpan={1}>
{userRole === "Admin" || (selectedOrganization && is_org_admin(selectedOrganization, userID)) ? (
<Button className="mx-auto mb-5" onClick={() => setIsAddMemberModalVisible(true)}>
+ Add member
</Button>
) : null}
<Modal
title="Add member"
open={isAddMemberModalVisible}
width={800}
footer={null}
onOk={handleMemberOk}
onCancel={handleMemberCancel}
>
<Text className="mb-2">User must exist in proxy. Get User ID from &apos;Users&apos; tab.</Text>
<Form
form={form}
onFinish={onMemberAdd}
labelCol={{ span: 8 }}
wrapperCol={{ span: 16 }}
labelAlign="left"
initialValues={{
role: "internal_user",
}}
>
<Form.Item label="Email" name="user_email" className="mb-4">
<Input name="user_email" className="px-3 py-2 border rounded-md w-full" />
</Form.Item>
<div className="text-center mb-4">OR</div>
<Form.Item label="User ID" name="user_id" className="mb-4">
<Input name="user_id" className="px-3 py-2 border rounded-md w-full" />
</Form.Item>
<Form.Item label="Member Role" name="role" className="mb-4">
<Select2 defaultValue="user">
<Select2.Option value="org_admin">
<div className="flex">
Org Admin{" "}
<p className="ml-2" style={{ color: "gray", fontSize: "12px" }}>
Can add and remove members, and change their roles.
</p>
</div>
</Select2.Option>
<Select2.Option value="internal_user">
<div className="flex">
Internal User{" "}
<p className="ml-2" style={{ color: "gray", fontSize: "12px" }}>
Can view/create keys for themselves within organization.
</p>
</div>
</Select2.Option>
<Select2.Option value="internal_user_viewer">
<div className="flex">
Internal User Viewer{" "}
<p className="ml-2" style={{ color: "gray", fontSize: "12px" }}>
Can only view their keys within organization.
</p>
</div>
</Select2.Option>
</Select2>
</Form.Item>
<div style={{ textAlign: "right", marginTop: "10px" }}>
<Button2 htmlType="submit">Add member</Button2>
</div>
</Form>
</Modal>
</Col>
);
};
export default AddOrgAdmin;

View File

@ -1,74 +0,0 @@
import React, { FC } from "react";
import { Organization, EditModalProps, OrganizationMember } from "./types";
import { Card, Col, Table, TableHead, TableHeaderCell, TableBody, TableRow, TableCell } from "@tremor/react";
interface Member {
user_email?: string;
user_id?: string;
role: string;
}
interface MemberListTableProps {
selectedEntity?: Organization;
onEditSubmit: (entity: Organization) => void;
editModalComponent: React.ComponentType<EditModalProps>;
entityType: "team" | "organization";
}
const MemberListTable: FC<MemberListTableProps> = ({
selectedEntity,
onEditSubmit,
editModalComponent: EditModal,
entityType,
}) => {
const [editModalVisible, setEditModalVisible] = React.useState(false);
const handleEditCancel = () => {
setEditModalVisible(false);
};
const handleEditSubmit = (entity: Organization) => {
onEditSubmit(entity);
setEditModalVisible(false);
};
const getMemberIdentifier = (member: Member) => {
return member.user_email || member.user_id || "Unknown Member";
};
return (
<Col numColSpan={1}>
<Card className="w-full mx-auto flex-auto overflow-y-auto max-h-[50vh]">
<Table>
<TableHead>
<TableRow>
<TableHeaderCell>{entityType === "team" ? "Team Member" : "Organization Member"}</TableHeaderCell>
<TableHeaderCell>Role</TableHeaderCell>
</TableRow>
</TableHead>
<TableBody>
{(selectedEntity?.members ?? []).map((value: OrganizationMember, index: number) => (
<TableRow key={`${value.user_id}-${index}`}>
<TableCell>{value.user_id}</TableCell>
<TableCell>{value.user_role}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</Card>
{selectedEntity && (
<EditModal
visible={editModalVisible}
onCancel={handleEditCancel}
entity={selectedEntity}
onSubmit={handleEditSubmit}
/>
)}
</Col>
);
};
export default MemberListTable;