diff --git a/app/.env.example b/app/.env.example index 4eaf96d..acdeb20 100644 --- a/app/.env.example +++ b/app/.env.example @@ -31,3 +31,6 @@ NEXT_PUBLIC_HOST="http://localhost:3000" # Next Auth Github Provider GITHUB_CLIENT_ID="your_client_id" GITHUB_CLIENT_SECRET="your_secret" + +OPENPIPE_BASE_URL="http://localhost:3000/api" +OPENPIPE_API_KEY="your_key" diff --git a/app/@types/nextjs-routes.d.ts b/app/@types/nextjs-routes.d.ts index 63efc55..6253ce2 100644 --- a/app/@types/nextjs-routes.d.ts +++ b/app/@types/nextjs-routes.d.ts @@ -12,8 +12,10 @@ declare module "nextjs-routes" { export type Route = | StaticRoute<"/account/signin"> + | DynamicRoute<"/api/[...trpc]", { "trpc": string[] }> | DynamicRoute<"/api/auth/[...nextauth]", { "nextauth": string[] }> | StaticRoute<"/api/experiments/og-image"> + | StaticRoute<"/api/openapi"> | StaticRoute<"/api/sentry-example-api"> | DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }> | DynamicRoute<"/data/[id]", { "id": string }> @@ -21,6 +23,8 @@ declare module "nextjs-routes" { | DynamicRoute<"/experiments/[id]", { "id": string }> | StaticRoute<"/experiments"> | StaticRoute<"/"> + | StaticRoute<"/logged-calls"> + | StaticRoute<"/project/settings"> | StaticRoute<"/sentry-example-page"> | StaticRoute<"/world-champs"> | StaticRoute<"/world-champs/signup">; diff --git a/app/Dockerfile b/app/Dockerfile index 044fe16..3ef59e3 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -23,6 +23,7 @@ ARG NEXT_PUBLIC_SOCKET_URL ARG NEXT_PUBLIC_HOST ARG NEXT_PUBLIC_SENTRY_DSN ARG SENTRY_AUTH_TOKEN +ARG NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS WORKDIR /app COPY --from=deps /app/node_modules ./node_modules diff --git a/app/openapitools.json b/app/openapitools.json new file mode 100644 index 0000000..cd53ff4 --- /dev/null +++ b/app/openapitools.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.6.0" + } +} diff --git a/app/package.json b/app/package.json index 1855f60..e7c5818 100644 --- a/app/package.json +++ b/app/package.json @@ -16,7 +16,7 @@ "postinstall": "prisma generate", "lint": "next lint", "start": "next start", - "codegen": "tsx src/codegen/export-openai-types.ts", + "codegen": "tsx src/server/scripts/client-codegen.ts", "seed": "tsx prisma/seed.ts", "check": "concurrently 'pnpm lint' 'pnpm tsc' 'pnpm prettier . --check'", "test": "pnpm vitest --no-threads" @@ -50,6 +50,7 @@ "chroma-js": "^2.4.2", "concurrently": "^8.2.0", "cors": "^2.8.5", + "crypto-random-string": "^5.0.0", "dayjs": "^1.11.8", "dedent": "^1.0.1", "dotenv": "^16.3.1", @@ -62,12 +63,16 @@ "json-schema-to-typescript": "^13.0.2", "json-stringify-pretty-compact": "^4.0.0", "jsonschema": "^1.4.1", + "kysely": "^0.26.1", "lodash-es": "^4.17.21", + "lucide-react": "^0.265.0", "next": "^13.4.2", "next-auth": "^4.22.1", "next-query-params": "^4.2.3", + "nextjs-cors": "^2.1.2", "nextjs-routes": "^2.0.1", "openai": "4.0.0-beta.7", + "pg": "^8.11.2", "pluralize": "^8.0.0", "posthog-js": "^1.75.3", "posthog-node": "^3.1.1", @@ -83,10 +88,12 @@ "react-syntax-highlighter": "^15.5.0", "react-textarea-autosize": "^8.5.0", "recast": "^0.23.3", + "recharts": "^2.7.2", "replicate": "^0.12.3", "socket.io": "^4.7.1", "socket.io-client": "^4.7.1", "superjson": "1.12.2", + "trpc-openapi": "^1.2.0", "tsx": "^3.12.7", "type-fest": "^4.0.0", "use-query-params": "^2.2.1", @@ -106,6 +113,7 @@ "@types/json-schema": "^7.0.12", "@types/lodash-es": "^4.17.8", "@types/node": "^18.16.0", + "@types/pg": "^8.10.2", "@types/pluralize": "^0.0.30", "@types/prismjs": "^1.26.0", "@types/react": "^18.2.6", diff --git a/app/pnpm-lock.yaml b/app/pnpm-lock.yaml index 91855f5..76ff991 100644 --- a/app/pnpm-lock.yaml +++ b/app/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.1' +lockfileVersion: '6.0' settings: autoInstallPeers: true @@ -89,6 +89,9 @@ dependencies: cors: specifier: ^2.8.5 version: 2.8.5 + crypto-random-string: + specifier: ^5.0.0 + version: 5.0.0 dayjs: specifier: ^1.11.8 version: 1.11.8 @@ -125,9 +128,15 @@ dependencies: jsonschema: specifier: ^1.4.1 version: 1.4.1 + kysely: + specifier: ^0.26.1 + version: 0.26.1 lodash-es: specifier: ^4.17.21 version: 4.17.21 + lucide-react: + specifier: ^0.265.0 + version: 0.265.0(react@18.2.0) next: specifier: ^13.4.2 version: 13.4.2(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) @@ -137,12 +146,18 @@ dependencies: next-query-params: specifier: ^4.2.3 version: 4.2.3(next@13.4.2)(react@18.2.0)(use-query-params@2.2.1) + nextjs-cors: + specifier: ^2.1.2 + version: 2.1.2(next@13.4.2) nextjs-routes: specifier: ^2.0.1 version: 2.0.1(next@13.4.2) openai: specifier: 4.0.0-beta.7 version: 4.0.0-beta.7 + pg: + specifier: ^8.11.2 + version: 8.11.2 pluralize: specifier: ^8.0.0 version: 8.0.0 @@ -188,6 +203,9 @@ dependencies: recast: specifier: ^0.23.3 version: 0.23.3 + recharts: + specifier: ^2.7.2 + version: 2.7.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) replicate: specifier: ^0.12.3 version: 0.12.3 @@ -200,6 +218,9 @@ dependencies: superjson: specifier: 1.12.2 version: 1.12.2 + trpc-openapi: + specifier: ^1.2.0 + version: 1.2.0(@trpc/server@10.26.0)(zod@3.21.4) tsx: specifier: ^3.12.7 version: 3.12.7 @@ -253,6 +274,9 @@ devDependencies: '@types/node': specifier: ^18.16.0 version: 18.16.0 + '@types/pg': + specifier: ^8.10.2 + version: 8.10.2 '@types/pluralize': specifier: ^0.0.30 version: 0.0.30 @@ -331,14 +355,13 @@ packages: '@types/node': 18.16.0 '@types/node-fetch': 2.6.4 abort-controller: 3.0.0 - agentkeepalive: 4.3.0 + agentkeepalive: 4.5.0 digest-fetch: 1.3.0 form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.6.12 transitivePeerDependencies: - encoding - - supports-color dev: false /@apidevtools/json-schema-ref-parser@10.1.0: @@ -409,7 +432,7 @@ packages: '@babel/compat-data': 7.22.9 '@babel/core': 7.22.9 '@babel/helper-validator-option': 7.22.5 - browserslist: 4.21.9 + browserslist: 4.21.10 lru-cache: 5.1.1 semver: 6.3.1 @@ -1988,8 +2011,8 @@ packages: dev: false optional: true - /@esbuild/android-arm64@0.18.14: - resolution: {integrity: sha512-rZ2v+Luba5/3D6l8kofWgTnqE+qsC/L5MleKIKFyllHTKHrNBMqeRCnZI1BtRx8B24xMYxeU32iIddRQqMsOsg==} + /@esbuild/android-arm64@0.18.18: + resolution: {integrity: sha512-dkAPYzRHq3dNXIzOyAknYOzsx8o3KWaNiuu56B2rP9IFPmFWMS58WQcTlUQi6iloku8ZyHHMluCe5sTWhKq/Yw==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -2006,8 +2029,8 @@ packages: dev: false optional: true - /@esbuild/android-arm@0.18.14: - resolution: {integrity: sha512-blODaaL+lngG5bdK/t4qZcQvq2BBqrABmYwqPPcS5VRxrCSGHb9R/rA3fqxh7R18I7WU4KKv+NYkt22FDfalcg==} + /@esbuild/android-arm@0.18.18: + resolution: {integrity: sha512-oBymf7ZwplAawSxmiSlBCf+FMcY0f4bs5QP2jn43JKUf0M9DnrUTjqa5RvFPl1elw+sMfcpfBRPK+rb+E1q7zg==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -2024,8 +2047,8 @@ packages: dev: false optional: true - /@esbuild/android-x64@0.18.14: - resolution: {integrity: sha512-qSwh8y38QKl+1Iqg+YhvCVYlSk3dVLk9N88VO71U4FUjtiSFylMWK3Ugr8GC6eTkkP4Tc83dVppt2n8vIdlSGg==} + /@esbuild/android-x64@0.18.18: + resolution: {integrity: sha512-r7/pVcrUQMYkjvtE/1/n6BxhWM+/9tvLxDG1ev1ce4z3YsqoxMK9bbOM6bFcj0BowMeGQvOZWcBV182lFFKmrw==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -2042,8 +2065,8 @@ packages: dev: false optional: true - /@esbuild/darwin-arm64@0.18.14: - resolution: {integrity: sha512-9Hl2D2PBeDYZiNbnRKRWuxwHa9v5ssWBBjisXFkVcSP5cZqzZRFBUWEQuqBHO4+PKx4q4wgHoWtfQ1S7rUqJ2Q==} + /@esbuild/darwin-arm64@0.18.18: + resolution: {integrity: sha512-MSe2iV9MAH3wfP0g+vzN9bp36rtPPuCSk+bT5E2vv/d8krvW5uB/Pi/Q5+txUZuxsG3GcO8dhygjnFq0wJU9hQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -2060,8 +2083,8 @@ packages: dev: false optional: true - /@esbuild/darwin-x64@0.18.14: - resolution: {integrity: sha512-ZnI3Dg4ElQ6tlv82qLc/UNHtFsgZSKZ7KjsUNAo1BF1SoYDjkGKHJyCrYyWjFecmXpvvG/KJ9A/oe0H12odPLQ==} + /@esbuild/darwin-x64@0.18.18: + resolution: {integrity: sha512-ARFYISOWkaifjcr48YtO70gcDNeOf1H2RnmOj6ip3xHIj66f3dAbhcd5Nph5np6oHI7DhHIcr9MWO18RvUL1bw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -2078,8 +2101,8 @@ packages: dev: false optional: true - /@esbuild/freebsd-arm64@0.18.14: - resolution: {integrity: sha512-h3OqR80Da4oQCIa37zl8tU5MwHQ7qgPV0oVScPfKJK21fSRZEhLE4IIVpmcOxfAVmqjU6NDxcxhYaM8aDIGRLw==} + /@esbuild/freebsd-arm64@0.18.18: + resolution: {integrity: sha512-BHnXmexzEWRU2ZySJosU0Ts0NRnJnNrMB6t4EiIaOSel73I8iLsNiTPLH0rJulAh19cYZutsB5XHK6N8fi5eMg==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -2096,8 +2119,8 @@ packages: dev: false optional: true - /@esbuild/freebsd-x64@0.18.14: - resolution: {integrity: sha512-ha4BX+S6CZG4BoH9tOZTrFIYC1DH13UTCRHzFc3GWX74nz3h/N6MPF3tuR3XlsNjMFUazGgm35MPW5tHkn2lzQ==} + /@esbuild/freebsd-x64@0.18.18: + resolution: {integrity: sha512-n823w35wm0ZOobbuE//0sJjuz1Qj619+AwjgOcAJMN2pomZhH9BONCtn+KlfrmM/NWZ+27yB/eGVFzUIWLeh3w==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -2114,8 +2137,8 @@ packages: dev: false optional: true - /@esbuild/linux-arm64@0.18.14: - resolution: {integrity: sha512-IXORRe22In7U65NZCzjwAUc03nn8SDIzWCnfzJ6t/8AvGx5zBkcLfknI+0P+hhuftufJBmIXxdSTbzWc8X/V4w==} + /@esbuild/linux-arm64@0.18.18: + resolution: {integrity: sha512-zANxnwF0sCinDcAqoMohGoWBK9QaFJ65Vgh0ZE+RXtURaMwx+RfmfLElqtnn7X8OYNckMoIXSg7u+tZ3tqTlrA==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -2132,8 +2155,8 @@ packages: dev: false optional: true - /@esbuild/linux-arm@0.18.14: - resolution: {integrity: sha512-5+7vehI1iqru5WRtJyU2XvTOvTGURw3OZxe3YTdE9muNNIdmKAVmSHpB3Vw2LazJk2ifEdIMt/wTWnVe5V98Kg==} + /@esbuild/linux-arm@0.18.18: + resolution: {integrity: sha512-Kck3jxPLQU4VeAGwe8Q4NU+IWIx+suULYOFUI9T0C2J1+UQlOHJ08ITN+MaJJ+2youzJOmKmcphH/t3SJxQ1Tw==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -2150,8 +2173,8 @@ packages: dev: false optional: true - /@esbuild/linux-ia32@0.18.14: - resolution: {integrity: sha512-BfHlMa0nibwpjG+VXbOoqJDmFde4UK2gnW351SQ2Zd4t1N3zNdmUEqRkw/srC1Sa1DRBE88Dbwg4JgWCbNz/FQ==} + /@esbuild/linux-ia32@0.18.18: + resolution: {integrity: sha512-+VHz2sIRlY5u8IlaLJpdf5TL2kM76yx186pW7bpTB+vLWpzcFQVP04L842ZB2Ty13A1VXUvy3DbU1jV65P2skg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -2168,8 +2191,8 @@ packages: dev: false optional: true - /@esbuild/linux-loong64@0.18.14: - resolution: {integrity: sha512-j2/Ex++DRUWIAaUDprXd3JevzGtZ4/d7VKz+AYDoHZ3HjJzCyYBub9CU1wwIXN+viOP0b4VR3RhGClsvyt/xSw==} + /@esbuild/linux-loong64@0.18.18: + resolution: {integrity: sha512-fXPEPdeGBvguo/1+Na8OIWz3667BN1cwbGtTEZWTd0qdyTsk5gGf9jVX8MblElbDb/Cpw6y5JiaQuL96YmvBwQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -2186,8 +2209,8 @@ packages: dev: false optional: true - /@esbuild/linux-mips64el@0.18.14: - resolution: {integrity: sha512-qn2+nc+ZCrJmiicoAnJXJJkZWt8Nwswgu1crY7N+PBR8ChBHh89XRxj38UU6Dkthl2yCVO9jWuafZ24muzDC/A==} + /@esbuild/linux-mips64el@0.18.18: + resolution: {integrity: sha512-dLvRB87pIBIRnEIC32LIcgwK1JzlIuADIRjLKdUIpxauKwMuS/xMpN+cFl+0nN4RHNYOZ57DmXFFmQAcdlFOmw==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -2204,8 +2227,8 @@ packages: dev: false optional: true - /@esbuild/linux-ppc64@0.18.14: - resolution: {integrity: sha512-aGzXzd+djqeEC5IRkDKt3kWzvXoXC6K6GyYKxd+wsFJ2VQYnOWE954qV2tvy5/aaNrmgPTb52cSCHFE+Z7Z0yg==} + /@esbuild/linux-ppc64@0.18.18: + resolution: {integrity: sha512-fRChqIJZ7hLkXSKfBLYgsX9Ssb5OGCjk3dzCETF5QSS1qjTgayLv0ALUdJDB9QOh/nbWwp+qfLZU6md4XcjL7w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -2222,8 +2245,8 @@ packages: dev: false optional: true - /@esbuild/linux-riscv64@0.18.14: - resolution: {integrity: sha512-8C6vWbfr0ygbAiMFLS6OPz0BHvApkT2gCboOGV76YrYw+sD/MQJzyITNsjZWDXJwPu9tjrFQOVG7zijRzBCnLw==} + /@esbuild/linux-riscv64@0.18.18: + resolution: {integrity: sha512-ALK/BT3u7Hoa/vHjow6W6+MKF0ohYcVcVA1EpskI4bkBPVuDLrUDqt2YFifg5UcZc8qup0CwQqWmFUd6VMNgaA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -2240,8 +2263,8 @@ packages: dev: false optional: true - /@esbuild/linux-s390x@0.18.14: - resolution: {integrity: sha512-G/Lf9iu8sRMM60OVGOh94ZW2nIStksEcITkXdkD09/T6QFD/o+g0+9WVyR/jajIb3A0LvBJ670tBnGe1GgXMgw==} + /@esbuild/linux-s390x@0.18.18: + resolution: {integrity: sha512-crT7jtOXd9iirY65B+mJQ6W0HWdNy8dtkZqKGWNcBnunpLcTCfne5y5bKic9bhyYzKpQEsO+C/VBPD8iF0RhRw==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -2258,8 +2281,8 @@ packages: dev: false optional: true - /@esbuild/linux-x64@0.18.14: - resolution: {integrity: sha512-TBgStYBQaa3EGhgqIDM+ECnkreb0wkcKqL7H6m+XPcGUoU4dO7dqewfbm0mWEQYH3kzFHrzjOFNpSAVzDZRSJw==} + /@esbuild/linux-x64@0.18.18: + resolution: {integrity: sha512-/NSgghjBOW9ELqjXDYxOCCIsvQUZpvua1/6NdnA9Vnrp9UzEydyDdFXljUjMMS9p5KxMzbMO9frjHYGVHBfCHg==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -2276,8 +2299,8 @@ packages: dev: false optional: true - /@esbuild/netbsd-x64@0.18.14: - resolution: {integrity: sha512-stvCcjyCQR2lMTroqNhAbvROqRjxPEq0oQ380YdXxA81TaRJEucH/PzJ/qsEtsHgXlWFW6Ryr/X15vxQiyRXVg==} + /@esbuild/netbsd-x64@0.18.18: + resolution: {integrity: sha512-8Otf05Vx5sZjLLDulgr5QS5lsWXMplKZEyHMArH9/S4olLlhzmdhQBPhzhJTNwaL2FJNdWcUPNGAcoD5zDTfUA==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -2294,8 +2317,8 @@ packages: dev: false optional: true - /@esbuild/openbsd-x64@0.18.14: - resolution: {integrity: sha512-apAOJF14CIsN5ht1PA57PboEMsNV70j3FUdxLmA2liZ20gEQnfTG5QU0FhENo5nwbTqCB2O3WDsXAihfODjHYw==} + /@esbuild/openbsd-x64@0.18.18: + resolution: {integrity: sha512-tFiFF4kT5L5qhVrWJUNxEXWvvX8nK/UX9ZrB7apuTwY3f6+Xy4aFMBPwAVrBYtBd5MOUuyOVHK6HBZCAHkwUlw==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -2312,8 +2335,8 @@ packages: dev: false optional: true - /@esbuild/sunos-x64@0.18.14: - resolution: {integrity: sha512-fYRaaS8mDgZcGybPn2MQbn1ZNZx+UXFSUoS5Hd2oEnlsyUcr/l3c6RnXf1bLDRKKdLRSabTmyCy7VLQ7VhGdOQ==} + /@esbuild/sunos-x64@0.18.18: + resolution: {integrity: sha512-MPogVV8Bzh8os4OM+YDGGsSzCzmNRiyKGtHoJyZLtI4BMmd6EcxmGlcEGK1uM46h1BiOyi7Z7teUtzzQhvkC+w==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -2330,8 +2353,8 @@ packages: dev: false optional: true - /@esbuild/win32-arm64@0.18.14: - resolution: {integrity: sha512-1c44RcxKEJPrVj62XdmYhxXaU/V7auELCmnD+Ri+UCt+AGxTvzxl9uauQhrFso8gj6ZV1DaORV0sT9XSHOAk8Q==} + /@esbuild/win32-arm64@0.18.18: + resolution: {integrity: sha512-YKD6LF/XXY9REu+ZL5RAsusiG48n602qxsMVh/E8FFD9hp4OyTQaL9fpE1ovxwQXqFio+tT0ITUGjDSSSPN13w==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -2348,8 +2371,8 @@ packages: dev: false optional: true - /@esbuild/win32-ia32@0.18.14: - resolution: {integrity: sha512-EXAFttrdAxZkFQmpvcAQ2bywlWUsONp/9c2lcfvPUhu8vXBBenCXpoq9YkUvVP639ld3YGiYx0YUQ6/VQz3Maw==} + /@esbuild/win32-ia32@0.18.18: + resolution: {integrity: sha512-NjSBmBsyZBTsZB6ga6rA6PfG/RHnwruUz/9YEVXcm4STGauFWvhYhOMhEyw1yU5NVgYYm8CH5AltCm77TS21/Q==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -2366,8 +2389,8 @@ packages: dev: false optional: true - /@esbuild/win32-x64@0.18.14: - resolution: {integrity: sha512-K0QjGbcskx+gY+qp3v4/940qg8JitpXbdxFhRDA1aYoNaPff88+aEwoq45aqJ+ogpxQxmU0ZTjgnrQD/w8iiUg==} + /@esbuild/win32-x64@0.18.18: + resolution: {integrity: sha512-eTSg/gC3p3tdjj4roDhe5xu94l1s2jMazP8u2FsYO8SEKvSpPOO71EucprDn/IuErDPvTFUhV9lTw5z5WJCRKQ==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -2382,16 +2405,16 @@ packages: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: eslint: 8.40.0 - eslint-visitor-keys: 3.4.1 + eslint-visitor-keys: 3.4.2 dev: true - /@eslint-community/regexpp@4.5.1: - resolution: {integrity: sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==} + /@eslint-community/regexpp@4.6.2: + resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.1.0: - resolution: {integrity: sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==} + /@eslint/eslintrc@2.1.1: + resolution: {integrity: sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 @@ -2412,14 +2435,21 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@floating-ui/core@1.3.1: - resolution: {integrity: sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==} + /@floating-ui/core@1.4.1: + resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==} + dependencies: + '@floating-ui/utils': 0.1.1 dev: false - /@floating-ui/dom@1.4.5: - resolution: {integrity: sha512-96KnRWkRnuBSSFbj0sFGwwOUd8EkiecINVl0O9wiZlZ64EkpyAOG3Xc2vKKNJmru0Z7RqWNymA+6b8OZqjgyyw==} + /@floating-ui/dom@1.5.1: + resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==} dependencies: - '@floating-ui/core': 1.3.1 + '@floating-ui/core': 1.4.1 + '@floating-ui/utils': 0.1.1 + dev: false + + /@floating-ui/utils@0.1.1: + resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==} dev: false /@fontsource/inconsolata@5.0.5: @@ -2630,7 +2660,7 @@ packages: resolution: {integrity: sha512-E6s9hfQx125CfGXW5896s0ZtUEecTS69KvqkNDPxKomeZ/Y2rNsG90yO8K47uchXqKw5RhD/rCNcOJ2VODfQiw==} dependencies: '@types/json-schema': 7.0.12 - '@types/node': 20.4.2 + '@types/node': 20.4.8 fast-deep-equal: 3.1.3 openapi-typescript: 5.4.1 dev: true @@ -2644,11 +2674,11 @@ packages: engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} dependencies: cross-spawn: 7.0.3 - fast-glob: 3.3.0 + fast-glob: 3.3.1 is-glob: 4.0.3 open: 9.1.0 picocolors: 1.0.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: true /@popperjs/core@2.11.8: @@ -2726,7 +2756,7 @@ packages: '@sentry/core': 7.61.0 '@sentry/types': 7.61.0 '@sentry/utils': 7.61.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /@sentry/browser@7.61.0: @@ -2738,7 +2768,7 @@ packages: '@sentry/replay': 7.61.0 '@sentry/types': 7.61.0 '@sentry/utils': 7.61.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /@sentry/cli@1.75.2: @@ -2764,7 +2794,7 @@ packages: dependencies: '@sentry/types': 7.61.0 '@sentry/utils': 7.61.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /@sentry/integrations@7.61.0: @@ -2774,7 +2804,7 @@ packages: '@sentry/types': 7.61.0 '@sentry/utils': 7.61.0 localforage: 1.10.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /@sentry/nextjs@7.61.0(next@13.4.2)(react@18.2.0)(webpack@5.88.2): @@ -2801,7 +2831,7 @@ packages: react: 18.2.0 rollup: 2.78.0 stacktrace-parser: 0.1.10 - tslib: 2.6.0 + tslib: 2.6.1 webpack: 5.88.2 transitivePeerDependencies: - encoding @@ -2819,7 +2849,7 @@ packages: cookie: 0.4.2 https-proxy-agent: 5.0.1 lru_map: 0.3.3 - tslib: 2.6.0 + tslib: 2.6.1 transitivePeerDependencies: - supports-color dev: false @@ -2835,7 +2865,7 @@ packages: '@sentry/utils': 7.61.0 hoist-non-react-statics: 3.3.2 react: 18.2.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /@sentry/replay@7.61.0: @@ -2857,7 +2887,7 @@ packages: engines: {node: '>=8'} dependencies: '@sentry/types': 7.61.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /@sentry/webpack-plugin@1.20.0: @@ -2891,7 +2921,7 @@ packages: /@swc/helpers@0.5.1: resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==} dependencies: - tslib: 2.6.0 + tslib: 2.6.1 dev: false /@t3-oss/env-core@0.3.1(typescript@5.0.4)(zod@3.21.4): @@ -3065,7 +3095,7 @@ packages: /@types/connect@3.4.35: resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==} dependencies: - '@types/node': 18.16.0 + '@types/node': 18.17.3 dev: true /@types/cookie@0.4.1: @@ -3077,6 +3107,48 @@ packages: dependencies: '@types/node': 18.16.0 + /@types/d3-array@3.0.5: + resolution: {integrity: sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A==} + dev: false + + /@types/d3-color@3.1.0: + resolution: {integrity: sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==} + dev: false + + /@types/d3-ease@3.0.0: + resolution: {integrity: sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==} + dev: false + + /@types/d3-interpolate@3.0.1: + resolution: {integrity: sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==} + dependencies: + '@types/d3-color': 3.1.0 + dev: false + + /@types/d3-path@3.0.0: + resolution: {integrity: sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==} + dev: false + + /@types/d3-scale@4.0.3: + resolution: {integrity: sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==} + dependencies: + '@types/d3-time': 3.0.0 + dev: false + + /@types/d3-shape@3.1.1: + resolution: {integrity: sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==} + dependencies: + '@types/d3-path': 3.0.0 + dev: false + + /@types/d3-time@3.0.0: + resolution: {integrity: sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==} + dev: false + + /@types/d3-timer@3.0.0: + resolution: {integrity: sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==} + dev: false + /@types/debug@4.1.8: resolution: {integrity: sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==} dependencies: @@ -3086,7 +3158,7 @@ packages: /@types/eslint-scope@3.7.4: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: - '@types/eslint': 8.44.1 + '@types/eslint': 8.44.2 '@types/estree': 1.0.1 /@types/eslint@8.37.0: @@ -3096,8 +3168,8 @@ packages: '@types/json-schema': 7.0.12 dev: true - /@types/eslint@8.44.1: - resolution: {integrity: sha512-XpNDc4Z5Tb4x+SW1MriMVeIsMoONHCkWFMkR/aPJbzEsxqHy+4Glu/BqTdPrApfDeMaXbtNh6bseNgl5KaWrSg==} + /@types/eslint@8.44.2: + resolution: {integrity: sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==} dependencies: '@types/estree': 1.0.1 '@types/json-schema': 7.0.12 @@ -3127,7 +3199,7 @@ packages: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: '@types/minimatch': 5.1.2 - '@types/node': 18.16.0 + '@types/node': 18.17.3 dev: false /@types/hast@2.3.5: @@ -3150,23 +3222,23 @@ packages: /@types/lodash-es@4.17.8: resolution: {integrity: sha512-euY3XQcZmIzSy7YH5+Unb3b2X12Wtk54YWINBvvGQ5SmMvwb11JQskGsfkH/5HXK77Kr8GF0wkVDIxzAisWtog==} dependencies: - '@types/lodash': 4.14.195 + '@types/lodash': 4.14.196 dev: true /@types/lodash.clonedeep@4.5.7: resolution: {integrity: sha512-ccNqkPptFIXrpVqUECi60/DFxjNKsfoQxSQsgcBJCX/fuX1wgyQieojkcWH/KpE3xzLoWN/2k+ZeGqIN3paSvw==} dependencies: - '@types/lodash': 4.14.195 + '@types/lodash': 4.14.196 dev: false /@types/lodash.mergewith@4.6.7: resolution: {integrity: sha512-3m+lkO5CLRRYU0fhGRp7zbsGi6+BZj0uTVSwvcKU+nSlhjA9/QRNfuSGnD2mX6hQA7ZbmcCkzk5h4ZYGOtk14A==} dependencies: - '@types/lodash': 4.14.195 + '@types/lodash': 4.14.196 dev: false - /@types/lodash@4.14.195: - resolution: {integrity: sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==} + /@types/lodash@4.14.196: + resolution: {integrity: sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==} /@types/mime@1.3.2: resolution: {integrity: sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==} @@ -3187,18 +3259,18 @@ packages: /@types/node-fetch@2.6.4: resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} dependencies: - '@types/node': 18.16.0 + '@types/node': 18.17.3 form-data: 3.0.1 dev: false /@types/node@18.16.0: resolution: {integrity: sha512-BsAaKhB+7X+H4GnSjGhJG9Qi8Tw+inU9nJDwmD5CgOmBLEI6ArdhikpLX7DjbjDRDTbqZzU2LSQNZg8WGPiSZQ==} - /@types/node@18.17.1: - resolution: {integrity: sha512-xlR1jahfizdplZYRU59JlUx9uzF1ARa8jbhM11ccpCJya8kvos5jwdm2ZAgxSCwOl0fq21svP18EVwPBXMQudw==} + /@types/node@18.17.3: + resolution: {integrity: sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==} - /@types/node@20.4.2: - resolution: {integrity: sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==} + /@types/node@20.4.8: + resolution: {integrity: sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==} dev: true /@types/parse-json@4.0.0: @@ -3208,10 +3280,9 @@ packages: /@types/pg@8.10.2: resolution: {integrity: sha512-MKFs9P6nJ+LAeHLU3V0cODEOgyThJ3OAnmOlsZsxux6sfQs3HRXR5bBn7xG5DjckEFhTAxsXi7k7cd0pCMxpJw==} dependencies: - '@types/node': 18.16.0 + '@types/node': 18.17.3 pg-protocol: 1.6.0 pg-types: 4.0.1 - dev: false /@types/pluralize@0.0.30: resolution: {integrity: sha512-kVww6xZrW/db5BR9OqiT71J9huRdQ+z/r+LbDuT7/EK50mCmj5FoaIARnVv0rvjUS/YpDox0cDU9lpQT011VBA==} @@ -3272,7 +3343,7 @@ packages: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: '@types/mime': 1.3.2 - '@types/node': 18.16.0 + '@types/node': 18.17.3 dev: true /@types/serve-static@1.15.2: @@ -3302,7 +3373,7 @@ packages: typescript: optional: true dependencies: - '@eslint-community/regexpp': 4.5.1 + '@eslint-community/regexpp': 4.6.2 '@typescript-eslint/parser': 5.59.6(eslint@8.40.0)(typescript@5.0.4) '@typescript-eslint/scope-manager': 5.59.6 '@typescript-eslint/type-utils': 5.59.6(eslint@8.40.0)(typescript@5.0.4) @@ -3418,7 +3489,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: '@typescript-eslint/types': 5.59.6 - eslint-visitor-keys: 3.4.1 + eslint-visitor-keys: 3.4.2 dev: true /@vercel/og@0.5.9: @@ -3449,9 +3520,9 @@ packages: /@vitest/snapshot@0.33.0: resolution: {integrity: sha512-tJjrl//qAHbyHajpFvr8Wsk8DIOODEebTu7pgBrP07iOepR5jYkLFiqLq2Ltxv+r0uptUb4izv1J8XBOwKkVYA==} dependencies: - magic-string: 0.30.1 + magic-string: 0.30.2 pathe: 1.1.1 - pretty-format: 29.6.1 + pretty-format: 29.6.2 dev: true /@vitest/spy@0.33.0: @@ -3465,7 +3536,7 @@ packages: dependencies: diff-sequences: 29.4.3 loupe: 2.3.6 - pretty-format: 29.6.1 + pretty-format: 29.6.2 dev: true /@webassemblyjs/ast@1.11.6: @@ -3622,15 +3693,11 @@ packages: - supports-color dev: false - /agentkeepalive@4.3.0: - resolution: {integrity: sha512-7Epl1Blf4Sy37j4v9f9FjICCh4+KAQOyXgHEwlyBiAQLbhKdq/i2QQU3amQalS/wPhdPzDXPL5DMR5bkn+YeWg==} + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} engines: {node: '>= 8.0.0'} dependencies: - debug: 4.3.4 - depd: 2.0.0 humanize-ms: 1.2.1 - transitivePeerDependencies: - - supports-color dev: false /ajv-keywords@3.5.2(ajv@6.12.6): @@ -3693,7 +3760,7 @@ packages: resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==} engines: {node: '>=10'} dependencies: - tslib: 2.6.0 + tslib: 2.6.1 dev: false /aria-query@5.3.0: @@ -3729,6 +3796,17 @@ packages: engines: {node: '>=8'} dev: true + /array.prototype.findlastindex@1.2.2: + resolution: {integrity: sha512-tb5thFFlUcp7NdNF6/MpDk/1r/4awWG1FIz3YqDf+/zJSTezBb+/5WViH41obXULHVpDzoiCLpJ/ZO9YbJMsdw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + es-shim-unscopables: 1.0.0 + get-intrinsic: 1.2.1 + dev: true + /array.prototype.flat@1.3.1: resolution: {integrity: sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==} engines: {node: '>= 0.4'} @@ -3792,14 +3870,14 @@ packages: resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==} engines: {node: '>=4'} dependencies: - tslib: 2.6.0 + tslib: 2.6.1 dev: false /ast-types@0.16.1: resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} engines: {node: '>=4'} dependencies: - tslib: 2.6.0 + tslib: 2.6.1 dev: false /asynckit@0.4.0: @@ -3850,7 +3928,7 @@ packages: dependencies: '@babel/runtime': 7.22.6 cosmiconfig: 6.0.0 - resolve: 1.22.2 + resolve: 1.22.4 dev: false /babel-plugin-macros@3.1.0: @@ -3859,7 +3937,7 @@ packages: dependencies: '@babel/runtime': 7.22.6 cosmiconfig: 7.1.0 - resolve: 1.22.2 + resolve: 1.22.4 dev: false /babel-plugin-syntax-jsx@6.18.0: @@ -3956,20 +4034,10 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001519 - electron-to-chromium: 1.4.482 + electron-to-chromium: 1.4.485 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) - /browserslist@4.21.9: - resolution: {integrity: sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001517 - electron-to-chromium: 1.4.465 - node-releases: 2.0.13 - update-browserslist-db: 1.0.11(browserslist@4.21.9) - /buffer-from@0.1.2: resolution: {integrity: sha512-RiWIenusJsmI2KcvqQABB83tLxCByE3upSP8QU3rJDMVFGPWLvPQJt/O1Su9moRWeH7d+Q2HYb68f6+v+tw2vg==} dev: false @@ -4023,9 +4091,6 @@ packages: resolution: {integrity: sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==} dev: false - /caniuse-lite@1.0.30001517: - resolution: {integrity: sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==} - /caniuse-lite@1.0.30001519: resolution: {integrity: sha512-0QHgqR+Jv4bxHMp8kZ1Kn8CH55OikjKJ6JmKkZYP1F3D7w+lnFXF70nG5eNfsZS89jadi5Ywy5UCSKLAglIRkg==} @@ -4144,6 +4209,15 @@ packages: wrap-ansi: 7.0.0 dev: false + /co-body@6.1.0: + resolution: {integrity: sha512-m7pOT6CdLN7FuXUcpuz/8lfQ/L77x8SchHCF4G0RBTJO20Wzmhn5Sp4/5WsKy8OSpifBSUrmg83qEqaDHdyFuQ==} + dependencies: + inflation: 2.0.0 + qs: 6.11.2 + raw-body: 2.5.2 + type-is: 1.6.18 + dev: false + /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -4235,6 +4309,10 @@ packages: /convert-source-map@1.9.0: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + /cookie-es@1.0.0: + resolution: {integrity: sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==} + dev: false + /cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: false @@ -4318,6 +4396,13 @@ packages: resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} dev: false + /crypto-random-string@5.0.0: + resolution: {integrity: sha512-KWjTXWwxFd6a94m5CdRGW/t82Tr8DoBc9dNnPCAbFI1EBweN6v1tv8y4Y1m7ndkp/nkIBRxUxAzpaBnR2k3bcQ==} + engines: {node: '>=14.16'} + dependencies: + type-fest: 2.19.0 + dev: false + /css-background-parser@0.1.0: resolution: {integrity: sha512-2EZLisiZQ+7m4wwur/qiYJRniHX4K5Tc9w93MT3AS0WS1u5kaZ4FKXlOTBhOjc+CgEgPiGY+fX1yWD8UwpEqUA==} dev: false @@ -4345,6 +4430,10 @@ packages: postcss-value-parser: 4.2.0 dev: false + /css-unit-converter@1.1.2: + resolution: {integrity: sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA==} + dev: false + /csstype@2.6.21: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} dev: false @@ -4356,6 +4445,77 @@ packages: resolution: {integrity: sha512-JiQosUWiOFgp4hQn0an+SBoV9IKdqzhROM0iiN4LB7UpfJBlsSJlWl9nq4zGgxgMAzHJ6V4t29VAVD+3+2NJAg==} dev: true + /d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + dependencies: + internmap: 2.0.3 + dev: false + + /d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + dev: false + + /d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + dev: false + + /d3-format@3.1.0: + resolution: {integrity: sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==} + engines: {node: '>=12'} + dev: false + + /d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + dependencies: + d3-color: 3.1.0 + dev: false + + /d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + dev: false + + /d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.0 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + dev: false + + /d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + dependencies: + d3-path: 3.1.0 + dev: false + + /d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + dependencies: + d3-time: 3.1.0 + dev: false + + /d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.4 + dev: false + + /d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + dev: false + /d@1.0.1: resolution: {integrity: sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==} dependencies: @@ -4411,6 +4571,10 @@ packages: dependencies: ms: 2.1.2 + /decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + dev: false + /decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} dev: false @@ -4446,7 +4610,7 @@ packages: dependencies: bundle-name: 3.0.0 default-browser-id: 3.0.0 - execa: 7.1.1 + execa: 7.2.0 titleize: 3.0.0 dev: true @@ -4462,11 +4626,20 @@ packages: has-property-descriptors: 1.0.0 object-keys: 1.1.1 + /defu@6.1.2: + resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} + dev: false + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} dev: false + /depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + dev: false + /depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} engines: {node: '>= 0.8'} @@ -4477,6 +4650,10 @@ packages: engines: {node: '>=6'} dev: true + /destr@2.0.1: + resolution: {integrity: sha512-M1Ob1zPSIvlARiJUkKqvAZ3VAqQY6Jcuth/pBKQ2b1dX/Qx0OnJ8Vux6J2H5PTMQeRzWrrbTu70VxBfv/OPDJA==} + dev: false + /destroy@1.2.0: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -4524,6 +4701,12 @@ packages: esutils: 2.0.3 dev: true + /dom-helpers@3.4.0: + resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==} + dependencies: + '@babel/runtime': 7.22.6 + dev: false + /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: @@ -4546,11 +4729,8 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false - /electron-to-chromium@1.4.465: - resolution: {integrity: sha512-XQcuHvEJRMU97UJ75e170mgcITZoz0lIyiaVjk6R+NMTJ8KBIvUHYd1779swgOppUlzxR+JsLpq59PumaXS1jQ==} - - /electron-to-chromium@1.4.482: - resolution: {integrity: sha512-h+UqpfmEr1Qkk0zp7ej/jid7CXoq4m4QzW6wNTb0ELJ/BZCpA4wgUylBIMGCe621tnr4l5VmoHjdoSx2lbnNJA==} + /electron-to-chromium@1.4.485: + resolution: {integrity: sha512-1ndQ5IBNEnFirPwvyud69GHL+31FkE09gH/CJ6m3KCbkx3i0EVOrjwz4UNxRmN9H8OVHbC6vMRZGN1yCvjSs9w==} /emoji-regex@10.2.1: resolution: {integrity: sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==} @@ -4581,12 +4761,12 @@ packages: engines: {node: '>= 0.8'} dev: false - /engine.io-client@6.5.1: - resolution: {integrity: sha512-hE5wKXH8Ru4L19MbM1GgYV/2Qo54JSMh1rlJbfpa40bEWkCKNo3ol2eOtGmowcr+ysgbI7+SGL+by42Q3pt/Ng==} + /engine.io-client@6.5.2: + resolution: {integrity: sha512-CQZqbrpEYnrpGqC07a9dJDz4gePZUgTPMU3NKJPSeQOyw27Tst4Pl3FemKoFGAlHzgZmKjoRmiJvbWfhCXUlIg==} dependencies: '@socket.io/component-emitter': 3.1.0 debug: 4.3.4 - engine.io-parser: 5.1.0 + engine.io-parser: 5.2.1 ws: 8.11.0 xmlhttprequest-ssl: 2.0.0 transitivePeerDependencies: @@ -4595,24 +4775,24 @@ packages: - utf-8-validate dev: false - /engine.io-parser@5.1.0: - resolution: {integrity: sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==} + /engine.io-parser@5.2.1: + resolution: {integrity: sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==} engines: {node: '>=10.0.0'} dev: false - /engine.io@6.5.1: - resolution: {integrity: sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==} - engines: {node: '>=10.0.0'} + /engine.io@6.5.2: + resolution: {integrity: sha512-IXsMcGpw/xRfjra46sVZVHiSWo/nJ/3g1337q9KNXtS6YRzbW5yIzTCb9DjhrBe7r3GZQR0I4+nq+4ODk5g/cA==} + engines: {node: '>=10.2.0'} dependencies: '@types/cookie': 0.4.1 '@types/cors': 2.8.13 - '@types/node': 18.16.0 + '@types/node': 18.17.3 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.4.2 cors: 2.8.5 debug: 4.3.4 - engine.io-parser: 5.1.0 + engine.io-parser: 5.2.1 ws: 8.11.0 transitivePeerDependencies: - bufferutil @@ -4773,34 +4953,34 @@ packages: '@esbuild/win32-x64': 0.17.19 dev: false - /esbuild@0.18.14: - resolution: {integrity: sha512-uNPj5oHPYmj+ZhSQeYQVFZ+hAlJZbAGOmmILWIqrGvPVlNLbyOvU5Bu6Woi8G8nskcx0vwY0iFoMPrzT86Ko+w==} + /esbuild@0.18.18: + resolution: {integrity: sha512-UckDPWvdVJLNT0npk5AMTpVwGRQhS76rWFLmHwEtgNvWlR9sgVV1eyc/oeBtM86q9s8ABBLMmm0CwNxhVemOiw==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.18.14 - '@esbuild/android-arm64': 0.18.14 - '@esbuild/android-x64': 0.18.14 - '@esbuild/darwin-arm64': 0.18.14 - '@esbuild/darwin-x64': 0.18.14 - '@esbuild/freebsd-arm64': 0.18.14 - '@esbuild/freebsd-x64': 0.18.14 - '@esbuild/linux-arm': 0.18.14 - '@esbuild/linux-arm64': 0.18.14 - '@esbuild/linux-ia32': 0.18.14 - '@esbuild/linux-loong64': 0.18.14 - '@esbuild/linux-mips64el': 0.18.14 - '@esbuild/linux-ppc64': 0.18.14 - '@esbuild/linux-riscv64': 0.18.14 - '@esbuild/linux-s390x': 0.18.14 - '@esbuild/linux-x64': 0.18.14 - '@esbuild/netbsd-x64': 0.18.14 - '@esbuild/openbsd-x64': 0.18.14 - '@esbuild/sunos-x64': 0.18.14 - '@esbuild/win32-arm64': 0.18.14 - '@esbuild/win32-ia32': 0.18.14 - '@esbuild/win32-x64': 0.18.14 + '@esbuild/android-arm': 0.18.18 + '@esbuild/android-arm64': 0.18.18 + '@esbuild/android-x64': 0.18.18 + '@esbuild/darwin-arm64': 0.18.18 + '@esbuild/darwin-x64': 0.18.18 + '@esbuild/freebsd-arm64': 0.18.18 + '@esbuild/freebsd-x64': 0.18.18 + '@esbuild/linux-arm': 0.18.18 + '@esbuild/linux-arm64': 0.18.18 + '@esbuild/linux-ia32': 0.18.18 + '@esbuild/linux-loong64': 0.18.18 + '@esbuild/linux-mips64el': 0.18.18 + '@esbuild/linux-ppc64': 0.18.18 + '@esbuild/linux-riscv64': 0.18.18 + '@esbuild/linux-s390x': 0.18.18 + '@esbuild/linux-x64': 0.18.18 + '@esbuild/netbsd-x64': 0.18.18 + '@esbuild/openbsd-x64': 0.18.18 + '@esbuild/sunos-x64': 0.18.18 + '@esbuild/win32-arm64': 0.18.18 + '@esbuild/win32-ia32': 0.18.18 + '@esbuild/win32-x64': 0.18.18 dev: true /escalade@3.1.1: @@ -4832,11 +5012,11 @@ packages: '@rushstack/eslint-patch': 1.3.2 '@typescript-eslint/parser': 5.59.6(eslint@8.40.0)(typescript@5.0.4) eslint: 8.40.0 - eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.40.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) + eslint-import-resolver-node: 0.3.8 + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.8)(eslint-plugin-import@2.28.0)(eslint@8.40.0) + eslint-plugin-import: 2.28.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) eslint-plugin-jsx-a11y: 6.7.1(eslint@8.40.0) - eslint-plugin-react: 7.32.2(eslint@8.40.0) + eslint-plugin-react: 7.33.1(eslint@8.40.0) eslint-plugin-react-hooks: 4.6.0(eslint@8.40.0) typescript: 5.0.4 transitivePeerDependencies: @@ -4844,17 +5024,17 @@ packages: - supports-color dev: true - /eslint-import-resolver-node@0.3.7: - resolution: {integrity: sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==} + /eslint-import-resolver-node@0.3.8: + resolution: {integrity: sha512-tEe+Pok22qIGaK3KoMP+N96GVDS66B/zreoVVmiavLvRUEmGRtvb4B8wO9jwnb8d2lvHtrkhZ7UD73dWBVnf/Q==} dependencies: debug: 3.2.7 - is-core-module: 2.12.1 - resolve: 1.22.2 + is-core-module: 2.13.0 + resolve: 1.22.4 transitivePeerDependencies: - supports-color dev: true - /eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.40.0): + /eslint-import-resolver-typescript@3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.8)(eslint-plugin-import@2.28.0)(eslint@8.40.0): resolution: {integrity: sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: @@ -4864,11 +5044,11 @@ packages: debug: 4.3.4 enhanced-resolve: 5.15.0 eslint: 8.40.0 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) + eslint-plugin-import: 2.28.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) get-tsconfig: 4.6.2 globby: 13.2.2 - is-core-module: 2.12.1 + is-core-module: 2.13.0 is-glob: 4.0.3 synckit: 0.8.5 transitivePeerDependencies: @@ -4878,7 +5058,7 @@ packages: - supports-color dev: true - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -4902,14 +5082,14 @@ packages: '@typescript-eslint/parser': 5.59.6(eslint@8.40.0)(typescript@5.0.4) debug: 3.2.7 eslint: 8.40.0 - eslint-import-resolver-node: 0.3.7 - eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-plugin-import@2.27.5)(eslint@8.40.0) + eslint-import-resolver-node: 0.3.8 + eslint-import-resolver-typescript: 3.5.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.8)(eslint-plugin-import@2.28.0)(eslint@8.40.0) transitivePeerDependencies: - supports-color dev: true - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0): - resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} + /eslint-plugin-import@2.28.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0): + resolution: {integrity: sha512-B8s/n+ZluN7sxj9eUf7/pRFERX0r5bnFA2dCaLHy2ZeaQEAz0k+ZZkFWRFHJAqxfxQDx6KLv9LeIki7cFdwW+Q==} engines: {node: '>=4'} peerDependencies: '@typescript-eslint/parser': '*' @@ -4920,19 +5100,22 @@ packages: dependencies: '@typescript-eslint/parser': 5.59.6(eslint@8.40.0)(typescript@5.0.4) array-includes: 3.1.6 + array.prototype.findlastindex: 1.2.2 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 eslint: 8.40.0 - eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.7)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) + eslint-import-resolver-node: 0.3.8 + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.6)(eslint-import-resolver-node@0.3.8)(eslint-import-resolver-typescript@3.5.5)(eslint@8.40.0) has: 1.0.3 - is-core-module: 2.12.1 + is-core-module: 2.13.0 is-glob: 4.0.3 minimatch: 3.1.2 + object.fromentries: 2.0.6 + object.groupby: 1.0.0 object.values: 1.1.6 - resolve: 1.22.2 + resolve: 1.22.4 semver: 6.3.1 tsconfig-paths: 3.14.2 transitivePeerDependencies: @@ -4958,7 +5141,7 @@ packages: emoji-regex: 9.2.2 eslint: 8.40.0 has: 1.0.3 - jsx-ast-utils: 3.3.4 + jsx-ast-utils: 3.3.5 language-tags: 1.0.5 minimatch: 3.1.2 object.entries: 1.1.6 @@ -4975,8 +5158,8 @@ packages: eslint: 8.40.0 dev: true - /eslint-plugin-react@7.32.2(eslint@8.40.0): - resolution: {integrity: sha512-t2fBMa+XzonrrNkyVirzKlvn5RXzzPwRHtMvLAtVZrt8oxgnTQaYbU6SXTOO1mwQgp1y5+toMSKInnzGr0Knqg==} + /eslint-plugin-react@7.33.1(eslint@8.40.0): + resolution: {integrity: sha512-L093k0WAMvr6VhNwReB8VgOq5s2LesZmrpPdKz/kZElQDzqS7G7+DnKoqT+w4JwuiGeAhAvHO0fvy0Eyk4ejDA==} engines: {node: '>=4'} peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 @@ -4987,7 +5170,7 @@ packages: doctrine: 2.1.0 eslint: 8.40.0 estraverse: 5.3.0 - jsx-ast-utils: 3.3.4 + jsx-ast-utils: 3.3.5 minimatch: 3.1.2 object.entries: 1.1.6 object.fromentries: 2.0.6 @@ -5026,16 +5209,16 @@ packages: esrecurse: 4.3.0 estraverse: 4.3.0 - /eslint-scope@7.2.1: - resolution: {integrity: sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==} + /eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 dev: true - /eslint-visitor-keys@3.4.1: - resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==} + /eslint-visitor-keys@3.4.2: + resolution: {integrity: sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true @@ -5045,8 +5228,8 @@ packages: hasBin: true dependencies: '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0) - '@eslint-community/regexpp': 4.5.1 - '@eslint/eslintrc': 2.1.0 + '@eslint-community/regexpp': 4.6.2 + '@eslint/eslintrc': 2.1.1 '@eslint/js': 8.40.0 '@humanwhocodes/config-array': 0.11.10 '@humanwhocodes/module-importer': 1.0.1 @@ -5057,8 +5240,8 @@ packages: debug: 4.3.4 doctrine: 3.0.0 escape-string-regexp: 4.0.0 - eslint-scope: 7.2.1 - eslint-visitor-keys: 3.4.1 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.2 espree: 9.6.1 esquery: 1.5.0 esutils: 2.0.3 @@ -5073,7 +5256,7 @@ packages: imurmurhash: 0.1.4 is-glob: 4.0.3 is-path-inside: 3.0.3 - js-sdsl: 4.4.1 + js-sdsl: 4.4.2 js-yaml: 4.1.0 json-stable-stringify-without-jsonify: 1.0.1 levn: 0.4.1 @@ -5094,7 +5277,7 @@ packages: dependencies: acorn: 8.10.0 acorn-jsx: 5.3.2(acorn@8.10.0) - eslint-visitor-keys: 3.4.1 + eslint-visitor-keys: 3.4.2 dev: true /esprima@4.0.1: @@ -5150,6 +5333,10 @@ packages: engines: {node: '>=6'} dev: false + /eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + dev: false + /events@3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} @@ -5169,8 +5356,8 @@ packages: strip-final-newline: 2.0.0 dev: true - /execa@7.1.1: - resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==} + /execa@7.2.0: + resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} dependencies: cross-spawn: 7.0.3 @@ -5232,8 +5419,13 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - /fast-glob@3.3.0: - resolution: {integrity: sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==} + /fast-equals@5.0.1: + resolution: {integrity: sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==} + engines: {node: '>=6.0.0'} + dev: false + + /fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 @@ -5326,7 +5518,7 @@ packages: resolution: {integrity: sha512-KSuV3ur4gf2KqMNoZx3nXNVhqCkn42GuTYCX4tXPEwf0MjpFQmNMiN6m7dXaUXgIoivL6/65agoUMg4RLS0Vbg==} engines: {node: '>=10'} dependencies: - tslib: 2.6.0 + tslib: 2.6.1 dev: false /follow-redirects@1.15.2: @@ -5397,7 +5589,7 @@ packages: dependencies: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) - tslib: 2.6.0 + tslib: 2.6.1 optionalDependencies: '@emotion/is-prop-valid': 0.8.8 dev: false @@ -5579,7 +5771,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.0 + fast-glob: 3.3.1 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -5590,7 +5782,7 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dependencies: dir-glob: 3.0.1 - fast-glob: 3.3.0 + fast-glob: 3.3.1 ignore: 5.2.4 merge2: 1.4.1 slash: 4.0.0 @@ -5629,13 +5821,25 @@ packages: chokidar: 3.5.3 cosmiconfig: 7.1.0 json5: 2.2.3 - pg: 8.11.1 - tslib: 2.6.0 + pg: 8.11.2 + tslib: 2.6.1 yargs: 16.2.0 transitivePeerDependencies: - pg-native dev: false + /h3@1.7.1: + resolution: {integrity: sha512-A9V2NEDNHet7v1gCg7CMwerSigLi0SRbhTy7C3lGb0N4YKIpPmLDjedTUopqp4dnn7COHfqUjjaz3zbtz4QduA==} + dependencies: + cookie-es: 1.0.0 + defu: 6.1.2 + destr: 2.0.1 + iron-webcrypto: 0.7.1 + radix3: 1.0.1 + ufo: 1.2.0 + uncrypto: 0.1.3 + dev: false + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -5782,6 +5986,11 @@ packages: engines: {node: '>=0.8.19'} dev: true + /inflation@2.0.0: + resolution: {integrity: sha512-m3xv4hJYR2oXw4o4Y5l6P5P16WYmazYof+el6Al3f+YlggGj6qT9kImBAnzDelRALnP5d3h4jGBPKzYCizjZZw==} + engines: {node: '>= 0.8.0'} + dev: false + /inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -5800,6 +6009,11 @@ packages: side-channel: 1.0.4 dev: true + /internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + dev: false + /invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} dependencies: @@ -5811,6 +6025,10 @@ packages: engines: {node: '>= 0.10'} dev: false + /iron-webcrypto@0.7.1: + resolution: {integrity: sha512-K/UmlEhPCPXEHV5hAtH5C0tI5JnFuOrv4yO/j7ODPl3HaiiHBLbOLTde+ieUaAyfCATe4LoAnclyF+hmSCOVmQ==} + dev: false + /is-alphabetical@1.0.4: resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} dev: false @@ -5875,8 +6093,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - /is-core-module@2.12.1: - resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} + /is-core-module@2.13.0: + resolution: {integrity: sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==} dependencies: has: 1.0.3 @@ -6063,7 +6281,7 @@ packages: resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} engines: {node: '>= 10.13.0'} dependencies: - '@types/node': 18.17.1 + '@types/node': 18.17.3 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -6071,8 +6289,8 @@ packages: resolution: {integrity: sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==} dev: false - /js-sdsl@4.4.1: - resolution: {integrity: sha512-6Gsx8R0RucyePbWqPssR8DyfuXmLBooYN5cZFZKjHGnQuaf7pEzhtpceagJxVu4LqhYY5EYA7nko3FmeHZ1KbA==} + /js-sdsl@4.4.2: + resolution: {integrity: sha512-dwXFwByc/ajSV6m5bcKAPwe4yDDF6D614pxmIi5odytzxRlwqF6nwoiCek80Ixc7Cvma5awClxrzFtxCQvcM8w==} dev: true /js-tiktoken@1.0.7: @@ -6105,7 +6323,7 @@ packages: dependencies: '@bcherny/json-schema-ref-parser': 10.0.5-fork '@types/json-schema': 7.0.12 - '@types/lodash': 4.14.195 + '@types/lodash': 4.14.196 '@types/prettier': 2.7.3 cli-color: 2.0.3 get-stdin: 8.0.0 @@ -6150,8 +6368,8 @@ packages: resolution: {integrity: sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==} dev: false - /jsx-ast-utils@3.3.4: - resolution: {integrity: sha512-fX2TVdCViod6HwKEtSWGHs57oFhVfCMwieb9PuRDgjDPh5XeqJiHFFFJCHxU5cnTc3Bu/GRL+kPiFmw8XWOfKw==} + /jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} dependencies: array-includes: 3.1.6 @@ -6160,6 +6378,11 @@ packages: object.values: 1.1.6 dev: true + /kysely@0.26.1: + resolution: {integrity: sha512-FVRomkdZofBu3O8SiwAOXrwbhPZZr8mBN5ZeUWyprH29jzvy6Inzqbd0IMmGxpd4rcOCL9HyyBNWBa8FBqDAdg==} + engines: {node: '>=14.0.0'} + dev: false + /language-subtag-registry@0.3.22: resolution: {integrity: sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==} dev: true @@ -6290,6 +6513,14 @@ packages: resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} dev: false + /lucide-react@0.265.0(react@18.2.0): + resolution: {integrity: sha512-znyvziBEUQ7CKR31GiU4viomQbJrpDLG5ac+FajwiZIavC3YbPFLkzQx3dCXT4JWJx/pB34EwmtiZ0ElGZX0PA==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /magic-string@0.27.0: resolution: {integrity: sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==} engines: {node: '>=12'} @@ -6297,8 +6528,8 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: false - /magic-string@0.30.1: - resolution: {integrity: sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==} + /magic-string@0.30.2: + resolution: {integrity: sha512-lNZdu7pewtq/ZvWUp9Wpf/x7WzMTsR26TWV03BRZrXFsv+BI6dy8RAiKgm1uM/kyR0rCfUcqvOlXKG66KhIGug==} engines: {node: '>=12'} dependencies: '@jridgewell/sourcemap-codec': 1.4.15 @@ -6429,7 +6660,7 @@ packages: acorn: 8.10.0 pathe: 1.1.1 pkg-types: 1.0.3 - ufo: 1.1.2 + ufo: 1.2.0 dev: true /monaco-editor@0.40.0: @@ -6515,7 +6746,7 @@ packages: dependencies: next: 13.4.2(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) react: 18.2.0 - tslib: 2.6.0 + tslib: 2.6.1 use-query-params: 2.2.1(react-dom@18.2.0)(react@18.2.0) dev: false @@ -6547,7 +6778,7 @@ packages: '@next/env': 13.4.2 '@swc/helpers': 0.5.1 busboy: 1.6.0 - caniuse-lite: 1.0.30001517 + caniuse-lite: 1.0.30001519 postcss: 8.4.14 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) @@ -6568,6 +6799,15 @@ packages: - babel-plugin-macros dev: false + /nextjs-cors@2.1.2(next@13.4.2): + resolution: {integrity: sha512-2yOVivaaf2ILe4f/qY32hnj3oC77VCOsUQJQfhVMGsXE/YMEWUY2zy78sH9FKUCM7eG42/l3pDofIzMD781XGA==} + peerDependencies: + next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0 + dependencies: + cors: 2.8.5 + next: 13.4.2(@babel/core@7.22.9)(react-dom@18.2.0)(react@18.2.0) + dev: false + /nextjs-routes@2.0.1(next@13.4.2): resolution: {integrity: sha512-pBGRm6uR44zwUjWWYn6+gwz08BhBbqUYlIzsbNHAh1TWohHYKWFaa2YVsj8BxEo726MZYg87OJPnHpaaY1ia0w==} hasBin: true @@ -6595,6 +6835,22 @@ packages: whatwg-url: 5.0.0 dev: false + /node-mocks-http@1.12.2: + resolution: {integrity: sha512-xhWwC0dh35R9rf0j3bRZXuISXdHxxtMx0ywZQBwjrg3yl7KpRETzogfeCamUIjltpn0Fxvs/ZhGJul1vPLrdJQ==} + engines: {node: '>=0.6'} + dependencies: + accepts: 1.3.8 + content-disposition: 0.5.4 + depd: 1.1.2 + fresh: 0.5.2 + merge-descriptors: 1.0.1 + methods: 1.1.2 + mime: 1.6.0 + parseurl: 1.3.3 + range-parser: 1.2.1 + type-is: 1.6.18 + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} @@ -6677,6 +6933,15 @@ packages: es-abstract: 1.22.1 dev: true + /object.groupby@1.0.0: + resolution: {integrity: sha512-70MWG6NfRH9GnbZOikuhPPYzpUpof9iW2J9E4dW7FXTqPNb6rllE6u39SKwwiNh8lCwX3DDb5OgcKGiEBrTTyw==} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + get-intrinsic: 1.2.1 + dev: true + /object.hasown@1.1.2: resolution: {integrity: sha512-B5UIT3J1W+WuWIU55h0mjlwaqxiE5vYENJXIXZ4VFe05pNYrkKuK0U/6aFcb0pKywYJh7IhfoqUfKVmrJJHZHw==} dependencies: @@ -6695,7 +6960,6 @@ packages: /obuf@1.1.2: resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} - dev: false /oidc-token-hash@5.0.3: resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==} @@ -6744,14 +7008,17 @@ packages: '@types/node': 18.16.0 '@types/node-fetch': 2.6.4 abort-controller: 3.0.0 - agentkeepalive: 4.3.0 + agentkeepalive: 4.5.0 digest-fetch: 1.3.0 form-data-encoder: 1.7.2 formdata-node: 4.4.1 node-fetch: 2.6.12 transitivePeerDependencies: - encoding - - supports-color + dev: false + + /openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} dev: false /openapi-typescript@5.4.1: @@ -6763,7 +7030,7 @@ packages: mime: 3.0.0 prettier: 2.8.8 tiny-glob: 0.2.9 - undici: 5.22.1 + undici: 5.23.0 yargs-parser: 21.1.1 dev: true @@ -6772,10 +7039,10 @@ packages: hasBin: true dependencies: ansi-colors: 4.1.3 - fast-glob: 3.3.0 + fast-glob: 3.3.1 js-yaml: 4.1.0 supports-color: 9.4.0 - undici: 5.22.1 + undici: 5.23.0 yargs-parser: 21.1.1 dev: true @@ -6912,31 +7179,28 @@ packages: dev: false optional: true - /pg-connection-string@2.6.1: - resolution: {integrity: sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==} + /pg-connection-string@2.6.2: + resolution: {integrity: sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==} dev: false /pg-int8@1.0.1: resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==} engines: {node: '>=4.0.0'} - dev: false /pg-numeric@1.0.2: resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==} engines: {node: '>=4'} - dev: false - /pg-pool@3.6.1(pg@8.11.1): + /pg-pool@3.6.1(pg@8.11.2): resolution: {integrity: sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og==} peerDependencies: pg: '>=8.0' dependencies: - pg: 8.11.1 + pg: 8.11.2 dev: false /pg-protocol@1.6.0: resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==} - dev: false /pg-types@2.2.0: resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==} @@ -6960,10 +7224,9 @@ packages: postgres-date: 2.0.1 postgres-interval: 3.0.0 postgres-range: 1.1.3 - dev: false - /pg@8.11.1: - resolution: {integrity: sha512-utdq2obft07MxaDg0zBJI+l/M3mBRfIpEN3iSemsz0G5F2/VXx+XzqF4oxrbIZXQxt2AZzIUzyVg/YM6xOP/WQ==} + /pg@8.11.2: + resolution: {integrity: sha512-l4rmVeV8qTIrrPrIR3kZQqBgSN93331s9i6wiUiLOSk0Q7PmUxZD/m1rQI622l3NfqBby9Ar5PABfS/SulfieQ==} engines: {node: '>= 8.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -6973,8 +7236,8 @@ packages: dependencies: buffer-writer: 2.0.0 packet-reader: 1.0.0 - pg-connection-string: 2.6.1 - pg-pool: 3.6.1(pg@8.11.1) + pg-connection-string: 2.6.2 + pg-pool: 3.6.1(pg@8.11.2) pg-protocol: 1.6.0 pg-types: 2.2.0 pgpass: 1.0.5 @@ -7008,6 +7271,10 @@ packages: engines: {node: '>=4'} dev: false + /postcss-value-parser@3.3.1: + resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==} + dev: false + /postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: false @@ -7021,8 +7288,8 @@ packages: source-map-js: 1.0.2 dev: false - /postcss@8.4.26: - resolution: {integrity: sha512-jrXHFF8iTloAenySjM/ob3gSj7pCu0Ji49hnjqzsgSRa50hkWCKD0HQ+gMNJkW38jBI68MpAAg7ZWwHwX8NMMw==} + /postcss@8.4.27: + resolution: {integrity: sha512-gY/ACJtJPSmUFPDCHtX78+01fHa64FaU4zaaWfuh1MhGJISufJAH4cun6k/8fwsHYeK4UQmENQK+tRLCFJE8JQ==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.6 @@ -7038,7 +7305,6 @@ packages: /postgres-array@3.0.2: resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==} engines: {node: '>=12'} - dev: false /postgres-bytea@1.0.0: resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==} @@ -7050,7 +7316,6 @@ packages: engines: {node: '>= 6'} dependencies: obuf: 1.1.2 - dev: false /postgres-date@1.0.7: resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==} @@ -7060,7 +7325,6 @@ packages: /postgres-date@2.0.1: resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==} engines: {node: '>=12'} - dev: false /postgres-interval@1.2.0: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} @@ -7072,11 +7336,9 @@ packages: /postgres-interval@3.0.0: resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==} engines: {node: '>=12'} - dev: false /postgres-range@1.1.3: resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==} - dev: false /posthog-js@1.75.3: resolution: {integrity: sha512-q5xP4R/Tx8E6H0goZQjY+URMLATFiYXc2raHA+31aNvpBs118fPTmExa4RK6MgRZDFhBiMUBZNT6aj7dM3SyUQ==} @@ -7123,8 +7385,8 @@ packages: hasBin: true dev: false - /pretty-format@29.6.1: - resolution: {integrity: sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==} + /pretty-format@29.6.2: + resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} dependencies: '@jest/schemas': 29.6.0 @@ -7199,10 +7461,21 @@ packages: side-channel: 1.0.4 dev: false + /qs@6.11.2: + resolution: {integrity: sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /radix3@1.0.1: + resolution: {integrity: sha512-y+AcwZ3HcUIGc9zGsNVf5+BY/LxL+z+4h4J3/pp8jxSmy1STaCocPS3qrj4tA5ehUSzqtqK+0Aygvz/r/8vy4g==} + dev: false + /randombytes@2.1.0: resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} dependencies: @@ -7223,6 +7496,16 @@ packages: unpipe: 1.0.0 dev: false + /raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + /raw-loader@4.0.2(webpack@5.88.2): resolution: {integrity: sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==} engines: {node: '>= 10.13.0'} @@ -7239,7 +7522,7 @@ packages: dependencies: '@babel/runtime': 7.22.6 '@types/base16': 1.0.2 - '@types/lodash': 4.14.195 + '@types/lodash': 4.14.196 base16: 1.0.0 color: 3.2.1 csstype: 3.1.2 @@ -7336,12 +7619,16 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 dependencies: '@babel/runtime': 7.22.6 - '@types/lodash': 4.14.195 + '@types/lodash': 4.14.196 '@types/react': 18.2.6 react: 18.2.0 react-base16-styling: 0.9.1 dev: false + /react-lifecycles-compat@3.0.4: + resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==} + dev: false + /react-remove-scroll-bar@2.3.4(@types/react@18.2.6)(react@18.2.0): resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} engines: {node: '>=10'} @@ -7355,7 +7642,7 @@ packages: '@types/react': 18.2.6 react: 18.2.0 react-style-singleton: 2.2.1(@types/react@18.2.6)(react@18.2.0) - tslib: 2.6.0 + tslib: 2.6.1 dev: false /react-remove-scroll@2.5.6(@types/react@18.2.6)(react@18.2.0): @@ -7372,11 +7659,22 @@ packages: react: 18.2.0 react-remove-scroll-bar: 2.3.4(@types/react@18.2.6)(react@18.2.0) react-style-singleton: 2.2.1(@types/react@18.2.6)(react@18.2.0) - tslib: 2.6.0 + tslib: 2.6.1 use-callback-ref: 1.3.0(@types/react@18.2.6)(react@18.2.0) use-sidecar: 1.1.2(@types/react@18.2.6)(react@18.2.0) dev: false + /react-resize-detector@8.1.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + lodash: 4.17.21 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /react-select@5.7.4(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-NhuE56X+p9QDFh4BgeygHFIvJJszO1i1KSkg/JPcIJrbovyRtI+GuOEa4XzFCEpZRAEoEI8u/cAHK+jG/PgUzQ==} peerDependencies: @@ -7386,7 +7684,7 @@ packages: '@babel/runtime': 7.22.6 '@emotion/cache': 11.11.0 '@emotion/react': 11.11.1(@types/react@18.2.6)(react@18.2.0) - '@floating-ui/dom': 1.4.5 + '@floating-ui/dom': 1.5.1 '@types/react-transition-group': 4.4.6 memoize-one: 6.0.0 prop-types: 15.8.1 @@ -7398,6 +7696,20 @@ packages: - '@types/react' dev: false + /react-smooth@2.0.3(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yl4y3XiMorss7ayF5QnBiSprig0+qFHui8uh7Hgg46QX5O+aRMRKlfGGNGLHno35JkQSvSYY8eCWkBfHfrSHfg==} + peerDependencies: + prop-types: ^15.6.0 + react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + fast-equals: 5.0.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-transition-group: 2.9.0(react-dom@18.2.0)(react@18.2.0) + dev: false + /react-ssr-prepass@1.5.0(react@18.2.0): resolution: {integrity: sha512-yFNHrlVEReVYKsLI5lF05tZoHveA5pGzjFbFJY/3pOqqjGOmMmqx83N4hIjN2n6E1AOa+eQEUxs3CgRnPmT0RQ==} peerDependencies: @@ -7420,7 +7732,7 @@ packages: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.2.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /react-syntax-highlighter@15.5.0(react@18.2.0): @@ -7450,6 +7762,20 @@ packages: - '@types/react' dev: false + /react-transition-group@2.9.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==} + peerDependencies: + react: '>=15.0.0' + react-dom: '>=15.0.0' + dependencies: + dom-helpers: 3.4.0 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-lifecycles-compat: 3.0.4 + dev: false + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} peerDependencies: @@ -7507,7 +7833,42 @@ packages: ast-types: 0.16.1 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.6.0 + tslib: 2.6.1 + dev: false + + /recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + dependencies: + decimal.js-light: 2.5.1 + dev: false + + /recharts@2.7.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-HMKRBkGoOXHW+7JcRa6+MukPSifNtJlqbc+JreGVNA407VLE/vOP+8n3YYjprDVVIF9E2ZgwWnL3D7K/LUFzBg==} + engines: {node: '>=12'} + peerDependencies: + prop-types: ^15.6.0 + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + classnames: 2.3.2 + eventemitter3: 4.0.7 + lodash: 4.17.21 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 16.13.1 + react-resize-detector: 8.1.0(react-dom@18.2.0)(react@18.2.0) + react-smooth: 2.0.3(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) + recharts-scale: 0.4.5 + reduce-css-calc: 2.1.8 + victory-vendor: 36.6.11 + dev: false + + /reduce-css-calc@2.1.8: + resolution: {integrity: sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==} + dependencies: + css-unit-converter: 1.1.2 + postcss-value-parser: 3.3.1 dev: false /refractor@3.6.0: @@ -7547,11 +7908,11 @@ packages: /resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - /resolve@1.22.2: - resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} + /resolve@1.22.4: + resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true dependencies: - is-core-module: 2.12.1 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -7559,7 +7920,7 @@ packages: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} hasBin: true dependencies: - is-core-module: 2.12.1 + is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 dev: true @@ -7584,8 +7945,8 @@ packages: fsevents: 2.3.2 dev: false - /rollup@3.26.3: - resolution: {integrity: sha512-7Tin0C8l86TkpcMtXvQu6saWH93nhG3dGQ1/+l5V2TDMceTxO7kDiK6GzbfLWNNxqJXm591PcEZUozZm51ogwQ==} + /rollup@3.27.2: + resolution: {integrity: sha512-YGwmHf7h2oUHkVBT248x0yt6vZkYQ3/rvE5iQuVBh3WO8GcJ6BNeOkpoX1yMHIiBm18EMLjBPIoUDkhgnyxGOQ==} engines: {node: '>=14.18.0', npm: '>=8.0.0'} hasBin: true optionalDependencies: @@ -7612,7 +7973,7 @@ packages: /rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} dependencies: - tslib: 2.6.0 + tslib: 2.6.1 dev: false /safe-array-concat@1.0.0: @@ -7794,7 +8155,7 @@ packages: dependencies: '@socket.io/component-emitter': 3.1.0 debug: 4.3.4 - engine.io-client: 6.5.1 + engine.io-client: 6.5.2 socket.io-parser: 4.2.4 transitivePeerDependencies: - bufferutil @@ -7820,7 +8181,7 @@ packages: base64id: 2.0.0 cors: 2.8.5 debug: 4.3.4 - engine.io: 6.5.1 + engine.io: 6.5.2 socket.io-adapter: 2.5.2 socket.io-parser: 4.2.4 transitivePeerDependencies: @@ -7976,8 +8337,8 @@ packages: engines: {node: '>=8'} dev: true - /strip-literal@1.0.1: - resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==} + /strip-literal@1.3.0: + resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==} dependencies: acorn: 8.10.0 dev: true @@ -8043,7 +8404,7 @@ packages: engines: {node: ^14.18.0 || >=16.0.0} dependencies: '@pkgr/utils': 2.4.2 - tslib: 2.6.0 + tslib: 2.6.1 dev: true /tapable@2.2.1: @@ -8180,6 +8541,22 @@ packages: hasBin: true dev: false + /trpc-openapi@1.2.0(@trpc/server@10.26.0)(zod@3.21.4): + resolution: {integrity: sha512-pfYoCd/3KYXWXvUPZBKJw455OOwngKN/6SIcj7Yit19OMLJ+8yVZkEvGEeg5wUSwfsiTdRsKuvqkRPXVSwV7ew==} + peerDependencies: + '@trpc/server': ^10.0.0 + zod: ^3.14.4 + dependencies: + '@trpc/server': 10.26.0 + co-body: 6.1.0 + h3: 1.7.1 + lodash.clonedeep: 4.5.0 + node-mocks-http: 1.12.2 + openapi-types: 12.1.3 + zod: 3.21.4 + zod-to-json-schema: 3.21.4(zod@3.21.4) + dev: false + /tsconfck@2.1.2(typescript@5.0.4): resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} engines: {node: ^14.13.1 || ^16 || >=18} @@ -8210,8 +8587,8 @@ packages: resolution: {integrity: sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==} dev: false - /tslib@2.6.0: - resolution: {integrity: sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==} + /tslib@2.6.1: + resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} /tsutils@3.21.0(typescript@5.0.4): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} @@ -8256,6 +8633,11 @@ packages: engines: {node: '>=8'} dev: false + /type-fest@2.19.0: + resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} + engines: {node: '>=12.20'} + dev: false + /type-fest@4.0.0: resolution: {integrity: sha512-d/oYtUnPM9zar2fqqGLYPzgcY0qUlYK0evgNVti93xpzfjGkMgZHu9Lvgrkn0rqGXTgsFRxFamzjGoD9Uo+dgw==} engines: {node: '>=16'} @@ -8320,9 +8702,8 @@ packages: engines: {node: '>=12.20'} hasBin: true - /ufo@1.1.2: - resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==} - dev: true + /ufo@1.2.0: + resolution: {integrity: sha512-RsPyTbqORDNDxqAdQPQBpgqhWle1VcTSou/FraClYlHf6TZnQcGslpLcAphNR+sQW4q5lLWLbOsRlh9j24baQg==} /unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} @@ -8333,8 +8714,12 @@ packages: which-boxed-primitive: 1.0.2 dev: true - /undici@5.22.1: - resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==} + /uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + dev: false + + /undici@5.23.0: + resolution: {integrity: sha512-1D7w+fvRsqlQ9GscLBwcAJinqcZGHUKjbOmXdlE/v8BvEGXjeWAax+341q44EuTcHXXnfyKNbKRq4Lg7OzhMmg==} engines: {node: '>=14.0'} dependencies: busboy: 1.6.0 @@ -8367,16 +8752,6 @@ packages: escalade: 3.1.1 picocolors: 1.0.0 - /update-browserslist-db@1.0.11(browserslist@4.21.9): - resolution: {integrity: sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.21.9 - escalade: 3.1.1 - picocolors: 1.0.0 - /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -8394,7 +8769,7 @@ packages: dependencies: '@types/react': 18.2.6 react: 18.2.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /use-composed-ref@1.3.0(react@18.2.0): @@ -8463,7 +8838,7 @@ packages: '@types/react': 18.2.6 detect-node-es: 1.1.0 react: 18.2.0 - tslib: 2.6.0 + tslib: 2.6.1 dev: false /use-sync-external-store@1.2.0(react@18.2.0): @@ -8508,6 +8883,25 @@ packages: engines: {node: '>= 0.8'} dev: false + /victory-vendor@36.6.11: + resolution: {integrity: sha512-nT8kCiJp8dQh8g991J/R5w5eE2KnO8EAIP0xocWlh9l2okngMWglOPoMZzJvek8Q1KUc4XE/mJxTZnvOB1sTYg==} + dependencies: + '@types/d3-array': 3.0.5 + '@types/d3-ease': 3.0.0 + '@types/d3-interpolate': 3.0.1 + '@types/d3-scale': 4.0.3 + '@types/d3-shape': 3.1.1 + '@types/d3-time': 3.0.0 + '@types/d3-timer': 3.0.0 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + dev: false + /vite-node@0.33.0(@types/node@18.16.0): resolution: {integrity: sha512-19FpHYbwWWxDr73ruNahC+vtEdza52kA90Qb3La98yZ0xULqV8A5JLNPUff0f5zID4984tW7l3DH2przTJUZSw==} engines: {node: '>=v14.18.0'} @@ -8518,7 +8912,7 @@ packages: mlly: 1.4.0 pathe: 1.1.1 picocolors: 1.0.0 - vite: 4.4.4(@types/node@18.16.0) + vite: 4.4.8(@types/node@18.16.0) transitivePeerDependencies: - '@types/node' - less @@ -8546,8 +8940,8 @@ packages: - typescript dev: false - /vite@4.4.4(@types/node@18.16.0): - resolution: {integrity: sha512-4mvsTxjkveWrKDJI70QmelfVqTm+ihFAb6+xf4sjEU2TmUCTlVX87tmg/QooPEMQb/lM9qGHT99ebqPziEd3wg==} + /vite@4.4.8(@types/node@18.16.0): + resolution: {integrity: sha512-LONawOUUjxQridNWGQlNizfKH89qPigK36XhMI7COMGztz8KNY0JHim7/xDd71CZwGT4HtSRgI7Hy+RlhG0Gvg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -8575,9 +8969,9 @@ packages: optional: true dependencies: '@types/node': 18.16.0 - esbuild: 0.18.14 - postcss: 8.4.26 - rollup: 3.26.3 + esbuild: 0.18.18 + postcss: 8.4.27 + rollup: 3.27.2 optionalDependencies: fsevents: 2.3.2 dev: true @@ -8627,14 +9021,14 @@ packages: chai: 4.3.7 debug: 4.3.4 local-pkg: 0.4.3 - magic-string: 0.30.1 + magic-string: 0.30.2 pathe: 1.1.1 picocolors: 1.0.0 std-env: 3.3.3 - strip-literal: 1.0.1 + strip-literal: 1.3.0 tinybench: 2.5.0 tinypool: 0.6.0 - vite: 4.4.4(@types/node@18.16.0) + vite: 4.4.8(@types/node@18.16.0) vite-node: 0.33.0(@types/node@18.16.0) why-is-node-running: 2.2.2 transitivePeerDependencies: @@ -8861,6 +9255,14 @@ packages: resolution: {integrity: sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==} dev: false + /zod-to-json-schema@3.21.4(zod@3.21.4): + resolution: {integrity: sha512-fjUZh4nQ1s6HMccgIeE0VP4QG/YRGPmyjO9sAh890aQKPEk3nqbfUXhMFaC+Dr5KvYBm8BCyvfpZf2jY9aGSsw==} + peerDependencies: + zod: ^3.21.4 + dependencies: + zod: 3.21.4 + dev: false + /zod@3.21.4: resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} dev: false diff --git a/app/prisma/migrations/20230806024615_add_logged_calls_and_api_keys/migration.sql b/app/prisma/migrations/20230806024615_add_logged_calls_and_api_keys/migration.sql new file mode 100644 index 0000000..4611bab --- /dev/null +++ b/app/prisma/migrations/20230806024615_add_logged_calls_and_api_keys/migration.sql @@ -0,0 +1,90 @@ +-- CreateTable +CREATE TABLE "LoggedCall" ( + "id" UUID NOT NULL, + "startTime" TIMESTAMP(3) NOT NULL, + "cacheHit" BOOLEAN NOT NULL, + "modelResponseId" UUID NOT NULL, + "organizationId" UUID NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LoggedCall_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LoggedCallModelResponse" ( + "id" UUID NOT NULL, + "reqPayload" JSONB NOT NULL, + "respStatus" INTEGER, + "respPayload" JSONB, + "error" TEXT, + "startTime" TIMESTAMP(3) NOT NULL, + "endTime" TIMESTAMP(3) NOT NULL, + "cacheKey" TEXT, + "durationMs" INTEGER, + "inputTokens" INTEGER, + "outputTokens" INTEGER, + "finishReason" TEXT, + "completionId" TEXT, + "totalCost" DECIMAL(18,12), + "originalLoggedCallId" UUID NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LoggedCallModelResponse_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "LoggedCallTag" ( + "id" UUID NOT NULL, + "name" TEXT NOT NULL, + "value" TEXT, + "loggedCallId" UUID NOT NULL, + + CONSTRAINT "LoggedCallTag_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "ApiKey" ( + "id" UUID NOT NULL, + "name" TEXT NOT NULL, + "apiKey" TEXT NOT NULL, + "organizationId" UUID NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "LoggedCall_startTime_idx" ON "LoggedCall"("startTime"); + +-- CreateIndex +CREATE UNIQUE INDEX "LoggedCallModelResponse_originalLoggedCallId_key" ON "LoggedCallModelResponse"("originalLoggedCallId"); + +-- CreateIndex +CREATE INDEX "LoggedCallModelResponse_cacheKey_idx" ON "LoggedCallModelResponse"("cacheKey"); + +-- CreateIndex +CREATE INDEX "LoggedCallTag_name_idx" ON "LoggedCallTag"("name"); + +-- CreateIndex +CREATE INDEX "LoggedCallTag_name_value_idx" ON "LoggedCallTag"("name", "value"); + +-- CreateIndex +CREATE UNIQUE INDEX "ApiKey_apiKey_key" ON "ApiKey"("apiKey"); + +-- AddForeignKey +ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_modelResponseId_fkey" FOREIGN KEY ("modelResponseId") REFERENCES "LoggedCallModelResponse"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LoggedCall" ADD CONSTRAINT "LoggedCall_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LoggedCallModelResponse" ADD CONSTRAINT "LoggedCallModelResponse_originalLoggedCallId_fkey" FOREIGN KEY ("originalLoggedCallId") REFERENCES "LoggedCall"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "LoggedCallTag" ADD CONSTRAINT "LoggedCallTag_loggedCallId_fkey" FOREIGN KEY ("loggedCallId") REFERENCES "LoggedCall"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/app/prisma/migrations/20230807044936_add_name_to_organization/migration.sql b/app/prisma/migrations/20230807044936_add_name_to_organization/migration.sql new file mode 100644 index 0000000..8ec9300 --- /dev/null +++ b/app/prisma/migrations/20230807044936_add_name_to_organization/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Organization" ADD COLUMN "name" TEXT NOT NULL DEFAULT 'Project 1'; diff --git a/app/prisma/migrations/20230808034313_make_model_response_optional/migration.sql b/app/prisma/migrations/20230808034313_make_model_response_optional/migration.sql new file mode 100644 index 0000000..f9c991c --- /dev/null +++ b/app/prisma/migrations/20230808034313_make_model_response_optional/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "LoggedCall" ALTER COLUMN "modelResponseId" DROP NOT NULL; diff --git a/app/prisma/schema.prisma b/app/prisma/schema.prisma index 6b8812f..60b7d7d 100644 --- a/app/prisma/schema.prisma +++ b/app/prisma/schema.prisma @@ -200,16 +200,21 @@ model DatasetEntry { updatedAt DateTime @updatedAt } +// TODO rename Organization to Project model Organization { id String @id @default(uuid()) @db.Uuid + name String @default("Project 1") + personalOrgUserId String? @unique @db.Uuid - PersonalOrgUser User? @relation(fields: [personalOrgUserId], references: [id], onDelete: Cascade) + personalOrgUser User? @relation(fields: [personalOrgUserId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt organizationUsers OrganizationUser[] experiments Experiment[] datasets Dataset[] + loggedCalls LoggedCall[] + apiKeys ApiKey[] } enum OrganizationUserRole { @@ -249,6 +254,99 @@ model WorldChampEntrant { @@unique([userId]) } +model LoggedCall { + id String @id @default(uuid()) @db.Uuid + + startTime DateTime + + // True if this call was served from the cache, false otherwise + cacheHit Boolean + + // A LoggedCall is always associated with a LoggedCallModelResponse. If this + // is a cache miss, we create a new LoggedCallModelResponse. + // If it's a cache hit, it's a pre-existing LoggedCallModelResponse. + modelResponseId String? @db.Uuid + modelResponse LoggedCallModelResponse? @relation(fields: [modelResponseId], references: [id], onDelete: Cascade) + + // The responses created by this LoggedCall. Will be empty if this LoggedCall was a cache hit. + createdResponses LoggedCallModelResponse[] @relation(name: "ModelResponseOriginalCall") + + organizationId String @db.Uuid + organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) + + tags LoggedCallTag[] + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@index([startTime]) +} + +model LoggedCallModelResponse { + id String @id @default(uuid()) @db.Uuid + + reqPayload Json + + // The HTTP status returned by the model provider + respStatus Int? + respPayload Json? + + // Should be null if the request was successful, and some string if the request failed. + error String? + + startTime DateTime + endTime DateTime + + // Note: the function to calculate the cacheKey should include the project + // ID so we don't share cached responses between projects, which could be an + // attack vector. Also, we should only set the cacheKey on the model if the + // request was successful. + cacheKey String? + + // Derived fields + durationMs Int? + inputTokens Int? + outputTokens Int? + finishReason String? + completionId String? + totalCost Decimal? @db.Decimal(18, 12) + + // The LoggedCall that created this LoggedCallModelResponse + originalLoggedCallId String @unique @db.Uuid + originalLoggedCall LoggedCall @relation(name: "ModelResponseOriginalCall", fields: [originalLoggedCallId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + loggedCalls LoggedCall[] + + @@index([cacheKey]) +} + +model LoggedCallTag { + id String @id @default(uuid()) @db.Uuid + name String + value String? + + loggedCallId String @db.Uuid + loggedCall LoggedCall @relation(fields: [loggedCallId], references: [id], onDelete: Cascade) + + @@index([name]) + @@index([name, value]) +} + +model ApiKey { + id String @id @default(uuid()) @db.Uuid + + name String + apiKey String @unique + + organizationId String @db.Uuid + organization Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + model Account { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid diff --git a/app/prisma/seedDashboard.ts b/app/prisma/seedDashboard.ts new file mode 100644 index 0000000..ff7de60 --- /dev/null +++ b/app/prisma/seedDashboard.ts @@ -0,0 +1,410 @@ +import { v4 as uuidv4 } from "uuid"; +import { type Prisma } from "@prisma/client"; + +import { prisma } from "~/server/db"; +import { hashRequest } from "~/server/utils/hashObject"; +import { type JsonValue } from "type-fest"; + +const MODEL_RESPONSE_TEMPLATES: { + reqPayload: any; + respPayload: any; + respStatus: number; + error: string | null; + inputTokens: number; + outputTokens: number; + finishReason: string; +}[] = [ + { + reqPayload: { + model: "gpt-3.5-turbo-0613", + messages: [ + { + role: "system", + content: + "The user is testing multiple scenarios against the same prompt. Attempt to generate a new scenario that is different from the others.", + }, + { + role: "user", + content: + 'Prompt constructor function:\n---\n/**\n * Use Javascript to define an OpenAI chat completion\n * (https://platform.openai.com/docs/api-reference/chat/create).\n *\n * You have access to the current scenario in the `scenario`\n * variable.\n */\n\ndefinePrompt("openai/ChatCompletion", {\n model: "gpt-3.5-turbo-0613",\n stream: true,\n messages: [\n {\n role: "system",\n content: `Write \'Start experimenting!\' in ${scenario.language}`,\n },\n ],\n});', + }, + { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"French"}', + }, + }, + { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"English"}', + }, + }, + { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"Spanish"}', + }, + }, + { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"German"}', + }, + }, + ], + functions: [ + { + name: "add_scenario", + parameters: { + type: "object", + properties: { + language: { type: "string" }, + }, + }, + }, + ], + temperature: 0.5, + function_call: { + name: "add_scenario", + }, + }, + respStatus: 200, + respPayload: { + id: "chatcmpl-7lNspqePJWVyXwXebupxb1eMozo6Q", + model: "gpt-3.5-turbo-0613", + usage: { + total_tokens: 241, + prompt_tokens: 236, + completion_tokens: 5, + }, + object: "chat.completion", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"Italian"}', + }, + }, + finish_reason: "stop", + }, + ], + created: 1691527579, + }, + error: null, + inputTokens: 236, + outputTokens: 5, + finishReason: "stop", + }, + { + reqPayload: { + model: "gpt-3.5-turbo-0613", + messages: [ + { + role: "system", + content: + "The user is testing multiple scenarios against the same prompt. Attempt to generate a new scenario that is different from the others.", + }, + { + role: "user", + content: + 'Prompt constructor function:\n---\n/**\n * Use Javascript to define an OpenAI chat completion\n * (https://platform.openai.com/docs/api-reference/chat/create).\n *\n * You have access to the current scenario in the `scenario`\n * variable.\n */\n\ndefinePrompt("openai/ChatCompletion", {\n model: "gpt-3.5-turbo-0613",\n stream: true,\n messages: [\n {\n role: "system",\n content: `Write \'Start experimenting!\' in ${scenario.language}`,\n },\n ],\n});', + }, + { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"English"}', + }, + }, + { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"Spanish"}', + }, + }, + { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"German"}', + }, + }, + ], + functions: [ + { + name: "add_scenario", + parameters: { + type: "object", + properties: { + language: { type: "string" }, + }, + }, + }, + ], + temperature: 0.5, + function_call: { + name: "add_scenario", + }, + }, + respStatus: 200, + respPayload: { + id: "chatcmpl-7lNifmc5AncyAvleZRDBhAcLFYBIT", + model: "gpt-3.5-turbo-0613", + usage: { + total_tokens: 227, + prompt_tokens: 222, + completion_tokens: 5, + }, + object: "chat.completion", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: null, + function_call: { + name: "add_scenario", + arguments: '{"language":"French"}', + }, + }, + finish_reason: "stop", + }, + ], + created: 1691526949, + }, + error: null, + inputTokens: 222, + outputTokens: 5, + finishReason: "stop", + }, + { + reqPayload: { + model: "gpt-3.5-turbo-0613", + stream: false, + messages: [ + { + role: "system", + content: "Write 'Start experimenting!' in German", + }, + ], + }, + respStatus: 200, + respPayload: { + id: "chatcmpl-7lNh1TtrsJVgz3Nj70bKkZZk7xPi7", + model: "gpt-3.5-turbo-0613", + usage: { + total_tokens: 21, + prompt_tokens: 14, + completion_tokens: 7, + }, + object: "chat.completion", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: "Fang an zu experimentieren!", + }, + finish_reason: "stop", + }, + ], + created: 1691526847, + }, + error: null, + inputTokens: 14, + outputTokens: 7, + finishReason: "stop", + }, + { + reqPayload: { + model: "gpt-4", + messages: [ + { + role: "system", + content: + 'Your job is to update prompt constructor functions. Here is the api shape for the current model:\n---\n{\n "type": "object",\n "properties": {\n "model": {\n "description": "ID of the model to use. See the [model endpoint compatibility](/docs/models/model-endpoint-compatibility) table for details on which models work with the Chat API.",\n "example": "gpt-3.5-turbo",\n "type": "string",\n "enum": [\n "gpt-4",\n "gpt-4-0613",\n "gpt-4-32k",\n "gpt-4-32k-0613",\n "gpt-3.5-turbo",\n "gpt-3.5-turbo-16k",\n "gpt-3.5-turbo-0613",\n "gpt-3.5-turbo-16k-0613"\n ]\n },\n "messages": {\n "description": "A list of messages comprising the conversation so far. [Example Python code](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_format_inputs_to_ChatGPT_models.ipynb).",\n "type": "array",\n "minItems": 1,\n "items": {\n "type": "object",\n "properties": {\n "role": {\n "type": "string",\n "enum": [\n "system",\n "user",\n "assistant",\n "function"\n ],\n "description": "The role of the messages author. One of `system`, `user`, `assistant`, or `function`."\n },\n "content": {\n "type": "string",\n "description": "The contents of the message. `content` is required for all messages except assistant messages with function calls."\n },\n "name": {\n "type": "string",\n "description": "The name of the author of this message. `name` is required if role is `function`, and it should be the name of the function whose response is in the `content`. May contain a-z, A-Z, 0-9, and underscores, with a maximum length of 64 characters."\n },\n "function_call": {\n "type": "object",\n "description": "The name and arguments of a function that should be called, as generated by the model.",\n "properties": {\n "name": {\n "type": "string",\n "description": "The name of the function to call."\n },\n "arguments": {\n "type": "string",\n "description": "The arguments to call the function with, as generated by the model in JSON format. Note that the model does not always generate valid JSON, and may hallucinate parameters not defined by your function schema. Validate the arguments in your code before calling your function."\n }\n }\n }\n },\n "required": [\n "role"\n ]\n }\n },\n "functions": {\n "description": "A list of functions the model may generate JSON inputs for.",\n "type": "array",\n "minItems": 1,\n "items": {\n "type": "object",\n "properties": {\n "name": {\n "type": "string",\n "description": "The name of the function to be called. Must be a-z, A-Z, 0-9, or contain underscores and dashes, with a maximum length of 64."\n },\n "description": {\n "type": "string",\n "description": "The description of what the function does."\n },\n "parameters": {\n "type": "object",\n "description": "The parameters the functions accepts, described as a JSON Schema object. See the [guide](/docs/guides/gpt/function-calling) for examples, and the [JSON Schema reference](https://json-schema.org/understanding-json-schema/) for documentation about the format.",\n "additionalProperties": true\n }\n },\n "required": [\n "name"\n ]\n }\n },\n "function_call": {\n "description": "Controls how the model responds to function calls. \\"none\\" means the model does not call a function, and responds to the end-user. \\"auto\\" means the model can pick between an end-user or calling a function. Specifying a particular function via `{\\"name\\":\\\\ \\"my_function\\"}` forces the model to call that function. \\"none\\" is the default when no functions are present. \\"auto\\" is the default if functions are present.",\n "oneOf": [\n {\n "type": "string",\n "enum": [\n "none",\n "auto"\n ]\n },\n {\n "type": "object",\n "properties": {\n "name": {\n "type": "string",\n "description": "The name of the function to call."\n }\n },\n "required": [\n "name"\n ]\n }\n ]\n },\n "temperature": {\n "type": "number",\n "minimum": 0,\n "maximum": 2,\n "default": 1,\n "example": 1,\n "nullable": true,\n "description": "What sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic.\\n\\nWe generally recommend altering this or `top_p` but not both.\\n"\n },\n "top_p": {\n "type": "number",\n "minimum": 0,\n "maximum": 1,\n "default": 1,\n "example": 1,\n "nullable": true,\n "description": "An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\\n\\nWe generally recommend altering this or `temperature` but not both.\\n"\n },\n "n": {\n "type": "integer",\n "minimum": 1,\n "maximum": 128,\n "default": 1,\n "example": 1,\n "nullable": true,\n "description": "How many chat completion choices to generate for each input message."\n },\n "stream": {\n "description": "If set, partial message deltas will be sent, like in ChatGPT. Tokens will be sent as data-only [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Event_stream_format) as they become available, with the stream terminated by a `data: [DONE]` message. [Example Python code](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_stream_completions.ipynb).\\n",\n "type": "boolean",\n "nullable": true,\n "default": false\n },\n "stop": {\n "description": "Up to 4 sequences where the API will stop generating further tokens.\\n",\n "default": null,\n "oneOf": [\n {\n "type": "string",\n "nullable": true\n },\n {\n "type": "array",\n "minItems": 1,\n "maxItems": 4,\n "items": {\n "type": "string"\n }\n }\n ]\n },\n "max_tokens": {\n "description": "The maximum number of [tokens](/tokenizer) to generate in the chat completion.\\n\\nThe total length of input tokens and generated tokens is limited by the model\'s context length. [Example Python code](https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb) for counting tokens.\\n",\n "type": "integer"\n },\n "presence_penalty": {\n "type": "number",\n "default": 0,\n "minimum": -2,\n "maximum": 2,\n "nullable": true,\n "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.\\n\\n[See more information about frequency and presence penalties.](/docs/api-reference/parameter-details)\\n"\n },\n "frequency_penalty": {\n "type": "number",\n "default": 0,\n "minimum": -2,\n "maximum": 2,\n "nullable": true,\n "description": "Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.\\n\\n[See more information about frequency and presence penalties.](/docs/api-reference/parameter-details)\\n"\n },\n "logit_bias": {\n "type": "object",\n "x-oaiTypeLabel": "map",\n "default": null,\n "nullable": true,\n "description": "Modify the likelihood of specified tokens appearing in the completion.\\n\\nAccepts a json object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to 100. Mathematically, the bias is added to the logits generated by the model prior to sampling. The exact effect will vary per model, but values between -1 and 1 should decrease or increase likelihood of selection; values like -100 or 100 should result in a ban or exclusive selection of the relevant token.\\n"\n },\n "user": {\n "type": "string",\n "example": "user-1234",\n "description": "A unique identifier representing your end-user, which can help OpenAI to monitor and detect abuse. [Learn more](/docs/guides/safety-best-practices/end-user-ids).\\n"\n }\n },\n "required": [\n "model",\n "messages"\n ]\n}\n\nDo not add any assistant messages.', + }, + { + role: "user", + content: + 'This is the current prompt constructor function:\n---\n/**\n * Use Javascript to define an OpenAI chat completion\n * (https://platform.openai.com/docs/api-reference/chat/create).\n *\n * You have access to the current scenario in the `scenario`\n * variable.\n */\n\ndefinePrompt("openai/ChatCompletion", {\n model: "gpt-3.5-turbo-0613",\n stream: true,\n messages: [\n {\n role: "system",\n content: `Write \'Start experimenting!\' in ${scenario.language}`,\n },\n ],\n});', + }, + { + role: "user", + content: + "Return the prompt constructor function for LLama 2 7B Chat given the existing prompt constructor function for GPT-3.5 Turbo", + }, + { + role: "user", + content: + 'As seen in the first argument to definePrompt, the old provider endpoint was "openai/ChatCompletion". The new provider endpoint is "replicate/llama2". Here is the schema for the new model:\n---\n{\n "type": "object",\n "properties": {\n "model": {\n "type": "string",\n "enum": [\n "7b-chat",\n "13b-chat",\n "70b-chat"\n ]\n },\n "system_prompt": {\n "type": "string",\n "description": "System prompt to send to Llama v2. This is prepended to the prompt and helps guide system behavior."\n },\n "prompt": {\n "type": "string",\n "description": "Prompt to send to Llama v2."\n },\n "max_new_tokens": {\n "type": "number",\n "description": "Maximum number of tokens to generate. A word is generally 2-3 tokens (minimum: 1)"\n },\n "temperature": {\n "type": "number",\n "description": "Adjusts randomness of outputs, 0.1 is a good starting value. (minimum: 0.01; maximum: 5)"\n },\n "top_p": {\n "type": "number",\n "description": "When decoding text, samples from the top p percentage of most likely tokens; lower to ignore less likely tokens (minimum: 0.01; maximum: 1)"\n },\n "repetition_penalty": {\n "type": "number",\n "description": "Penalty for repeated words in generated text; 1 is no penalty, values greater than 1 discourage repetition, less than 1 encourage it. (minimum: 0.01; maximum: 5)"\n },\n "debug": {\n "type": "boolean",\n "description": "provide debugging output in logs"\n }\n },\n "required": [\n "model",\n "prompt"\n ]\n}', + }, + ], + functions: [ + { + name: "update_prompt_constructor_function", + parameters: { + type: "object", + properties: { + new_prompt_function: { + type: "string", + description: "The new prompt function, runnable in typescript", + }, + }, + }, + }, + ], + function_call: { + name: "update_prompt_constructor_function", + }, + }, + respStatus: 200, + respPayload: { + id: "chatcmpl-7lQS3MktOT8BTgNEytl9dkyssCQqL", + model: "gpt-4-0613", + usage: { + total_tokens: 2910, + prompt_tokens: 2802, + completion_tokens: 108, + }, + object: "chat.completion", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: null, + function_call: { + name: "update_prompt_constructor_function", + arguments: + '{\n "new_prompt_function": "definePrompt(\\"replicate/llama2\\", {\\n model: \\"7b-chat\\",\\n system_prompt: `Write \'Start experimenting!\' in ${scenario.language}`,\\n prompt: `Experiment with the Llama v2 model`,\\n max_new_tokens: 100,\\n temperature: 0.1,\\n top_p: 0.95,\\n repetition_penalty: 1.2,\\n debug: true,\\n});"\n}', + }, + }, + finish_reason: "stop", + }, + ], + created: 1691537451, + }, + error: null, + inputTokens: 2802, + outputTokens: 108, + finishReason: "stop", + }, +]; + +await prisma.loggedCallModelResponse.deleteMany(); + +const org = await prisma.organization.findFirst({ + where: { + personalOrgUserId: { + not: null, + }, + }, + orderBy: { + createdAt: "asc", + }, +}); + +if (!org) { + console.error("No org found. Sign up to create your first org."); + process.exit(1); +} + +const loggedCallsToCreate: Prisma.LoggedCallCreateManyInput[] = []; +const loggedCallModelResponsesToCreate: Prisma.LoggedCallModelResponseCreateManyInput[] = []; +const loggedCallsToUpdate: Prisma.LoggedCallUpdateArgs[] = []; +const loggedCallTagsToCreate: Prisma.LoggedCallTagCreateManyInput[] = []; +for (let i = 0; i < 1437; i++) { + const loggedCallId = uuidv4(); + const loggedCallModelResponseId = uuidv4(); + const template = + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + MODEL_RESPONSE_TEMPLATES[Math.floor(Math.random() * MODEL_RESPONSE_TEMPLATES.length)]!; + const model = template.reqPayload.model; + // choose random time in the last two weeks, with a bias towards the last few days + const startTime = new Date(Date.now() - Math.pow(Math.random(), 2) * 1000 * 60 * 60 * 24 * 14); + // choose random delay anywhere from 2 to 10 seconds later for gpt-4, or 1 to 5 seconds for gpt-3.5 + const delay = + model === "gpt-4" ? 1000 * 2 + Math.random() * 1000 * 8 : 1000 + Math.random() * 1000 * 4; + const endTime = new Date(startTime.getTime() + delay); + loggedCallsToCreate.push({ + id: loggedCallId, + cacheHit: false, + startTime, + organizationId: org.id, + createdAt: startTime, + }); + + const { promptTokenPrice, completionTokenPrice } = + model === "gpt-4" + ? { + promptTokenPrice: 0.00003, + completionTokenPrice: 0.00006, + } + : { + promptTokenPrice: 0.0000015, + completionTokenPrice: 0.000002, + }; + + loggedCallModelResponsesToCreate.push({ + id: loggedCallModelResponseId, + startTime, + endTime, + originalLoggedCallId: loggedCallId, + reqPayload: template.reqPayload, + respPayload: template.respPayload, + respStatus: template.respStatus, + error: template.error, + createdAt: startTime, + cacheKey: hashRequest(org.id, template.reqPayload as JsonValue), + durationMs: endTime.getTime() - startTime.getTime(), + inputTokens: template.inputTokens, + outputTokens: template.outputTokens, + finishReason: template.finishReason, + totalCost: + template.inputTokens * promptTokenPrice + template.outputTokens * completionTokenPrice, + }); + loggedCallsToUpdate.push({ + where: { + id: loggedCallId, + }, + data: { + modelResponseId: loggedCallModelResponseId, + }, + }); + loggedCallTagsToCreate.push({ + loggedCallId, + name: "$model", + value: template.reqPayload.model, + }); +} + +await prisma.$transaction([ + prisma.loggedCall.createMany({ + data: loggedCallsToCreate, + }), + prisma.loggedCallModelResponse.createMany({ + data: loggedCallModelResponsesToCreate, + }), + ...loggedCallsToUpdate.map((update) => prisma.loggedCall.update(update)), + prisma.loggedCallTag.createMany({ + data: loggedCallTagsToCreate, + }), +]); diff --git a/app/src/components/CopiableCode.tsx b/app/src/components/CopiableCode.tsx new file mode 100644 index 0000000..1c4d136 --- /dev/null +++ b/app/src/components/CopiableCode.tsx @@ -0,0 +1,40 @@ +import { HStack, Icon, IconButton, Tooltip, Text } from "@chakra-ui/react"; +import { useState } from "react"; +import { MdContentCopy } from "react-icons/md"; +import { useHandledAsyncCallback } from "~/utils/hooks"; + +const CopiableCode = ({ code }: { code: string }) => { + const [copied, setCopied] = useState(false); + + const [copyToClipboard] = useHandledAsyncCallback(async () => { + await navigator.clipboard.writeText(code); + setCopied(true); + }, [code]); + return ( + + + {code} + + + } + size="xs" + colorScheme="white" + variant="ghost" + onClick={copyToClipboard} + onMouseLeave={() => setCopied(false)} + /> + + + ); +}; + +export default CopiableCode; diff --git a/app/src/components/ExperimentSettingsDrawer/DeleteButton.tsx b/app/src/components/ExperimentSettingsDrawer/DeleteButton.tsx index 6c5f6c7..c5e7a9f 100644 --- a/app/src/components/ExperimentSettingsDrawer/DeleteButton.tsx +++ b/app/src/components/ExperimentSettingsDrawer/DeleteButton.tsx @@ -14,6 +14,7 @@ import { import { useRouter } from "next/router"; import { useRef } from "react"; import { BsTrash } from "react-icons/bs"; +import { useAppStore } from "~/state/store"; import { api } from "~/utils/api"; import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks"; @@ -23,6 +24,8 @@ export const DeleteButton = () => { const utils = api.useContext(); const router = useRouter(); + const closeDrawer = useAppStore((s) => s.closeDrawer); + const { isOpen, onOpen, onClose } = useDisclosure(); const cancelRef = useRef(null); @@ -31,6 +34,8 @@ export const DeleteButton = () => { await mutation.mutateAsync({ id: experiment.data.id }); await utils.experiments.list.invalidate(); await router.push({ pathname: "/experiments" }); + closeDrawer(); + onClose(); }, [mutation, experiment.data?.id, router]); diff --git a/app/src/components/StatsCard.tsx b/app/src/components/StatsCard.tsx new file mode 100644 index 0000000..a44352e --- /dev/null +++ b/app/src/components/StatsCard.tsx @@ -0,0 +1,26 @@ +import { VStack, HStack, type StackProps, Text, Divider } from "@chakra-ui/react"; +import Link, { type LinkProps } from "next/link"; + +const StatsCard = ({ + title, + href, + children, + ...rest +}: { title: string; href: string } & StackProps & LinkProps) => { + return ( + + + + {title} + + + View all + + + + {children} + + ); +}; + +export default StatsCard; diff --git a/app/src/components/dashboard/LoggedCallTable.tsx b/app/src/components/dashboard/LoggedCallTable.tsx new file mode 100644 index 0000000..70aa7a2 --- /dev/null +++ b/app/src/components/dashboard/LoggedCallTable.tsx @@ -0,0 +1,201 @@ +import { + Box, + Card, + CardHeader, + Heading, + Table, + Tbody, + Td, + Th, + Thead, + Tr, + Tooltip, + Collapse, + HStack, + VStack, + IconButton, + useToast, + Icon, + Button, + ButtonGroup, +} from "@chakra-ui/react"; +import dayjs from "dayjs"; +import relativeTime from "dayjs/plugin/relativeTime"; +import { ChevronUpIcon, ChevronDownIcon, CopyIcon } from "lucide-react"; +import { useMemo, useState } from "react"; +import { type RouterOutputs, api } from "~/utils/api"; +import SyntaxHighlighter from "react-syntax-highlighter"; +import { atelierCaveLight } from "react-syntax-highlighter/dist/cjs/styles/hljs"; +import stringify from "json-stringify-pretty-compact"; +import Link from "next/link"; + +dayjs.extend(relativeTime); + +type LoggedCall = RouterOutputs["dashboard"]["loggedCalls"][0]; + +const FormattedJson = ({ json }: { json: any }) => { + const jsonString = stringify(json, { maxLength: 40 }); + const toast = useToast(); + + const copyToClipboard = async (text: string) => { + try { + await navigator.clipboard.writeText(text); + toast({ + title: "Copied to clipboard", + status: "success", + duration: 2000, + }); + } catch (err) { + toast({ + title: "Failed to copy to clipboard", + status: "error", + duration: 2000, + }); + } + }; + + return ( + + + {jsonString} + + } + position="absolute" + top={1} + right={1} + size="xs" + variant="ghost" + onClick={() => void copyToClipboard(jsonString)} + /> + + ); +}; + +function TableRow({ + loggedCall, + isExpanded, + onToggle, +}: { + loggedCall: LoggedCall; + isExpanded: boolean; + onToggle: () => void; +}) { + const isError = loggedCall.modelResponse?.respStatus !== 200; + const timeAgo = dayjs(loggedCall.startTime).fromNow(); + const fullTime = dayjs(loggedCall.startTime).toString(); + + const model = useMemo( + () => loggedCall.tags.find((tag) => tag.name.startsWith("$model"))?.value, + [loggedCall.tags], + ); + + return ( + <> + td": { borderBottom: "none" }, + }} + > + + + + + + + {timeAgo} + + + + {model} + {((loggedCall.modelResponse?.durationMs ?? 0) / 1000).toFixed(2)}s + {loggedCall.modelResponse?.inputTokens} + {loggedCall.modelResponse?.outputTokens} + + {loggedCall.modelResponse?.respStatus ?? "No response"} + + + + + + + + + Input + + + + Output + + + + + + Experiments + + + + + + + > + ); +} + +export default function LoggedCallTable() { + const [expandedRow, setExpandedRow] = useState(null); + const loggedCalls = api.dashboard.loggedCalls.useQuery({}); + + return ( + + + + Logged Calls + + + + + + + Time + Model + Duration + Input tokens + Output tokens + Status + + + + {loggedCalls.data?.map((loggedCall) => { + return ( + { + if (loggedCall.id === expandedRow) { + setExpandedRow(null); + } else { + setExpandedRow(loggedCall.id); + } + }} + /> + ); + })} + + + + ); +} diff --git a/app/src/components/datasets/DatasetCard.tsx b/app/src/components/datasets/DatasetCard.tsx index d5f9344..fc59911 100644 --- a/app/src/components/datasets/DatasetCard.tsx +++ b/app/src/components/datasets/DatasetCard.tsx @@ -15,6 +15,7 @@ import { useRouter } from "next/router"; import { BsPlusSquare } from "react-icons/bs"; import { api } from "~/utils/api"; import { useHandledAsyncCallback } from "~/utils/hooks"; +import { useAppStore } from "~/state/store"; type DatasetData = { name: string; @@ -71,11 +72,12 @@ const CountLabel = ({ label, count }: { label: string; count: number }) => { export const NewDatasetCard = () => { const router = useRouter(); + const selectedOrgId = useAppStore((s) => s.selectedOrgId); const createMutation = api.datasets.create.useMutation(); const [createDataset, isLoading] = useHandledAsyncCallback(async () => { - const newDataset = await createMutation.mutateAsync({ label: "New Dataset" }); + const newDataset = await createMutation.mutateAsync({ organizationId: selectedOrgId ?? "" }); await router.push({ pathname: "/data/[id]", query: { id: newDataset.id } }); - }, [createMutation, router]); + }, [createMutation, router, selectedOrgId]); return ( diff --git a/app/src/components/experiments/ExperimentCard.tsx b/app/src/components/experiments/ExperimentCard.tsx index 9a8f4c3..01853af 100644 --- a/app/src/components/experiments/ExperimentCard.tsx +++ b/app/src/components/experiments/ExperimentCard.tsx @@ -15,6 +15,7 @@ import { useRouter } from "next/router"; import { BsPlusSquare } from "react-icons/bs"; import { api } from "~/utils/api"; import { useHandledAsyncCallback } from "~/utils/hooks"; +import { useAppStore } from "~/state/store"; type ExperimentData = { testScenarioCount: number; @@ -75,11 +76,17 @@ const CountLabel = ({ label, count }: { label: string; count: number }) => { export const NewExperimentCard = () => { const router = useRouter(); + const selectedOrgId = useAppStore((s) => s.selectedOrgId); const createMutation = api.experiments.create.useMutation(); const [createExperiment, isLoading] = useHandledAsyncCallback(async () => { - const newExperiment = await createMutation.mutateAsync({ label: "New Experiment" }); - await router.push({ pathname: "/experiments/[id]", query: { id: newExperiment.id } }); - }, [createMutation, router]); + const newExperiment = await createMutation.mutateAsync({ + organizationId: selectedOrgId ?? "", + }); + await router.push({ + pathname: "/experiments/[id]", + query: { id: newExperiment.id }, + }); + }, [createMutation, router, selectedOrgId]); return ( diff --git a/app/src/components/experiments/ExperimentHeaderButtons/useOnForkButtonPressed.tsx b/app/src/components/experiments/ExperimentHeaderButtons/useOnForkButtonPressed.tsx index 9fbca82..62f99de 100644 --- a/app/src/components/experiments/ExperimentHeaderButtons/useOnForkButtonPressed.tsx +++ b/app/src/components/experiments/ExperimentHeaderButtons/useOnForkButtonPressed.tsx @@ -3,18 +3,23 @@ import { api } from "~/utils/api"; import { useExperiment, useHandledAsyncCallback } from "~/utils/hooks"; import { signIn, useSession } from "next-auth/react"; import { useRouter } from "next/router"; +import { useAppStore } from "~/state/store"; export const useOnForkButtonPressed = () => { const router = useRouter(); const user = useSession().data; const experiment = useExperiment(); + const selectedOrgId = useAppStore((state) => state.selectedOrgId); const forkMutation = api.experiments.fork.useMutation(); const [onFork, isForking] = useHandledAsyncCallback(async () => { - if (!experiment.data?.id) return; - const forkedExperimentId = await forkMutation.mutateAsync({ id: experiment.data.id }); + if (!experiment.data?.id || !selectedOrgId) return; + const forkedExperimentId = await forkMutation.mutateAsync({ + id: experiment.data.id, + organizationId: selectedOrgId, + }); await router.push({ pathname: "/experiments/[id]", query: { id: forkedExperimentId } }); }, [forkMutation, experiment.data?.id, router]); diff --git a/app/src/components/nav/AppShell.tsx b/app/src/components/nav/AppShell.tsx index b6aec3e..7070d1d 100644 --- a/app/src/components/nav/AppShell.tsx +++ b/app/src/components/nav/AppShell.tsx @@ -7,48 +7,22 @@ import { Image, Text, Box, - type BoxProps, Link as ChakraLink, Flex, } from "@chakra-ui/react"; import Head from "next/head"; -import Link, { type LinkProps } from "next/link"; -import { BsGithub, BsPersonCircle } from "react-icons/bs"; -import { useRouter } from "next/router"; -import { type IconType } from "react-icons"; +import Link from "next/link"; +import { BsGearFill, BsGithub, BsPersonCircle } from "react-icons/bs"; +import { IoStatsChartOutline } from "react-icons/io5"; import { RiDatabase2Line, RiFlaskLine } from "react-icons/ri"; import { signIn, useSession } from "next-auth/react"; import UserMenu from "./UserMenu"; import { env } from "~/env.mjs"; +import ProjectMenu from "./ProjectMenu"; +import NavSidebarOption from "./NavSidebarOption"; +import IconLink from "./IconLink"; -type IconLinkProps = BoxProps & LinkProps & { label?: string; icon: IconType; href: string }; - -const IconLink = ({ icon, label, href, color, ...props }: IconLinkProps) => { - const router = useRouter(); - const isActive = href && router.pathname.startsWith(href); - return ( - - - - - {label} - - - - ); -}; - -const Divider = () => ; +const Divider = () => ; const NavSidebar = () => { const user = useSession().data; @@ -56,22 +30,31 @@ const NavSidebar = () => { return ( - + OpenPipe - + + {user != null && ( <> + + + {env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS && ( + + )} {env.NEXT_PUBLIC_SHOW_DATA && ( @@ -79,29 +62,39 @@ const NavSidebar = () => { > )} {user === null && ( - { - signIn("github").catch(console.error); - }} - > - - - Sign In - - + + { + signIn("github").catch(console.error); + }} + > + + + Sign In + + + )} - {user ? ( - - ) : ( - - )} + + + CONFIGURATION + + + + {user && } + { ); }; -export default function AppShell(props: { children: React.ReactNode; title?: string }) { +export default function AppShell({ + children, + title, + requireAuth, +}: { + children: React.ReactNode; + title?: string; + requireAuth?: boolean; +}) { const [vh, setVh] = useState("100vh"); // Default height to prevent flicker on initial render useEffect(() => { @@ -137,14 +138,23 @@ export default function AppShell(props: { children: React.ReactNode; title?: str }; }, []); + const user = useSession().data; + const authLoading = useSession().status === "loading"; + + useEffect(() => { + if (requireAuth && user === null && !authLoading) { + signIn("github").catch(console.error); + } + }, [requireAuth, user, authLoading]); + return ( - {props.title ? `${props.title} | OpenPipe` : "OpenPipe"} + {title ? `${title} | OpenPipe` : "OpenPipe"} - {props.children} + {children} ); diff --git a/app/src/components/nav/IconLink.tsx b/app/src/components/nav/IconLink.tsx new file mode 100644 index 0000000..6613392 --- /dev/null +++ b/app/src/components/nav/IconLink.tsx @@ -0,0 +1,31 @@ +import { Icon, HStack, Text, type BoxProps } from "@chakra-ui/react"; +import Link, { type LinkProps } from "next/link"; +import { type IconType } from "react-icons"; +import NavSidebarOption from "./NavSidebarOption"; + +type IconLinkProps = BoxProps & + LinkProps & { label?: string; icon: IconType; href: string; beta?: boolean }; + +const IconLink = ({ icon, label, href, color, beta, ...props }: IconLinkProps) => { + return ( + + + + + + + {label} + + + {beta && ( + + BETA + + )} + + + + ); +}; + +export default IconLink; diff --git a/app/src/components/nav/NavSidebarOption.tsx b/app/src/components/nav/NavSidebarOption.tsx new file mode 100644 index 0000000..052e4b5 --- /dev/null +++ b/app/src/components/nav/NavSidebarOption.tsx @@ -0,0 +1,27 @@ +import { Box, type BoxProps } from "@chakra-ui/react"; +import { useRouter } from "next/router"; + +const NavSidebarOption = ({ + activeHrefPattern, + disableHoverEffect, + ...props +}: { activeHrefPattern?: string; disableHoverEffect?: boolean } & BoxProps) => { + const router = useRouter(); + const isActive = activeHrefPattern && router.pathname.startsWith(activeHrefPattern); + return ( + + {props.children} + + ); +}; + +export default NavSidebarOption; diff --git a/app/src/components/nav/PageHeaderContainer.tsx b/app/src/components/nav/PageHeaderContainer.tsx new file mode 100644 index 0000000..a682481 --- /dev/null +++ b/app/src/components/nav/PageHeaderContainer.tsx @@ -0,0 +1,19 @@ +import { Flex, type FlexProps } from "@chakra-ui/react"; + +const PageHeaderContainer = (props: FlexProps) => { + return ( + + ); +}; + +export default PageHeaderContainer; diff --git a/app/src/components/nav/ProjectBreadcrumbContents.tsx b/app/src/components/nav/ProjectBreadcrumbContents.tsx new file mode 100644 index 0000000..3d642c7 --- /dev/null +++ b/app/src/components/nav/ProjectBreadcrumbContents.tsx @@ -0,0 +1,28 @@ +import { HStack, Flex, Text } from "@chakra-ui/react"; +import { useSelectedOrg } from "~/utils/hooks"; + +// Have to export only contents here instead of full BreadcrumbItem because Chakra doesn't +// recognize a BreadcrumbItem exported with this component as a valid child of Breadcrumb. +export default function ProjectBreadcrumbContents({ orgName = "" }: { orgName?: string }) { + const { data: selectedOrg } = useSelectedOrg(); + + orgName = orgName || selectedOrg?.name || ""; + + return ( + + + {orgName[0]?.toUpperCase()} + + + {orgName} + + + ); +} diff --git a/app/src/components/nav/ProjectMenu.tsx b/app/src/components/nav/ProjectMenu.tsx new file mode 100644 index 0000000..6e5852e --- /dev/null +++ b/app/src/components/nav/ProjectMenu.tsx @@ -0,0 +1,178 @@ +import { + HStack, + VStack, + Text, + Popover, + PopoverTrigger, + PopoverContent, + Flex, + IconButton, + Icon, + Divider, + Button, + useDisclosure, + Spinner, +} from "@chakra-ui/react"; +import React, { useEffect, useState } from "react"; +import Link from "next/link"; +import { AiFillCaretDown } from "react-icons/ai"; +import { BsGear, BsPlus } from "react-icons/bs"; +import { type Organization } from "@prisma/client"; + +import { useAppStore } from "~/state/store"; +import { api } from "~/utils/api"; +import NavSidebarOption from "./NavSidebarOption"; +import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks"; +import { useRouter } from "next/router"; + +export default function ProjectMenu() { + const router = useRouter(); + const isActive = router.pathname.startsWith("/home"); + const utils = api.useContext(); + + const selectedOrgId = useAppStore((s) => s.selectedOrgId); + const setSelectedOrgId = useAppStore((s) => s.setSelectedOrgId); + + const { data: orgs } = api.organizations.list.useQuery(); + + useEffect(() => { + if (orgs && orgs[0] && (!selectedOrgId || !orgs.find((org) => org.id === selectedOrgId))) { + setSelectedOrgId(orgs[0].id); + } + }, [selectedOrgId, setSelectedOrgId, orgs]); + + const { data: selectedOrg } = useSelectedOrg(); + + const popover = useDisclosure(); + + const createMutation = api.organizations.create.useMutation(); + const [createProject, isLoading] = useHandledAsyncCallback(async () => { + const newOrg = await createMutation.mutateAsync({ name: "New Project" }); + await utils.organizations.list.invalidate(); + setSelectedOrgId(newOrg.id); + await router.push({ pathname: "/project/settings" }); + }, [createMutation, router]); + + return ( + + + PROJECT + + + + + + + {selectedOrg?.name[0]?.toUpperCase()} + + + {selectedOrg?.name} + + + + + + + + PROJECTS + + + + {orgs?.map((org) => ( + + ))} + + + + New project + + + + + + + ); +} + +const ProjectOption = ({ + org, + isActive, + onClose, +}: { + org: Organization; + isActive: boolean; + onClose: () => void; +}) => { + const setSelectedOrgId = useAppStore((s) => s.setSelectedOrgId); + const [gearHovered, setGearHovered] = useState(false); + return ( + { + setSelectedOrgId(org.id); + onClose(); + }} + w="full" + justifyContent="space-between" + bgColor={isActive ? "gray.100" : "transparent"} + _hover={gearHovered ? undefined : { bgColor: "gray.200", textDecoration: "none" }} + p={2} + > + {org.name} + } + variant="ghost" + size="xs" + p={0} + onMouseEnter={() => setGearHovered(true)} + onMouseLeave={() => setGearHovered(false)} + _hover={{ bgColor: isActive ? "gray.300" : "gray.100", transitionDelay: 0 }} + borderRadius={4} + /> + + ); +}; diff --git a/app/src/components/nav/UserMenu.tsx b/app/src/components/nav/UserMenu.tsx index 0f70e31..9d538bc 100644 --- a/app/src/components/nav/UserMenu.tsx +++ b/app/src/components/nav/UserMenu.tsx @@ -8,16 +8,15 @@ import { PopoverTrigger, PopoverContent, Link, - useColorMode, type StackProps, + Box, } from "@chakra-ui/react"; import { type Session } from "next-auth"; import { signOut } from "next-auth/react"; import { BsBoxArrowRight, BsChevronRight, BsPersonCircle } from "react-icons/bs"; +import NavSidebarOption from "./NavSidebarOption"; export default function UserMenu({ user, ...rest }: { user: Session } & StackProps) { - const { colorMode } = useColorMode(); - const profileImage = user.user.image ? ( ) : ( @@ -28,28 +27,28 @@ export default function UserMenu({ user, ...rest }: { user: Session } & StackPro <> - - {profileImage} - - - {user.user.name} - - - {user.user.email} - - - - + + + + {profileImage} + + + {user.user.name} + + + {/* {user.user.email} */} + + + + + + diff --git a/app/src/components/projectSettings/DeleteProjectDialog.tsx b/app/src/components/projectSettings/DeleteProjectDialog.tsx new file mode 100644 index 0000000..1a1d7d3 --- /dev/null +++ b/app/src/components/projectSettings/DeleteProjectDialog.tsx @@ -0,0 +1,89 @@ +import { + Button, + AlertDialog, + AlertDialogBody, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogContent, + AlertDialogOverlay, + Input, + Text, + VStack, + Box, + Spinner, +} from "@chakra-ui/react"; + +import { useRouter } from "next/router"; +import { useRef, useState } from "react"; +import { api } from "~/utils/api"; +import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks"; + +export const DeleteProjectDialog = ({ + isOpen, + onClose, +}: { + isOpen: boolean; + onClose: () => void; +}) => { + const selectedOrg = useSelectedOrg(); + const deleteMutation = api.organizations.delete.useMutation(); + const utils = api.useContext(); + const router = useRouter(); + + const cancelRef = useRef(null); + + const [onDeleteConfirm, isDeleting] = useHandledAsyncCallback(async () => { + if (!selectedOrg.data?.id) return; + await deleteMutation.mutateAsync({ id: selectedOrg.data.id }); + await utils.organizations.list.invalidate(); + await router.push({ pathname: "/experiments" }); + onClose(); + }, [deleteMutation, selectedOrg, router]); + + const [nameToDelete, setNameToDelete] = useState(""); + + return ( + + + + + Delete Project + + + + + + If you delete this project all the associated data and experiments will be deleted + as well. If you are sure that you want to delete this project, please type the name + of the project below. + + + {selectedOrg.data?.name} + + setNameToDelete(e.target.value)} + /> + + + + + + Cancel + + + {isDeleting ? : "Delete"} + + + + + + ); +}; diff --git a/app/src/env.mjs b/app/src/env.mjs index bec0e5e..6990709 100644 --- a/app/src/env.mjs +++ b/app/src/env.mjs @@ -20,6 +20,7 @@ export const env = createEnv({ REPLICATE_API_TOKEN: z.string().default("placeholder"), ANTHROPIC_API_KEY: z.string().default("placeholder"), SENTRY_AUTH_TOKEN: z.string().optional(), + OPENPIPE_API_KEY: z.string().optional(), }, /** @@ -33,6 +34,7 @@ export const env = createEnv({ NEXT_PUBLIC_HOST: z.string().url().default("http://localhost:3000"), NEXT_PUBLIC_SENTRY_DSN: z.string().optional(), NEXT_PUBLIC_SHOW_DATA: z.string().optional(), + NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS: z.string().optional(), }, /** @@ -54,6 +56,8 @@ export const env = createEnv({ ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY, NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN, SENTRY_AUTH_TOKEN: process.env.SENTRY_AUTH_TOKEN, + OPENPIPE_API_KEY: process.env.OPENPIPE_API_KEY, + NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS: process.env.NEXT_PUBLIC_FF_SHOW_LOGGED_CALLS, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. diff --git a/app/src/pages/api/[...trpc].ts b/app/src/pages/api/[...trpc].ts new file mode 100644 index 0000000..4751e6c --- /dev/null +++ b/app/src/pages/api/[...trpc].ts @@ -0,0 +1,22 @@ +import { type NextApiRequest, type NextApiResponse } from "next"; +import cors from "nextjs-cors"; +import { createOpenApiNextHandler } from "trpc-openapi"; +import { createProcedureCache } from "trpc-openapi/dist/adapters/node-http/procedures"; +import { appRouter } from "~/server/api/root.router"; +import { createTRPCContext } from "~/server/api/trpc"; + +const openApiHandler = createOpenApiNextHandler({ + router: appRouter, + createContext: createTRPCContext, +}); + +const cache = createProcedureCache(appRouter); + +const handler = async (req: NextApiRequest, res: NextApiResponse) => { + // Setup CORS + await cors(req, res); + + return openApiHandler(req, res); +}; + +export default handler; diff --git a/app/src/pages/api/openapi.json.ts b/app/src/pages/api/openapi.json.ts new file mode 100644 index 0000000..c02ca8e --- /dev/null +++ b/app/src/pages/api/openapi.json.ts @@ -0,0 +1,16 @@ +import { type NextApiRequest, type NextApiResponse } from "next"; +import { generateOpenApiDocument } from "trpc-openapi"; +import { appRouter } from "~/server/api/root.router"; + +export const openApiDocument = generateOpenApiDocument(appRouter, { + title: "OpenPipe API", + description: "The public API for reporting API calls to OpenPipe", + version: "0.1.0", + baseUrl: "https://app.openpipe.ai/api", +}); +// Respond with our OpenAPI schema +const hander = (req: NextApiRequest, res: NextApiResponse) => { + res.status(200).send(openApiDocument); +}; + +export default hander; diff --git a/app/src/pages/data/[id].tsx b/app/src/pages/data/[id].tsx index 8d06b53..b4a79fb 100644 --- a/app/src/pages/data/[id].tsx +++ b/app/src/pages/data/[id].tsx @@ -18,6 +18,8 @@ import { api } from "~/utils/api"; import { useDataset, useHandledAsyncCallback } from "~/utils/hooks"; import DatasetEntriesTable from "~/components/datasets/DatasetEntriesTable"; import { DatasetHeaderButtons } from "~/components/datasets/DatasetHeaderButtons/DatasetHeaderButtons"; +import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; +import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; export default function Dataset() { const router = useRouter(); @@ -55,15 +57,11 @@ export default function Dataset() { return ( - - + + + + + @@ -89,8 +87,8 @@ export default function Dataset() { - - + + {datasetId && } diff --git a/app/src/pages/data/index.tsx b/app/src/pages/data/index.tsx index b1e581e..fb55389 100644 --- a/app/src/pages/data/index.tsx +++ b/app/src/pages/data/index.tsx @@ -1,83 +1,49 @@ -import { - SimpleGrid, - Icon, - VStack, - Breadcrumb, - BreadcrumbItem, - Flex, - Center, - Text, - Link, - HStack, -} from "@chakra-ui/react"; +import { SimpleGrid, Icon, Breadcrumb, BreadcrumbItem, Flex } from "@chakra-ui/react"; import AppShell from "~/components/nav/AppShell"; -import { api } from "~/utils/api"; -import { signIn, useSession } from "next-auth/react"; import { RiDatabase2Line } from "react-icons/ri"; import { DatasetCard, DatasetCardSkeleton, NewDatasetCard, } from "~/components/datasets/DatasetCard"; +import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; +import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; +import { useDatasets } from "~/utils/hooks"; export default function DatasetsPage() { - const datasets = api.datasets.list.useQuery(); - - const user = useSession().data; - const authLoading = useSession().status === "loading"; - - if (user === null || authLoading) { - return ( - - - {!authLoading && ( - - { - signIn("github").catch(console.error); - }} - textDecor="underline" - > - Sign in - {" "} - to view or create new datasets! - - )} - - - ); - } + const datasets = useDatasets(); return ( - - - - - - - Datasets - - - - - - - {datasets.data && !datasets.isLoading ? ( - datasets?.data?.map((dataset) => ( - - )) - ) : ( - <> - - - - > - )} - - + + + + + + + + + Datasets + + + + + + + {datasets.data && !datasets.isLoading ? ( + datasets?.data?.map((dataset) => ( + + )) + ) : ( + <> + + + + > + )} + ); } diff --git a/app/src/pages/experiments/[id].tsx b/app/src/pages/experiments/[id].tsx index 998b19c..1d89685 100644 --- a/app/src/pages/experiments/[id].tsx +++ b/app/src/pages/experiments/[id].tsx @@ -23,6 +23,8 @@ import { useAppStore } from "~/state/store"; import { useSyncVariantEditor } from "~/state/sync"; import { ExperimentHeaderButtons } from "~/components/experiments/ExperimentHeaderButtons/ExperimentHeaderButtons"; import Head from "next/head"; +import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; +import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; // TODO: import less to fix deployment with server side props // export const getServerSideProps = async (context: GetServerSidePropsContext<{ id: string }>) => { @@ -104,14 +106,11 @@ export default function Experiment() { )} - - + + + + + @@ -143,7 +142,7 @@ export default function Experiment() { - + diff --git a/app/src/pages/experiments/index.tsx b/app/src/pages/experiments/index.tsx index 12b0770..6704f17 100644 --- a/app/src/pages/experiments/index.tsx +++ b/app/src/pages/experiments/index.tsx @@ -1,78 +1,44 @@ -import { - SimpleGrid, - Icon, - VStack, - Breadcrumb, - BreadcrumbItem, - Flex, - Center, - Text, - Link, - HStack, -} from "@chakra-ui/react"; +import { SimpleGrid, Icon, Breadcrumb, BreadcrumbItem, Flex } from "@chakra-ui/react"; import { RiFlaskLine } from "react-icons/ri"; import AppShell from "~/components/nav/AppShell"; -import { api } from "~/utils/api"; import { ExperimentCard, ExperimentCardSkeleton, NewExperimentCard, } from "~/components/experiments/ExperimentCard"; -import { signIn, useSession } from "next-auth/react"; +import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; +import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; +import { useExperiments } from "~/utils/hooks"; export default function ExperimentsPage() { - const experiments = api.experiments.list.useQuery(); - - const user = useSession().data; - const authLoading = useSession().status === "loading"; - - if (user === null || authLoading) { - return ( - - - {!authLoading && ( - - { - signIn("github").catch(console.error); - }} - textDecor="underline" - > - Sign in - {" "} - to view or create new experiments! - - )} - - - ); - } + const experiments = useExperiments(); return ( - - - - - - - Experiments - - - - - - - {experiments.data && !experiments.isLoading ? ( - experiments?.data?.map((exp) => ) - ) : ( - <> - - - - > - )} - - + + + + + + + + + Experiments + + + + + + + {experiments.data && !experiments.isLoading ? ( + experiments?.data?.map((exp) => ) + ) : ( + <> + + + + > + )} + ); } diff --git a/app/src/pages/logged-calls/index.tsx b/app/src/pages/logged-calls/index.tsx new file mode 100644 index 0000000..ca671db --- /dev/null +++ b/app/src/pages/logged-calls/index.tsx @@ -0,0 +1,181 @@ +import { + Heading, + Text, + Stat, + StatLabel, + StatNumber, + VStack, + HStack, + Card, + CardBody, + CardHeader, + Icon, + Table, + Tbody, + Tr, + Td, + Divider, + Breadcrumb, + BreadcrumbItem, +} from "@chakra-ui/react"; +import { + LineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from "recharts"; +import { Ban, DollarSign, Hash } from "lucide-react"; +import { useMemo } from "react"; + +import AppShell from "~/components/nav/AppShell"; +import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; +import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; +import { useSelectedOrg } from "~/utils/hooks"; +import dayjs from "~/utils/dayjs"; +import { api } from "~/utils/api"; +import LoggedCallTable from "~/components/dashboard/LoggedCallTable"; + +export default function LoggedCalls() { + const { data: selectedOrg } = useSelectedOrg(); + + const stats = api.dashboard.stats.useQuery( + { organizationId: selectedOrg?.id ?? "" }, + { enabled: !!selectedOrg }, + ); + + const data = useMemo(() => { + return ( + stats.data?.periods.map(({ period, numQueries, totalCost }) => ({ + period, + Requests: numQueries, + "Total Spent (USD)": parseFloat(totalCost.toString()), + })) || [] + ); + }, [stats.data]); + + return ( + + + + + + + + Logged Calls + + + + + + {selectedOrg?.name} + + + + + + + + Usage Statistics + + + + + + dayjs(str).format("MMM D")} + /> + + + + + + + + + + + + + + + + + Total Spent + + + + ${parseFloat(stats.data?.totals?.totalCost?.toString() ?? "0").toFixed(2)} + + + + + + + + + Total Requests + + + + {stats.data?.totals?.numQueries + ? parseInt(stats.data?.totals?.numQueries.toString())?.toLocaleString() + : undefined} + + + + + + + + + Errors + + + + + + {stats.data?.errors?.map((error) => ( + + + {error.name} ({error.code}) + + + {parseInt(error.count.toString()).toLocaleString()} + + + ))} + + + + + + + + + + + ); +} diff --git a/app/src/pages/project/settings/index.tsx b/app/src/pages/project/settings/index.tsx new file mode 100644 index 0000000..6083e69 --- /dev/null +++ b/app/src/pages/project/settings/index.tsx @@ -0,0 +1,152 @@ +import { + Breadcrumb, + BreadcrumbItem, + Text, + type TextProps, + VStack, + HStack, + Input, + Button, + Divider, + Icon, + useDisclosure, +} from "@chakra-ui/react"; +import { useEffect, useState } from "react"; +import { BsTrash } from "react-icons/bs"; + +import AppShell from "~/components/nav/AppShell"; +import PageHeaderContainer from "~/components/nav/PageHeaderContainer"; +import { api } from "~/utils/api"; +import { useHandledAsyncCallback, useSelectedOrg } from "~/utils/hooks"; +import ProjectBreadcrumbContents from "~/components/nav/ProjectBreadcrumbContents"; +import CopiableCode from "~/components/CopiableCode"; +import { DeleteProjectDialog } from "~/components/projectSettings/DeleteProjectDialog"; + +export default function Settings() { + const utils = api.useContext(); + const { data: selectedOrg } = useSelectedOrg(); + + const apiKey = + selectedOrg?.apiKeys?.length && selectedOrg?.apiKeys[0] ? selectedOrg?.apiKeys[0].apiKey : ""; + + const updateMutation = api.organizations.update.useMutation(); + const [onSaveName] = useHandledAsyncCallback(async () => { + if (name && name !== selectedOrg?.name && selectedOrg?.id) { + await updateMutation.mutateAsync({ + id: selectedOrg.id, + updates: { name }, + }); + await Promise.all([utils.organizations.get.invalidate({ id: selectedOrg.id })]); + } + }, [updateMutation, selectedOrg]); + + const [name, setName] = useState(selectedOrg?.name); + useEffect(() => { + setName(selectedOrg?.name); + }, [selectedOrg?.name]); + + const deleteProjectOpen = useDisclosure(); + + return ( + <> + + + + + + + + Project Settings + + + + + + + Project Settings + + + Configure your project settings. These settings only apply to {selectedOrg?.name}. + + + + + + Display Name + + setName(e.target.value)} + borderColor="gray.300" + /> + + Rename Project + + + + + Project API Key + + Use your project API key to authenticate your requests when sending data to + OpenPipe. You can set this key in your environment variables, or use it directly in + your code. + + + + + {selectedOrg?.personalOrgUserId ? ( + + Personal Project + + This project is {selectedOrg?.personalOrgUser?.name}'s personal project. It cannot + be deleted. + + + ) : ( + + Danger Zone + + Permanently delete your project and all of its data. This action cannot be undone. + + + + Delete {selectedOrg?.name} + + + )} + + + + + > + ); +} + +const Subtitle = (props: TextProps) => ; diff --git a/app/src/server/api/root.router.ts b/app/src/server/api/root.router.ts index 6ec1d8e..702f1ea 100644 --- a/app/src/server/api/root.router.ts +++ b/app/src/server/api/root.router.ts @@ -8,6 +8,9 @@ import { evaluationsRouter } from "./routers/evaluations.router"; import { worldChampsRouter } from "./routers/worldChamps.router"; import { datasetsRouter } from "./routers/datasets.router"; import { datasetEntries } from "./routers/datasetEntries.router"; +import { externalApiRouter } from "./routers/externalApi.router"; +import { organizationsRouter } from "./routers/organizations.router"; +import { dashboardRouter } from "./routers/dashboard.router"; /** * This is the primary router for your server. @@ -24,6 +27,9 @@ export const appRouter = createTRPCRouter({ worldChamps: worldChampsRouter, datasets: datasetsRouter, datasetEntries: datasetEntries, + organizations: organizationsRouter, + dashboard: dashboardRouter, + externalApi: externalApiRouter, }); // export type definition of API diff --git a/app/src/server/api/routers/dashboard.router.ts b/app/src/server/api/routers/dashboard.router.ts new file mode 100644 index 0000000..a97ce6f --- /dev/null +++ b/app/src/server/api/routers/dashboard.router.ts @@ -0,0 +1,118 @@ +import { sql } from "kysely"; +import { z } from "zod"; +import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; +import { kysely, prisma } from "~/server/db"; +import dayjs from "~/utils/dayjs"; + +export const dashboardRouter = createTRPCRouter({ + stats: publicProcedure + .input( + z.object({ + // TODO: actually take startDate into account + startDate: z.string().optional(), + organizationId: z.string(), + }), + ) + .query(async ({ input }) => { + // Return the stats group by hour + const periods = await kysely + .selectFrom("LoggedCall") + .leftJoin( + "LoggedCallModelResponse", + "LoggedCall.id", + "LoggedCallModelResponse.originalLoggedCallId", + ) + .where("organizationId", "=", input.organizationId) + .select(({ fn }) => [ + sql`date_trunc('day', "LoggedCallModelResponse"."startTime")`.as("period"), + sql`count("LoggedCall"."id")::int`.as("numQueries"), + fn.sum(fn.coalesce("LoggedCallModelResponse.totalCost", sql`0`)).as("totalCost"), + ]) + .groupBy("period") + .orderBy("period") + .execute(); + + let originalDataIndex = periods.length - 1; + // *SLAMS DOWN GLASS OF WHISKEY* timezones, amirite? + let dayToMatch = dayjs(input.startDate || new Date()); + // Ensure that the initial date we're matching against is never before the first period + if ( + periods[originalDataIndex] && + dayToMatch.isBefore(periods[originalDataIndex]?.period, "day") + ) { + dayToMatch = dayjs(periods[originalDataIndex]?.period); + } + const backfilledPeriods: typeof periods = []; + + // Backfill from now to 14 days ago or the date of the first logged call, whichever is earlier + while ( + backfilledPeriods.length < 14 || + (periods[0]?.period && !dayToMatch.isBefore(periods[0]?.period, "day")) + ) { + const nextOriginalPeriod = periods[originalDataIndex]; + if (nextOriginalPeriod && dayjs(nextOriginalPeriod?.period).isSame(dayToMatch, "day")) { + backfilledPeriods.unshift(nextOriginalPeriod); + originalDataIndex--; + } else { + backfilledPeriods.unshift({ + period: dayjs(dayToMatch).toDate(), + numQueries: 0, + totalCost: 0, + }); + } + dayToMatch = dayToMatch.subtract(1, "day"); + } + + const totals = await kysely + .selectFrom("LoggedCall") + .leftJoin( + "LoggedCallModelResponse", + "LoggedCall.id", + "LoggedCallModelResponse.originalLoggedCallId", + ) + .where("organizationId", "=", input.organizationId) + .select(({ fn }) => [ + fn.sum(fn.coalesce("LoggedCallModelResponse.totalCost", sql`0`)).as("totalCost"), + fn.count("LoggedCall.id").as("numQueries"), + ]) + .executeTakeFirst(); + + const errors = await kysely + .selectFrom("LoggedCall") + .where("organizationId", "=", input.organizationId) + .leftJoin( + "LoggedCallModelResponse", + "LoggedCall.id", + "LoggedCallModelResponse.originalLoggedCallId", + ) + .select(({ fn }) => [fn.count("LoggedCall.id").as("count"), "respStatus as code"]) + .where("respStatus", ">", 200) + .groupBy("code") + .orderBy("count", "desc") + .execute(); + + const namedErrors = errors.map((e) => { + if (e.code === 429) { + return { ...e, name: "Rate limited" }; + } else if (e.code === 500) { + return { ...e, name: "Internal server error" }; + } else { + return { ...e, name: "Other" }; + } + }); + + return { periods: backfilledPeriods, totals, errors: namedErrors }; + }), + + // TODO useInfiniteQuery + // https://discord.com/channels/966627436387266600/1122258443886153758/1122258443886153758 + loggedCalls: publicProcedure.input(z.object({})).query(async ({ input }) => { + const loggedCalls = await prisma.loggedCall.findMany({ + orderBy: { startTime: "desc" }, + include: { tags: true, modelResponse: true }, + take: 20, + }); + + return loggedCalls; + }), +}); diff --git a/app/src/server/api/routers/datasets.router.ts b/app/src/server/api/routers/datasets.router.ts index b25fde4..80bfd26 100644 --- a/app/src/server/api/routers/datasets.router.ts +++ b/app/src/server/api/routers/datasets.router.ts @@ -3,65 +3,62 @@ import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/ import { prisma } from "~/server/db"; import { requireCanModifyDataset, + requireCanModifyOrganization, requireCanViewDataset, - requireNothing, + requireCanViewOrganization, } from "~/utils/accessControl"; -import userOrg from "~/server/utils/userOrg"; export const datasetsRouter = createTRPCRouter({ - list: protectedProcedure.query(async ({ ctx }) => { - // Anyone can list experiments - requireNothing(ctx); + list: protectedProcedure + .input(z.object({ organizationId: z.string() })) + .query(async ({ input, ctx }) => { + await requireCanViewOrganization(input.organizationId, ctx); - const datasets = await prisma.dataset.findMany({ - where: { - organization: { - organizationUsers: { - some: { userId: ctx.session.user.id }, + const datasets = await prisma.dataset.findMany({ + where: { + organizationId: input.organizationId, + }, + orderBy: { + createdAt: "desc", + }, + include: { + _count: { + select: { datasetEntries: true }, }, }, - }, - orderBy: { - createdAt: "desc", - }, - include: { - _count: { - select: { datasetEntries: true }, - }, - }, - }); + }); - return datasets; - }), + return datasets; + }), get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => { await requireCanViewDataset(input.id, ctx); return await prisma.dataset.findFirstOrThrow({ where: { id: input.id }, + include: { + organization: true, + }, }); }), - create: protectedProcedure.input(z.object({})).mutation(async ({ ctx }) => { - // Anyone can create an experiment - requireNothing(ctx); + create: protectedProcedure + .input(z.object({ organizationId: z.string() })) + .mutation(async ({ input, ctx }) => { + await requireCanModifyOrganization(input.organizationId, ctx); - const numDatasets = await prisma.dataset.count({ - where: { - organization: { - organizationUsers: { - some: { userId: ctx.session.user.id }, - }, + const numDatasets = await prisma.dataset.count({ + where: { + organizationId: input.organizationId, }, - }, - }); + }); - return await prisma.dataset.create({ - data: { - name: `Dataset ${numDatasets + 1}`, - organizationId: (await userOrg(ctx.session.user.id)).id, - }, - }); - }), + return await prisma.dataset.create({ + data: { + name: `Dataset ${numDatasets + 1}`, + organizationId: input.organizationId, + }, + }); + }), update: protectedProcedure .input(z.object({ id: z.string(), updates: z.object({ name: z.string() }) })) diff --git a/app/src/server/api/routers/experiments.router.ts b/app/src/server/api/routers/experiments.router.ts index 2008baa..b69e3d2 100644 --- a/app/src/server/api/routers/experiments.router.ts +++ b/app/src/server/api/routers/experiments.router.ts @@ -8,10 +8,10 @@ import { generateNewCell } from "~/server/utils/generateNewCell"; import { canModifyExperiment, requireCanModifyExperiment, + requireCanModifyOrganization, requireCanViewExperiment, - requireNothing, + requireCanViewOrganization, } from "~/utils/accessControl"; -import userOrg from "~/server/utils/userOrg"; import generateTypes from "~/modelProviders/generateTypes"; import { promptConstructorVersion } from "~/promptConstructor/version"; @@ -43,55 +43,55 @@ export const experimentsRouter = createTRPCRouter({ testScenarioCount, }; }), - list: protectedProcedure.query(async ({ ctx }) => { - // Anyone can list experiments - requireNothing(ctx); + list: protectedProcedure + .input(z.object({ organizationId: z.string() })) + .query(async ({ input, ctx }) => { + await requireCanViewOrganization(input.organizationId, ctx); - const experiments = await prisma.experiment.findMany({ - where: { - organization: { - organizationUsers: { - some: { userId: ctx.session.user.id }, - }, + const experiments = await prisma.experiment.findMany({ + where: { + organizationId: input.organizationId, }, - }, - orderBy: { - sortIndex: "desc", - }, - }); + orderBy: { + sortIndex: "desc", + }, + }); - // TODO: look for cleaner way to do this. Maybe aggregate? - const experimentsWithCounts = await Promise.all( - experiments.map(async (experiment) => { - const visibleTestScenarioCount = await prisma.testScenario.count({ - where: { - experimentId: experiment.id, - visible: true, - }, - }); + // TODO: look for cleaner way to do this. Maybe aggregate? + const experimentsWithCounts = await Promise.all( + experiments.map(async (experiment) => { + const visibleTestScenarioCount = await prisma.testScenario.count({ + where: { + experimentId: experiment.id, + visible: true, + }, + }); - const visiblePromptVariantCount = await prisma.promptVariant.count({ - where: { - experimentId: experiment.id, - visible: true, - }, - }); + const visiblePromptVariantCount = await prisma.promptVariant.count({ + where: { + experimentId: experiment.id, + visible: true, + }, + }); - return { - ...experiment, - testScenarioCount: visibleTestScenarioCount, - promptVariantCount: visiblePromptVariantCount, - }; - }), - ); + return { + ...experiment, + testScenarioCount: visibleTestScenarioCount, + promptVariantCount: visiblePromptVariantCount, + }; + }), + ); - return experimentsWithCounts; - }), + return experimentsWithCounts; + }), get: publicProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => { await requireCanViewExperiment(input.id, ctx); const experiment = await prisma.experiment.findFirstOrThrow({ where: { id: input.id }, + include: { + organization: true, + }, }); const canModify = ctx.session?.user.id @@ -107,222 +107,224 @@ export const experimentsRouter = createTRPCRouter({ }; }), - fork: protectedProcedure.input(z.object({ id: z.string() })).mutation(async ({ input, ctx }) => { - await requireCanViewExperiment(input.id, ctx); + fork: protectedProcedure + .input(z.object({ id: z.string(), organizationId: z.string() })) + .mutation(async ({ input, ctx }) => { + await requireCanViewExperiment(input.id, ctx); + await requireCanModifyOrganization(input.organizationId, ctx); - const [ - existingExp, - existingVariants, - existingScenarios, - existingCells, - evaluations, - templateVariables, - ] = await prisma.$transaction([ - prisma.experiment.findUniqueOrThrow({ - where: { - id: input.id, - }, - }), - prisma.promptVariant.findMany({ - where: { - experimentId: input.id, - visible: true, - }, - }), - prisma.testScenario.findMany({ - where: { - experimentId: input.id, - visible: true, - }, - }), - prisma.scenarioVariantCell.findMany({ - where: { - testScenario: { - visible: true, + const [ + existingExp, + existingVariants, + existingScenarios, + existingCells, + evaluations, + templateVariables, + ] = await prisma.$transaction([ + prisma.experiment.findUniqueOrThrow({ + where: { + id: input.id, }, - promptVariant: { + }), + prisma.promptVariant.findMany({ + where: { experimentId: input.id, visible: true, }, - }, - include: { - modelResponses: { - include: { - outputEvaluations: true, + }), + prisma.testScenario.findMany({ + where: { + experimentId: input.id, + visible: true, + }, + }), + prisma.scenarioVariantCell.findMany({ + where: { + testScenario: { + visible: true, + }, + promptVariant: { + experimentId: input.id, + visible: true, }, }, - }, - }), - prisma.evaluation.findMany({ - where: { - experimentId: input.id, - }, - }), - prisma.templateVariable.findMany({ - where: { - experimentId: input.id, - }, - }), - ]); + include: { + modelResponses: { + include: { + outputEvaluations: true, + }, + }, + }, + }), + prisma.evaluation.findMany({ + where: { + experimentId: input.id, + }, + }), + prisma.templateVariable.findMany({ + where: { + experimentId: input.id, + }, + }), + ]); - const newExperimentId = uuidv4(); + const newExperimentId = uuidv4(); - const existingToNewVariantIds = new Map(); - const variantsToCreate: Prisma.PromptVariantCreateManyInput[] = []; - for (const variant of existingVariants) { - const newVariantId = uuidv4(); - existingToNewVariantIds.set(variant.id, newVariantId); - variantsToCreate.push({ - ...variant, - id: newVariantId, - experimentId: newExperimentId, - }); - } - - const existingToNewScenarioIds = new Map(); - const scenariosToCreate: Prisma.TestScenarioCreateManyInput[] = []; - for (const scenario of existingScenarios) { - const newScenarioId = uuidv4(); - existingToNewScenarioIds.set(scenario.id, newScenarioId); - scenariosToCreate.push({ - ...scenario, - id: newScenarioId, - experimentId: newExperimentId, - variableValues: scenario.variableValues as Prisma.InputJsonValue, - }); - } - - const existingToNewEvaluationIds = new Map(); - const evaluationsToCreate: Prisma.EvaluationCreateManyInput[] = []; - for (const evaluation of evaluations) { - const newEvaluationId = uuidv4(); - existingToNewEvaluationIds.set(evaluation.id, newEvaluationId); - evaluationsToCreate.push({ - ...evaluation, - id: newEvaluationId, - experimentId: newExperimentId, - }); - } - - const cellsToCreate: Prisma.ScenarioVariantCellCreateManyInput[] = []; - const modelResponsesToCreate: Prisma.ModelResponseCreateManyInput[] = []; - const outputEvaluationsToCreate: Prisma.OutputEvaluationCreateManyInput[] = []; - for (const cell of existingCells) { - const newCellId = uuidv4(); - const { modelResponses, ...cellData } = cell; - cellsToCreate.push({ - ...cellData, - id: newCellId, - promptVariantId: existingToNewVariantIds.get(cell.promptVariantId) ?? "", - testScenarioId: existingToNewScenarioIds.get(cell.testScenarioId) ?? "", - prompt: (cell.prompt as Prisma.InputJsonValue) ?? undefined, - }); - for (const modelResponse of modelResponses) { - const newModelResponseId = uuidv4(); - const { outputEvaluations, ...modelResponseData } = modelResponse; - modelResponsesToCreate.push({ - ...modelResponseData, - id: newModelResponseId, - scenarioVariantCellId: newCellId, - output: (modelResponse.output as Prisma.InputJsonValue) ?? undefined, + const existingToNewVariantIds = new Map(); + const variantsToCreate: Prisma.PromptVariantCreateManyInput[] = []; + for (const variant of existingVariants) { + const newVariantId = uuidv4(); + existingToNewVariantIds.set(variant.id, newVariantId); + variantsToCreate.push({ + ...variant, + id: newVariantId, + experimentId: newExperimentId, }); - for (const evaluation of outputEvaluations) { - outputEvaluationsToCreate.push({ - ...evaluation, - id: uuidv4(), - modelResponseId: newModelResponseId, - evaluationId: existingToNewEvaluationIds.get(evaluation.evaluationId) ?? "", + } + + const existingToNewScenarioIds = new Map(); + const scenariosToCreate: Prisma.TestScenarioCreateManyInput[] = []; + for (const scenario of existingScenarios) { + const newScenarioId = uuidv4(); + existingToNewScenarioIds.set(scenario.id, newScenarioId); + scenariosToCreate.push({ + ...scenario, + id: newScenarioId, + experimentId: newExperimentId, + variableValues: scenario.variableValues as Prisma.InputJsonValue, + }); + } + + const existingToNewEvaluationIds = new Map(); + const evaluationsToCreate: Prisma.EvaluationCreateManyInput[] = []; + for (const evaluation of evaluations) { + const newEvaluationId = uuidv4(); + existingToNewEvaluationIds.set(evaluation.id, newEvaluationId); + evaluationsToCreate.push({ + ...evaluation, + id: newEvaluationId, + experimentId: newExperimentId, + }); + } + + const cellsToCreate: Prisma.ScenarioVariantCellCreateManyInput[] = []; + const modelResponsesToCreate: Prisma.ModelResponseCreateManyInput[] = []; + const outputEvaluationsToCreate: Prisma.OutputEvaluationCreateManyInput[] = []; + for (const cell of existingCells) { + const newCellId = uuidv4(); + const { modelResponses, ...cellData } = cell; + cellsToCreate.push({ + ...cellData, + id: newCellId, + promptVariantId: existingToNewVariantIds.get(cell.promptVariantId) ?? "", + testScenarioId: existingToNewScenarioIds.get(cell.testScenarioId) ?? "", + prompt: (cell.prompt as Prisma.InputJsonValue) ?? undefined, + }); + for (const modelResponse of modelResponses) { + const newModelResponseId = uuidv4(); + const { outputEvaluations, ...modelResponseData } = modelResponse; + modelResponsesToCreate.push({ + ...modelResponseData, + id: newModelResponseId, + scenarioVariantCellId: newCellId, + output: (modelResponse.output as Prisma.InputJsonValue) ?? undefined, }); + for (const evaluation of outputEvaluations) { + outputEvaluationsToCreate.push({ + ...evaluation, + id: uuidv4(), + modelResponseId: newModelResponseId, + evaluationId: existingToNewEvaluationIds.get(evaluation.evaluationId) ?? "", + }); + } } } - } - const templateVariablesToCreate: Prisma.TemplateVariableCreateManyInput[] = []; - for (const templateVariable of templateVariables) { - templateVariablesToCreate.push({ - ...templateVariable, - id: uuidv4(), - experimentId: newExperimentId, - }); - } + const templateVariablesToCreate: Prisma.TemplateVariableCreateManyInput[] = []; + for (const templateVariable of templateVariables) { + templateVariablesToCreate.push({ + ...templateVariable, + id: uuidv4(), + experimentId: newExperimentId, + }); + } - const maxSortIndex = - ( - await prisma.experiment.aggregate({ - _max: { - sortIndex: true, + const maxSortIndex = + ( + await prisma.experiment.aggregate({ + _max: { + sortIndex: true, + }, + }) + )._max?.sortIndex ?? 0; + + await prisma.$transaction([ + prisma.experiment.create({ + data: { + id: newExperimentId, + sortIndex: maxSortIndex + 1, + label: `${existingExp.label} (forked)`, + organizationId: input.organizationId, }, - }) - )._max?.sortIndex ?? 0; + }), + prisma.promptVariant.createMany({ + data: variantsToCreate, + }), + prisma.testScenario.createMany({ + data: scenariosToCreate, + }), + prisma.scenarioVariantCell.createMany({ + data: cellsToCreate, + }), + prisma.modelResponse.createMany({ + data: modelResponsesToCreate, + }), + prisma.evaluation.createMany({ + data: evaluationsToCreate, + }), + prisma.outputEvaluation.createMany({ + data: outputEvaluationsToCreate, + }), + prisma.templateVariable.createMany({ + data: templateVariablesToCreate, + }), + ]); - await prisma.$transaction([ - prisma.experiment.create({ + return newExperimentId; + }), + + create: protectedProcedure + .input(z.object({ organizationId: z.string() })) + .mutation(async ({ input, ctx }) => { + await requireCanModifyOrganization(input.organizationId, ctx); + + const maxSortIndex = + ( + await prisma.experiment.aggregate({ + _max: { + sortIndex: true, + }, + where: { organizationId: input.organizationId }, + }) + )._max?.sortIndex ?? 0; + + const exp = await prisma.experiment.create({ data: { - id: newExperimentId, sortIndex: maxSortIndex + 1, - label: `${existingExp.label} (forked)`, - organizationId: (await userOrg(ctx.session.user.id)).id, + label: `Experiment ${maxSortIndex + 1}`, + organizationId: input.organizationId, }, - }), - prisma.promptVariant.createMany({ - data: variantsToCreate, - }), - prisma.testScenario.createMany({ - data: scenariosToCreate, - }), - prisma.scenarioVariantCell.createMany({ - data: cellsToCreate, - }), - prisma.modelResponse.createMany({ - data: modelResponsesToCreate, - }), - prisma.evaluation.createMany({ - data: evaluationsToCreate, - }), - prisma.outputEvaluation.createMany({ - data: outputEvaluationsToCreate, - }), - prisma.templateVariable.createMany({ - data: templateVariablesToCreate, - }), - ]); + }); - return newExperimentId; - }), - - create: protectedProcedure.input(z.object({})).mutation(async ({ ctx }) => { - // Anyone can create an experiment - requireNothing(ctx); - - const organizationId = (await userOrg(ctx.session.user.id)).id; - - const maxSortIndex = - ( - await prisma.experiment.aggregate({ - _max: { - sortIndex: true, - }, - where: { organizationId }, - }) - )._max?.sortIndex ?? 0; - - const exp = await prisma.experiment.create({ - data: { - sortIndex: maxSortIndex + 1, - label: `Experiment ${maxSortIndex + 1}`, - organizationId, - }, - }); - - const [variant, _, scenario1, scenario2, scenario3] = await prisma.$transaction([ - prisma.promptVariant.create({ - data: { - experimentId: exp.id, - label: "Prompt Variant 1", - sortIndex: 0, - // The interpolated $ is necessary until dedent incorporates - // https://github.com/dmnd/dedent/pull/46 - promptConstructor: dedent` + const [variant, _, scenario1, scenario2, scenario3] = await prisma.$transaction([ + prisma.promptVariant.create({ + data: { + experimentId: exp.id, + label: "Prompt Variant 1", + sortIndex: 0, + // The interpolated $ is necessary until dedent incorporates + // https://github.com/dmnd/dedent/pull/46 + promptConstructor: dedent` /** * Use Javascript to define an OpenAI chat completion * (https://platform.openai.com/docs/api-reference/chat/create). @@ -341,49 +343,49 @@ export const experimentsRouter = createTRPCRouter({ }, ], });`, - model: "gpt-3.5-turbo-0613", - modelProvider: "openai/ChatCompletion", - promptConstructorVersion, - }, - }), - prisma.templateVariable.create({ - data: { - experimentId: exp.id, - label: "language", - }, - }), - prisma.testScenario.create({ - data: { - experimentId: exp.id, - variableValues: { - language: "English", + model: "gpt-3.5-turbo-0613", + modelProvider: "openai/ChatCompletion", + promptConstructorVersion, }, - }, - }), - prisma.testScenario.create({ - data: { - experimentId: exp.id, - variableValues: { - language: "Spanish", + }), + prisma.templateVariable.create({ + data: { + experimentId: exp.id, + label: "language", }, - }, - }), - prisma.testScenario.create({ - data: { - experimentId: exp.id, - variableValues: { - language: "German", + }), + prisma.testScenario.create({ + data: { + experimentId: exp.id, + variableValues: { + language: "English", + }, }, - }, - }), - ]); + }), + prisma.testScenario.create({ + data: { + experimentId: exp.id, + variableValues: { + language: "Spanish", + }, + }, + }), + prisma.testScenario.create({ + data: { + experimentId: exp.id, + variableValues: { + language: "German", + }, + }, + }), + ]); - await generateNewCell(variant.id, scenario1.id); - await generateNewCell(variant.id, scenario2.id); - await generateNewCell(variant.id, scenario3.id); + await generateNewCell(variant.id, scenario1.id); + await generateNewCell(variant.id, scenario2.id); + await generateNewCell(variant.id, scenario3.id); - return exp; - }), + return exp; + }), update: protectedProcedure .input(z.object({ id: z.string(), updates: z.object({ label: z.string() }) })) diff --git a/app/src/server/api/routers/externalApi.router.ts b/app/src/server/api/routers/externalApi.router.ts new file mode 100644 index 0000000..08619bf --- /dev/null +++ b/app/src/server/api/routers/externalApi.router.ts @@ -0,0 +1,205 @@ +import { type Prisma } from "@prisma/client"; +import { type JsonValue } from "type-fest"; +import { z } from "zod"; +import { v4 as uuidv4 } from "uuid"; +import { TRPCError } from "@trpc/server"; + +import { createTRPCRouter, publicProcedure } from "~/server/api/trpc"; +import { prisma } from "~/server/db"; +import { hashRequest } from "~/server/utils/hashObject"; + +const reqValidator = z.object({ + model: z.string(), + messages: z.array(z.any()), +}); + +const respValidator = z.object({ + id: z.string(), + model: z.string(), + usage: z.object({ + total_tokens: z.number(), + prompt_tokens: z.number(), + completion_tokens: z.number(), + }), + choices: z.array( + z.object({ + finish_reason: z.string(), + }), + ), +}); + +export const externalApiRouter = createTRPCRouter({ + checkCache: publicProcedure + .meta({ + openapi: { + method: "POST", + path: "/v1/check-cache", + description: "Check if a prompt is cached", + }, + }) + .input( + z.object({ + startTime: z.number().describe("Unix timestamp in milliseconds"), + reqPayload: z.unknown().describe("JSON-encoded request payload"), + tags: z + .record(z.string()) + .optional() + .describe( + 'Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" }', + ), + }), + ) + .output( + z.object({ + respPayload: z.unknown().optional().describe("JSON-encoded response payload"), + }), + ) + .mutation(async ({ input, ctx }) => { + const apiKey = ctx.apiKey; + if (!apiKey) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + const key = await prisma.apiKey.findUnique({ + where: { apiKey }, + }); + if (!key) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + const reqPayload = await reqValidator.spa(input.reqPayload); + const cacheKey = hashRequest(key.organizationId, reqPayload as JsonValue); + + const existingResponse = await prisma.loggedCallModelResponse.findFirst({ + where: { + cacheKey, + }, + include: { + originalLoggedCall: true, + }, + orderBy: { + startTime: "desc", + }, + }); + + if (!existingResponse) return { respPayload: null }; + + await prisma.loggedCall.create({ + data: { + organizationId: key.organizationId, + startTime: new Date(input.startTime), + cacheHit: true, + modelResponseId: existingResponse.id, + }, + }); + + return { + respPayload: existingResponse.respPayload, + }; + }), + + report: publicProcedure + .meta({ + openapi: { + method: "POST", + path: "/v1/report", + description: "Report an API call", + }, + }) + .input( + z.object({ + startTime: z.number().describe("Unix timestamp in milliseconds"), + endTime: z.number().describe("Unix timestamp in milliseconds"), + reqPayload: z.unknown().describe("JSON-encoded request payload"), + respPayload: z.unknown().optional().describe("JSON-encoded response payload"), + respStatus: z.number().optional().describe("HTTP status code of response"), + error: z.string().optional().describe("User-friendly error message"), + tags: z + .record(z.string()) + .optional() + .describe( + 'Extra tags to attach to the call for filtering. Eg { "userId": "123", "promptId": "populate-title" }', + ), + }), + ) + .output(z.void()) + .mutation(async ({ input, ctx }) => { + const apiKey = ctx.apiKey; + if (!apiKey) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + const key = await prisma.apiKey.findUnique({ + where: { apiKey }, + }); + if (!key) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + const reqPayload = await reqValidator.spa(input.reqPayload); + const respPayload = await respValidator.spa(input.respPayload); + + const requestHash = hashRequest(key.organizationId, reqPayload as JsonValue); + + const newLoggedCallId = uuidv4(); + const newModelResponseId = uuidv4(); + + const usage = respPayload.success ? respPayload.data.usage : undefined; + + await prisma.$transaction([ + prisma.loggedCall.create({ + data: { + id: newLoggedCallId, + organizationId: key.organizationId, + startTime: new Date(input.startTime), + cacheHit: false, + }, + }), + prisma.loggedCallModelResponse.create({ + data: { + id: newModelResponseId, + originalLoggedCallId: newLoggedCallId, + startTime: new Date(input.startTime), + endTime: new Date(input.endTime), + reqPayload: input.reqPayload as Prisma.InputJsonValue, + respPayload: input.respPayload as Prisma.InputJsonValue, + respStatus: input.respStatus, + error: input.error, + durationMs: input.endTime - input.startTime, + ...(respPayload.success + ? { + cacheKey: requestHash, + inputTokens: usage ? usage.prompt_tokens : undefined, + outputTokens: usage ? usage.completion_tokens : undefined, + } + : null), + }, + }), + // Avoid foreign key constraint error by updating the logged call after the model response is created + prisma.loggedCall.update({ + where: { + id: newLoggedCallId, + }, + data: { + modelResponseId: newModelResponseId, + }, + }), + ]); + + if (input.tags) { + const tagsToCreate = Object.entries(input.tags).map(([name, value]) => ({ + loggedCallId: newLoggedCallId, + // sanitize tags + name: name.replaceAll(/[^a-zA-Z0-9_]/g, "_"), + value, + })); + + if (reqPayload.success) { + tagsToCreate.push({ + loggedCallId: newLoggedCallId, + name: "$model", + value: reqPayload.data.model, + }); + } + await prisma.loggedCallTag.createMany({ + data: tagsToCreate, + }); + } + }), +}); diff --git a/app/src/server/api/routers/organizations.router.ts b/app/src/server/api/routers/organizations.router.ts new file mode 100644 index 0000000..141aab2 --- /dev/null +++ b/app/src/server/api/routers/organizations.router.ts @@ -0,0 +1,128 @@ +import { TRPCError } from "@trpc/server"; +import { v4 as uuidv4 } from "uuid"; +import { z } from "zod"; + +import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; +import { prisma } from "~/server/db"; +import { generateApiKey } from "~/server/utils/generateApiKey"; +import userOrg from "~/server/utils/userOrg"; +import { + requireCanModifyOrganization, + requireCanViewOrganization, + requireIsOrgAdmin, + requireNothing, +} from "~/utils/accessControl"; + +export const organizationsRouter = createTRPCRouter({ + list: protectedProcedure.query(async ({ ctx }) => { + const userId = ctx.session.user.id; + requireNothing(ctx); + + if (!userId) { + return null; + } + + const organizations = await prisma.organization.findMany({ + where: { + organizationUsers: { + some: { userId: ctx.session.user.id }, + }, + }, + orderBy: { + createdAt: "asc", + }, + }); + + if (!organizations.length) { + // TODO: We should move this to a separate endpoint that is called on sign up + const personalOrg = await userOrg(userId); + organizations.push(personalOrg); + } + + return organizations; + }), + get: protectedProcedure.input(z.object({ id: z.string() })).query(async ({ input, ctx }) => { + await requireCanViewOrganization(input.id, ctx); + const [org, userRole] = await prisma.$transaction([ + prisma.organization.findUnique({ + where: { + id: input.id, + }, + include: { + apiKeys: true, + personalOrgUser: true, + }, + }), + prisma.organizationUser.findFirst({ + where: { + userId: ctx.session.user.id, + organizationId: input.id, + role: { + in: ["ADMIN", "MEMBER"], + }, + }, + }), + ]); + + if (!org) { + throw new TRPCError({ code: "NOT_FOUND" }); + } + + return { + ...org, + role: userRole?.role ?? null, + }; + }), + update: protectedProcedure + .input(z.object({ id: z.string(), updates: z.object({ name: z.string() }) })) + .mutation(async ({ input, ctx }) => { + await requireCanModifyOrganization(input.id, ctx); + return await prisma.organization.update({ + where: { + id: input.id, + }, + data: { + name: input.updates.name, + }, + }); + }), + create: protectedProcedure + .input(z.object({ name: z.string() })) + .mutation(async ({ input, ctx }) => { + requireNothing(ctx); + const newOrgId = uuidv4(); + const [newOrg] = await prisma.$transaction([ + prisma.organization.create({ + data: { + id: newOrgId, + name: input.name, + }, + }), + prisma.organizationUser.create({ + data: { + userId: ctx.session.user.id, + organizationId: newOrgId, + role: "ADMIN", + }, + }), + prisma.apiKey.create({ + data: { + name: "Default API Key", + organizationId: newOrgId, + apiKey: generateApiKey(), + }, + }), + ]); + return newOrg; + }), + delete: protectedProcedure + .input(z.object({ id: z.string() })) + .mutation(async ({ input, ctx }) => { + await requireIsOrgAdmin(input.id, ctx); + return await prisma.organization.delete({ + where: { + id: input.id, + }, + }); + }), +}); diff --git a/app/src/server/api/trpc.ts b/app/src/server/api/trpc.ts index dc67814..fc1f1e3 100644 --- a/app/src/server/api/trpc.ts +++ b/app/src/server/api/trpc.ts @@ -11,6 +11,7 @@ import { initTRPC, TRPCError } from "@trpc/server"; import { type CreateNextContextOptions } from "@trpc/server/adapters/next"; import { type Session } from "next-auth"; import superjson from "superjson"; +import { type OpenApiMeta } from "trpc-openapi"; import { ZodError } from "zod"; import { getServerAuthSession } from "~/server/auth"; import { prisma } from "~/server/db"; @@ -26,6 +27,7 @@ import { capturePath } from "~/utils/analytics/serverAnalytics"; type CreateContextOptions = { session: Session | null; + apiKey: string | null; }; // eslint-disable-next-line @typescript-eslint/no-empty-function @@ -44,6 +46,7 @@ const noOp = () => {}; export const createInnerTRPCContext = (opts: CreateContextOptions) => { return { session: opts.session, + apiKey: opts.apiKey, prisma, markAccessControlRun: noOp, }; @@ -61,8 +64,11 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => { // Get the session from the server using the getServerSession wrapper function const session = await getServerAuthSession({ req, res }); + const apiKey = req.headers["x-openpipe-api-key"] as string | null; + return createInnerTRPCContext({ session, + apiKey, }); }; @@ -76,18 +82,21 @@ export const createTRPCContext = async (opts: CreateNextContextOptions) => { export type TRPCContext = Awaited>; -const t = initTRPC.context().create({ - transformer: superjson, - errorFormatter({ shape, error }) { - return { - ...shape, - data: { - ...shape.data, - zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, - }, - }; - }, -}); +const t = initTRPC + .context() + .meta() + .create({ + transformer: superjson, + errorFormatter({ shape, error }) { + return { + ...shape, + data: { + ...shape.data, + zodError: error.cause instanceof ZodError ? error.cause.flatten() : null, + }, + }; + }, + }); /** * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) diff --git a/app/src/server/auth.ts b/app/src/server/auth.ts index f2b779c..f1644a4 100644 --- a/app/src/server/auth.ts +++ b/app/src/server/auth.ts @@ -2,9 +2,16 @@ import { PrismaAdapter } from "@next-auth/prisma-adapter"; import { type GetServerSidePropsContext } from "next"; import { getServerSession, type NextAuthOptions, type DefaultSession } from "next-auth"; import { prisma } from "~/server/db"; -import GitHubProvider from "next-auth/providers/github"; +import GitHubModule from "next-auth/providers/github"; import { env } from "~/env.mjs"; +// The client codegen script doesn't properly read the default export +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const untypedGitHubModule = GitHubModule as unknown as any; +const GitHubProvider: typeof GitHubModule = untypedGitHubModule.default + ? untypedGitHubModule.default + : untypedGitHubModule; + /** * Module augmentation for `next-auth` types. Allows us to add custom properties to the `session` * object and keep type safety. diff --git a/app/src/server/db.ts b/app/src/server/db.ts index 1cbc4cf..57c433e 100644 --- a/app/src/server/db.ts +++ b/app/src/server/db.ts @@ -1,6 +1,61 @@ -import { PrismaClient } from "@prisma/client"; +import { + type Experiment, + type PromptVariant, + type TestScenario, + type TemplateVariable, + type ScenarioVariantCell, + type ModelResponse, + type Evaluation, + type OutputEvaluation, + type Dataset, + type DatasetEntry, + type Organization, + type OrganizationUser, + type WorldChampEntrant, + type LoggedCall, + type LoggedCallModelResponse, + type LoggedCallTag, + type ApiKey, + type Account, + type Session, + type User, + type VerificationToken, + PrismaClient, +} from "@prisma/client"; +import { Kysely, PostgresDialect } from "kysely"; +// TODO: Revert to normal import when our tsconfig.json is fixed +// import { Pool } from "pg"; +import PGModule from "pg"; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +const UntypedPool = PGModule.Pool as any; +const Pool = (UntypedPool.default ? UntypedPool.default : UntypedPool) as typeof PGModule.Pool; + import { env } from "~/env.mjs"; +interface DB { + Experiment: Experiment; + PromptVariant: PromptVariant; + TestScenario: TestScenario; + TemplateVariable: TemplateVariable; + ScenarioVariantCell: ScenarioVariantCell; + ModelResponse: ModelResponse; + Evaluation: Evaluation; + OutputEvaluation: OutputEvaluation; + Dataset: Dataset; + DatasetEntry: DatasetEntry; + Organization: Organization; + OrganizationUser: OrganizationUser; + WorldChampEntrant: WorldChampEntrant; + LoggedCall: LoggedCall; + LoggedCallModelResponse: LoggedCallModelResponse; + LoggedCallTag: LoggedCallTag; + ApiKey: ApiKey; + Account: Account; + Session: Session; + User: User; + VerificationToken: VerificationToken; +} + const globalForPrisma = globalThis as unknown as { prisma: PrismaClient | undefined; }; @@ -14,4 +69,12 @@ export const prisma = : ["error"], }); +export const kysely = new Kysely({ + dialect: new PostgresDialect({ + pool: new Pool({ + connectionString: env.DATABASE_URL, + }), + }), +}); + if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; diff --git a/app/src/server/scripts/backfillApiKeys.ts b/app/src/server/scripts/backfillApiKeys.ts new file mode 100644 index 0000000..b7768f6 --- /dev/null +++ b/app/src/server/scripts/backfillApiKeys.ts @@ -0,0 +1,33 @@ +import { type Prisma } from "@prisma/client"; +import { prisma } from "~/server/db"; +import { generateApiKey } from "~/server/utils/generateApiKey"; + +console.log("backfilling api keys"); + +const organizations = await prisma.organization.findMany({ + include: { + apiKeys: true, + }, +}); + +console.log(`found ${organizations.length} organizations`); + +const apiKeysToCreate: Prisma.ApiKeyCreateManyInput[] = []; + +for (const org of organizations) { + if (!org.apiKeys.length) { + apiKeysToCreate.push({ + name: "Default API Key", + organizationId: org.id, + apiKey: generateApiKey(), + }); + } +} + +console.log(`creating ${apiKeysToCreate.length} api keys`); + +await prisma.apiKey.createMany({ + data: apiKeysToCreate, +}); + +console.log("done"); diff --git a/app/src/server/scripts/client-codegen.ts b/app/src/server/scripts/client-codegen.ts new file mode 100644 index 0000000..78d1262 --- /dev/null +++ b/app/src/server/scripts/client-codegen.ts @@ -0,0 +1,32 @@ +import "dotenv/config"; +import { openApiDocument } from "~/pages/api/openapi.json"; +import fs from "fs"; +import path from "path"; +import { execSync } from "child_process"; + +console.log("Exporting public OpenAPI schema to client-libs/schema.json"); + +const scriptPath = import.meta.url.replace("file://", ""); +const clientLibsPath = path.join(path.dirname(scriptPath), "../../../../client-libs"); + +const schemaPath = path.join(clientLibsPath, "schema.json"); + +console.log("Exporting schema"); +fs.writeFileSync(schemaPath, JSON.stringify(openApiDocument, null, 2), "utf-8"); + +console.log("Generating Typescript client"); + +const tsClientPath = path.join(clientLibsPath, "typescript/codegen"); + +fs.rmSync(tsClientPath, { recursive: true, force: true }); + +execSync( + `pnpm dlx @openapitools/openapi-generator-cli generate -i "${schemaPath}" -g typescript-axios -o "${tsClientPath}"`, + { + stdio: "inherit", + }, +); + +console.log("Done!"); + +process.exit(0); diff --git a/app/src/server/scripts/test-queries.ts b/app/src/server/scripts/test-queries.ts new file mode 100644 index 0000000..aa80bd7 --- /dev/null +++ b/app/src/server/scripts/test-queries.ts @@ -0,0 +1,63 @@ +import dayjs from "dayjs"; +import { prisma } from "../db"; + +const projectId = "1234"; + +// Find all calls in the last 24 hours +const responses = await prisma.loggedCall.findMany({ + where: { + organizationId: projectId, + startTime: { + gt: dayjs() + .subtract(24 * 3600) + .toDate(), + }, + }, + include: { + modelResponse: true, + }, + orderBy: { + startTime: "desc", + }, +}); + +// Find all calls in the last 24 hours with promptId 'hello-world' +const helloWorld = await prisma.loggedCall.findMany({ + where: { + organizationId: projectId, + startTime: { + gt: dayjs() + .subtract(24 * 3600) + .toDate(), + }, + tags: { + some: { + name: "promptId", + value: "hello-world", + }, + }, + }, + include: { + modelResponse: true, + }, + orderBy: { + startTime: "desc", + }, +}); + +// Total spent on OpenAI in the last month +const totalSpent = await prisma.loggedCallModelResponse.aggregate({ + _sum: { + totalCost: true, + }, + where: { + originalLoggedCall: { + organizationId: projectId, + }, + startTime: { + gt: dayjs() + .subtract(30 * 24 * 3600) + .toDate(), + }, + }, +}); diff --git a/app/src/server/tasks/queryModel.task.ts b/app/src/server/tasks/queryModel.task.ts index 6f9d08b..d7a5dc8 100644 --- a/app/src/server/tasks/queryModel.task.ts +++ b/app/src/server/tasks/queryModel.task.ts @@ -1,10 +1,10 @@ import { type Prisma } from "@prisma/client"; -import { type JsonObject } from "type-fest"; +import { type JsonValue, type JsonObject } from "type-fest"; import modelProviders from "~/modelProviders/modelProviders"; import { prisma } from "~/server/db"; import { wsConnection } from "~/utils/wsConnection"; import { runEvalsForOutput } from "../utils/evaluations"; -import hashPrompt from "../utils/hashPrompt"; +import hashObject from "../utils/hashObject"; import defineTask from "./defineTask"; import parsePromptConstructor from "~/promptConstructor/parse"; @@ -99,7 +99,7 @@ export const queryModel = defineTask("queryModel", async (task) = } : null; - const inputHash = hashPrompt(prompt); + const inputHash = hashObject(prompt as JsonValue); let modelResponse = await prisma.modelResponse.create({ data: { diff --git a/app/src/server/tasks/worker.ts b/app/src/server/tasks/worker.ts index 74be92f..c49b960 100644 --- a/app/src/server/tasks/worker.ts +++ b/app/src/server/tasks/worker.ts @@ -17,7 +17,7 @@ const taskList = registeredTasks.reduce((acc, task) => { // Run a worker to execute jobs: const runner = await run({ connectionString: env.DATABASE_URL, - concurrency: 50, + concurrency: 10, // Install signal handlers for graceful shutdown on SIGINT, SIGTERM, etc noHandleSignals: false, pollInterval: 1000, diff --git a/app/src/server/utils/generateApiKey.ts b/app/src/server/utils/generateApiKey.ts new file mode 100644 index 0000000..25ce442 --- /dev/null +++ b/app/src/server/utils/generateApiKey.ts @@ -0,0 +1,5 @@ +import cryptoRandomString from "crypto-random-string"; + +const KEY_LENGTH = 42; + +export const generateApiKey = () => `opc_${cryptoRandomString({ length: KEY_LENGTH })}`; diff --git a/app/src/server/utils/generateNewCell.ts b/app/src/server/utils/generateNewCell.ts index 2f79693..858781e 100644 --- a/app/src/server/utils/generateNewCell.ts +++ b/app/src/server/utils/generateNewCell.ts @@ -1,7 +1,7 @@ import { Prisma } from "@prisma/client"; import { prisma } from "../db"; import { type JsonObject } from "type-fest"; -import hashPrompt from "./hashPrompt"; +import hashObject from "./hashObject"; import { omit } from "lodash-es"; import { queueQueryModel } from "../tasks/queryModel.task"; import parsePromptConstructor from "~/promptConstructor/parse"; @@ -57,7 +57,7 @@ export const generateNewCell = async ( return; } - const inputHash = hashPrompt(parsedConstructFn); + const inputHash = hashObject(parsedConstructFn); cell = await prisma.scenarioVariantCell.create({ data: { diff --git a/app/src/server/utils/hashPrompt.ts b/app/src/server/utils/hashObject.ts similarity index 75% rename from app/src/server/utils/hashPrompt.ts rename to app/src/server/utils/hashObject.ts index 7b1f347..562e7c9 100644 --- a/app/src/server/utils/hashPrompt.ts +++ b/app/src/server/utils/hashObject.ts @@ -1,6 +1,5 @@ import crypto from "crypto"; import { type JsonValue } from "type-fest"; -import { ParsedPromptConstructor } from "~/promptConstructor/parse"; function sortKeys(obj: JsonValue): JsonValue { if (typeof obj !== "object" || obj === null) { @@ -25,9 +24,17 @@ function sortKeys(obj: JsonValue): JsonValue { return sortedObj; } -export default function hashPrompt(prompt: ParsedPromptConstructor): string { +export function hashRequest(organizationId: string, reqPayload: JsonValue): string { + const obj = { + organizationId, + reqPayload, + }; + return hashObject(obj); +} + +export default function hashObject(obj: JsonValue): string { // Sort object keys recursively - const sortedObj = sortKeys(prompt as unknown as JsonValue); + const sortedObj = sortKeys(obj); // Convert to JSON and hash it const str = JSON.stringify(sortedObj); diff --git a/app/src/server/utils/openai.ts b/app/src/server/utils/openai.ts index e345456..64ac3d8 100644 --- a/app/src/server/utils/openai.ts +++ b/app/src/server/utils/openai.ts @@ -1,6 +1,13 @@ import { env } from "~/env.mjs"; -import OpenAI from "openai"; +import { default as OriginalOpenAI } from "openai"; +// import { OpenAI } from "openpipe"; + +const openAIConfig = { apiKey: env.OPENAI_API_KEY ?? "dummy-key" }; // Set a dummy key so it doesn't fail at build time -export const openai = new OpenAI({ apiKey: env.OPENAI_API_KEY ?? "dummy-key" }); +// export const openai = env.OPENPIPE_API_KEY +// ? new OpenAI.OpenAI(openAIConfig) +// : new OriginalOpenAI(openAIConfig); + +export const openai = new OriginalOpenAI(openAIConfig); diff --git a/app/src/server/utils/userOrg.ts b/app/src/server/utils/userOrg.ts index a88d8f7..cbbecb1 100644 --- a/app/src/server/utils/userOrg.ts +++ b/app/src/server/utils/userOrg.ts @@ -1,4 +1,5 @@ import { prisma } from "~/server/db"; +import { generateApiKey } from "./generateApiKey"; export default async function userOrg(userId: string) { return await prisma.organization.upsert({ @@ -14,6 +15,14 @@ export default async function userOrg(userId: string) { role: "ADMIN", }, }, + apiKeys: { + create: [ + { + name: "Default API Key", + apiKey: generateApiKey(), + }, + ], + }, }, }); } diff --git a/app/src/state/store.ts b/app/src/state/store.ts index ba8a506..5584b21 100644 --- a/app/src/state/store.ts +++ b/app/src/state/store.ts @@ -14,6 +14,8 @@ export type State = { api: APIClient | null; setApi: (api: APIClient) => void; sharedVariantEditor: SharedVariantEditorSlice; + selectedOrgId: string | null; + setSelectedOrgId: (orgId: string) => void; }; export type SliceCreator = StateCreator; @@ -39,6 +41,11 @@ const useBaseStore = create( state.drawerOpen = false; }), sharedVariantEditor: createVariantEditorSlice(set, get, ...rest), + selectedOrgId: null, + setSelectedOrgId: (orgId: string) => + set((state) => { + state.selectedOrgId = orgId; + }), })), ); diff --git a/app/src/theme/ChakraThemeProvider.tsx b/app/src/theme/ChakraThemeProvider.tsx index 0c3f247..a8b7ff7 100644 --- a/app/src/theme/ChakraThemeProvider.tsx +++ b/app/src/theme/ChakraThemeProvider.tsx @@ -1,6 +1,5 @@ -import { extendTheme } from "@chakra-ui/react"; +import { extendTheme, defineStyleConfig, ChakraProvider } from "@chakra-ui/react"; import "@fontsource/inconsolata"; -import { ChakraProvider } from "@chakra-ui/react"; import { modalAnatomy } from "@chakra-ui/anatomy"; import { createMultiStyleConfigHelpers } from "@chakra-ui/styled-system"; @@ -18,6 +17,13 @@ const modalTheme = defineMultiStyleConfig({ }), }); +const Divider = defineStyleConfig({ + baseStyle: { + borderColor: "gray.300", + backgroundColor: "gray.300", + }, +}); + const theme = extendTheme({ styles: { global: (props: { colorMode: "dark" | "light" }) => ({ @@ -53,6 +59,7 @@ const theme = extendTheme({ }, }, Modal: modalTheme, + Divider, }, }); diff --git a/app/src/utils/accessControl.ts b/app/src/utils/accessControl.ts index 5fc1fd4..3985e1f 100644 --- a/app/src/utils/accessControl.ts +++ b/app/src/utils/accessControl.ts @@ -16,6 +16,68 @@ export const requireNothing = (ctx: TRPCContext) => { ctx.markAccessControlRun(); }; +export const requireIsOrgAdmin = async (organizationId: string, ctx: TRPCContext) => { + const userId = ctx.session?.user.id; + if (!userId) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + const isAdmin = await prisma.organizationUser.findFirst({ + where: { + userId, + organizationId, + role: "ADMIN", + }, + }); + + if (!isAdmin) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + ctx.markAccessControlRun(); +}; + +export const requireCanViewOrganization = async (organizationId: string, ctx: TRPCContext) => { + const userId = ctx.session?.user.id; + if (!userId) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + const canView = await prisma.organizationUser.findFirst({ + where: { + userId, + organizationId, + }, + }); + + if (!canView) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + ctx.markAccessControlRun(); +}; + +export const requireCanModifyOrganization = async (organizationId: string, ctx: TRPCContext) => { + const userId = ctx.session?.user.id; + if (!userId) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + const canModify = await prisma.organizationUser.findFirst({ + where: { + userId, + organizationId, + role: { in: [OrganizationUserRole.ADMIN, OrganizationUserRole.MEMBER] }, + }, + }); + + if (!canModify) { + throw new TRPCError({ code: "UNAUTHORIZED" }); + } + + ctx.markAccessControlRun(); +}; + export const requireCanViewDataset = async (datasetId: string, ctx: TRPCContext) => { const dataset = await prisma.dataset.findFirst({ where: { diff --git a/app/src/utils/dayjs.ts b/app/src/utils/dayjs.ts index 1d84bff..9656a25 100644 --- a/app/src/utils/dayjs.ts +++ b/app/src/utils/dayjs.ts @@ -1,9 +1,11 @@ import dayjs from "dayjs"; import duration from "dayjs/plugin/duration"; import relativeTime from "dayjs/plugin/relativeTime"; +import timezone from "dayjs/plugin/timezone"; dayjs.extend(duration); dayjs.extend(relativeTime); +dayjs.extend(timezone); export const formatTimePast = (date: Date) => dayjs.duration(dayjs(date).diff(dayjs())).humanize(true); diff --git a/app/src/utils/hooks.ts b/app/src/utils/hooks.ts index 451c7dc..11ae5e3 100644 --- a/app/src/utils/hooks.ts +++ b/app/src/utils/hooks.ts @@ -2,6 +2,15 @@ import { useRouter } from "next/router"; import { type RefObject, useCallback, useEffect, useRef, useState } from "react"; import { api } from "~/utils/api"; import { NumberParam, useQueryParam, withDefault } from "use-query-params"; +import { useAppStore } from "~/state/store"; + +export const useExperiments = () => { + const selectedOrgId = useAppStore((state) => state.selectedOrgId); + return api.experiments.list.useQuery( + { organizationId: selectedOrgId ?? "" }, + { enabled: !!selectedOrgId }, + ); +}; export const useExperiment = () => { const router = useRouter(); @@ -17,6 +26,14 @@ export const useExperimentAccess = () => { return useExperiment().data?.access ?? { canView: false, canModify: false }; }; +export const useDatasets = () => { + const selectedOrgId = useAppStore((state) => state.selectedOrgId); + return api.datasets.list.useQuery( + { organizationId: selectedOrgId ?? "" }, + { enabled: !!selectedOrgId }, + ); +}; + export const useDataset = () => { const router = useRouter(); const dataset = api.datasets.get.useQuery( @@ -132,3 +149,8 @@ export const useScenario = (scenarioId: string) => { }; export const useVisibleScenarioIds = () => useScenarios().data?.scenarios.map((s) => s.id) ?? []; + +export const useSelectedOrg = () => { + const selectedOrgId = useAppStore((state) => state.selectedOrgId); + return api.organizations.get.useQuery({ id: selectedOrgId ?? "" }, { enabled: !!selectedOrgId }); +}; diff --git a/app/src/utils/useSocket.ts b/app/src/utils/useSocket.ts index ba69387..bbefa4c 100644 --- a/app/src/utils/useSocket.ts +++ b/app/src/utils/useSocket.ts @@ -11,7 +11,6 @@ export default function useSocket(channel?: string | null) { useEffect(() => { if (!channel) return; - console.log("connecting to channel", channel); // Create websocket connection socketRef.current = io(url); diff --git a/client-libs/schema.json b/client-libs/schema.json new file mode 100644 index 0000000..3cf2280 --- /dev/null +++ b/client-libs/schema.json @@ -0,0 +1,187 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "OpenPipe API", + "description": "The public API for reporting API calls to OpenPipe", + "version": "0.1.0" + }, + "servers": [ + { + "url": "https://app.openpipe.ai/api" + } + ], + "paths": { + "/v1/check-cache": { + "post": { + "operationId": "externalApi-checkCache", + "description": "Check if a prompt is cached", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "startTime": { + "type": "number", + "description": "Unix timestamp in milliseconds" + }, + "reqPayload": { + "description": "JSON-encoded request payload" + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }" + } + }, + "required": [ + "startTime" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "respPayload": { + "description": "JSON-encoded response payload" + } + }, + "additionalProperties": false + } + } + } + }, + "default": { + "$ref": "#/components/responses/error" + } + } + } + }, + "/v1/report": { + "post": { + "operationId": "externalApi-report", + "description": "Report an API call", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "startTime": { + "type": "number", + "description": "Unix timestamp in milliseconds" + }, + "endTime": { + "type": "number", + "description": "Unix timestamp in milliseconds" + }, + "reqPayload": { + "description": "JSON-encoded request payload" + }, + "respPayload": { + "description": "JSON-encoded response payload" + }, + "respStatus": { + "type": "number", + "description": "HTTP status code of response" + }, + "error": { + "type": "string", + "description": "User-friendly error message" + }, + "tags": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" }" + } + }, + "required": [ + "startTime", + "endTime" + ], + "additionalProperties": false + } + } + } + }, + "parameters": [], + "responses": { + "200": { + "description": "Successful response", + "content": { + "application/json": { + "schema": {} + } + } + }, + "default": { + "$ref": "#/components/responses/error" + } + } + } + } + }, + "components": { + "securitySchemes": { + "Authorization": { + "type": "http", + "scheme": "bearer" + } + }, + "responses": { + "error": { + "description": "Error response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "code": { + "type": "string" + }, + "issues": { + "type": "array", + "items": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "additionalProperties": false + } + } + }, + "required": [ + "message", + "code" + ], + "additionalProperties": false + } + } + } + } + } + } +} \ No newline at end of file diff --git a/client-libs/typescript/.gitignore b/client-libs/typescript/.gitignore new file mode 100644 index 0000000..f06235c --- /dev/null +++ b/client-libs/typescript/.gitignore @@ -0,0 +1,2 @@ +node_modules +dist diff --git a/client-libs/typescript/codegen/.gitignore b/client-libs/typescript/codegen/.gitignore new file mode 100644 index 0000000..149b576 --- /dev/null +++ b/client-libs/typescript/codegen/.gitignore @@ -0,0 +1,4 @@ +wwwroot/*.js +node_modules +typings +dist diff --git a/client-libs/typescript/codegen/.npmignore b/client-libs/typescript/codegen/.npmignore new file mode 100644 index 0000000..999d88d --- /dev/null +++ b/client-libs/typescript/codegen/.npmignore @@ -0,0 +1 @@ +# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm \ No newline at end of file diff --git a/client-libs/typescript/codegen/.openapi-generator-ignore b/client-libs/typescript/codegen/.openapi-generator-ignore new file mode 100644 index 0000000..7484ee5 --- /dev/null +++ b/client-libs/typescript/codegen/.openapi-generator-ignore @@ -0,0 +1,23 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# Use this file to prevent files from being overwritten by the generator. +# The patterns follow closely to .gitignore or .dockerignore. + +# As an example, the C# client generator defines ApiClient.cs. +# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line: +#ApiClient.cs + +# You can match any string of characters against a directory, file or extension with a single asterisk (*): +#foo/*/qux +# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux + +# You can recursively match patterns against a directory, file or extension with a double asterisk (**): +#foo/**/qux +# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux + +# You can also negate patterns with an exclamation (!). +# For example, you can ignore all files in a docs folder with the file extension .md: +#docs/*.md +# Then explicitly reverse the ignore rule for a single file: +#!docs/README.md diff --git a/client-libs/typescript/codegen/.openapi-generator/FILES b/client-libs/typescript/codegen/.openapi-generator/FILES new file mode 100644 index 0000000..16b445e --- /dev/null +++ b/client-libs/typescript/codegen/.openapi-generator/FILES @@ -0,0 +1,9 @@ +.gitignore +.npmignore +.openapi-generator-ignore +api.ts +base.ts +common.ts +configuration.ts +git_push.sh +index.ts diff --git a/client-libs/typescript/codegen/.openapi-generator/VERSION b/client-libs/typescript/codegen/.openapi-generator/VERSION new file mode 100644 index 0000000..cd802a1 --- /dev/null +++ b/client-libs/typescript/codegen/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.6.0 \ No newline at end of file diff --git a/client-libs/typescript/codegen/api.js b/client-libs/typescript/codegen/api.js new file mode 100644 index 0000000..9680129 --- /dev/null +++ b/client-libs/typescript/codegen/api.js @@ -0,0 +1,272 @@ +"use strict"; +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.DefaultApi = exports.DefaultApiFactory = exports.DefaultApiFp = exports.DefaultApiAxiosParamCreator = void 0; +var axios_1 = require("axios"); +// Some imports not used depending on template conditions +// @ts-ignore +var common_1 = require("./common"); +// @ts-ignore +var base_1 = require("./base"); +/** + * DefaultApi - axios parameter creator + * @export + */ +var DefaultApiAxiosParamCreator = function (configuration) { + var _this = this; + return { + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiCheckCache: function (externalApiCheckCacheRequest, options) { + if (options === void 0) { options = {}; } + return __awaiter(_this, void 0, void 0, function () { + var localVarPath, localVarUrlObj, baseOptions, localVarRequestOptions, localVarHeaderParameter, localVarQueryParameter, headersFromBaseOptions; + return __generator(this, function (_a) { + // verify required parameter 'externalApiCheckCacheRequest' is not null or undefined + (0, common_1.assertParamExists)('externalApiCheckCache', 'externalApiCheckCacheRequest', externalApiCheckCacheRequest); + localVarPath = "/v1/check-cache"; + localVarUrlObj = new URL(localVarPath, common_1.DUMMY_BASE_URL); + if (configuration) { + baseOptions = configuration.baseOptions; + } + localVarRequestOptions = __assign(__assign({ method: 'POST' }, baseOptions), options); + localVarHeaderParameter = {}; + localVarQueryParameter = {}; + localVarHeaderParameter['Content-Type'] = 'application/json'; + (0, common_1.setSearchParams)(localVarUrlObj, localVarQueryParameter); + headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = __assign(__assign(__assign({}, localVarHeaderParameter), headersFromBaseOptions), options.headers); + localVarRequestOptions.data = (0, common_1.serializeDataIfNeeded)(externalApiCheckCacheRequest, localVarRequestOptions, configuration); + return [2 /*return*/, { + url: (0, common_1.toPathString)(localVarUrlObj), + options: localVarRequestOptions, + }]; + }); + }); + }, + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiReport: function (externalApiReportRequest, options) { + if (options === void 0) { options = {}; } + return __awaiter(_this, void 0, void 0, function () { + var localVarPath, localVarUrlObj, baseOptions, localVarRequestOptions, localVarHeaderParameter, localVarQueryParameter, headersFromBaseOptions; + return __generator(this, function (_a) { + // verify required parameter 'externalApiReportRequest' is not null or undefined + (0, common_1.assertParamExists)('externalApiReport', 'externalApiReportRequest', externalApiReportRequest); + localVarPath = "/v1/report"; + localVarUrlObj = new URL(localVarPath, common_1.DUMMY_BASE_URL); + if (configuration) { + baseOptions = configuration.baseOptions; + } + localVarRequestOptions = __assign(__assign({ method: 'POST' }, baseOptions), options); + localVarHeaderParameter = {}; + localVarQueryParameter = {}; + localVarHeaderParameter['Content-Type'] = 'application/json'; + (0, common_1.setSearchParams)(localVarUrlObj, localVarQueryParameter); + headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = __assign(__assign(__assign({}, localVarHeaderParameter), headersFromBaseOptions), options.headers); + localVarRequestOptions.data = (0, common_1.serializeDataIfNeeded)(externalApiReportRequest, localVarRequestOptions, configuration); + return [2 /*return*/, { + url: (0, common_1.toPathString)(localVarUrlObj), + options: localVarRequestOptions, + }]; + }); + }); + }, + }; +}; +exports.DefaultApiAxiosParamCreator = DefaultApiAxiosParamCreator; +/** + * DefaultApi - functional programming interface + * @export + */ +var DefaultApiFp = function (configuration) { + var localVarAxiosParamCreator = (0, exports.DefaultApiAxiosParamCreator)(configuration); + return { + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiCheckCache: function (externalApiCheckCacheRequest, options) { + return __awaiter(this, void 0, void 0, function () { + var localVarAxiosArgs; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, localVarAxiosParamCreator.externalApiCheckCache(externalApiCheckCacheRequest, options)]; + case 1: + localVarAxiosArgs = _a.sent(); + return [2 /*return*/, (0, common_1.createRequestFunction)(localVarAxiosArgs, axios_1.default, base_1.BASE_PATH, configuration)]; + } + }); + }); + }, + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiReport: function (externalApiReportRequest, options) { + return __awaiter(this, void 0, void 0, function () { + var localVarAxiosArgs; + return __generator(this, function (_a) { + switch (_a.label) { + case 0: return [4 /*yield*/, localVarAxiosParamCreator.externalApiReport(externalApiReportRequest, options)]; + case 1: + localVarAxiosArgs = _a.sent(); + return [2 /*return*/, (0, common_1.createRequestFunction)(localVarAxiosArgs, axios_1.default, base_1.BASE_PATH, configuration)]; + } + }); + }); + }, + }; +}; +exports.DefaultApiFp = DefaultApiFp; +/** + * DefaultApi - factory interface + * @export + */ +var DefaultApiFactory = function (configuration, basePath, axios) { + var localVarFp = (0, exports.DefaultApiFp)(configuration); + return { + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiCheckCache: function (externalApiCheckCacheRequest, options) { + return localVarFp.externalApiCheckCache(externalApiCheckCacheRequest, options).then(function (request) { return request(axios, basePath); }); + }, + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiReport: function (externalApiReportRequest, options) { + return localVarFp.externalApiReport(externalApiReportRequest, options).then(function (request) { return request(axios, basePath); }); + }, + }; +}; +exports.DefaultApiFactory = DefaultApiFactory; +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +var DefaultApi = /** @class */ (function (_super) { + __extends(DefaultApi, _super); + function DefaultApi() { + return _super !== null && _super.apply(this, arguments) || this; + } + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + DefaultApi.prototype.externalApiCheckCache = function (externalApiCheckCacheRequest, options) { + var _this = this; + return (0, exports.DefaultApiFp)(this.configuration).externalApiCheckCache(externalApiCheckCacheRequest, options).then(function (request) { return request(_this.axios, _this.basePath); }); + }; + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + DefaultApi.prototype.externalApiReport = function (externalApiReportRequest, options) { + var _this = this; + return (0, exports.DefaultApiFp)(this.configuration).externalApiReport(externalApiReportRequest, options).then(function (request) { return request(_this.axios, _this.basePath); }); + }; + return DefaultApi; +}(base_1.BaseAPI)); +exports.DefaultApi = DefaultApi; diff --git a/client-libs/typescript/codegen/api.ts b/client-libs/typescript/codegen/api.ts new file mode 100644 index 0000000..64b5068 --- /dev/null +++ b/client-libs/typescript/codegen/api.ts @@ -0,0 +1,319 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; +// Some imports not used depending on template conditions +// @ts-ignore +import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common'; +import type { RequestArgs } from './base'; +// @ts-ignore +import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError } from './base'; + +/** + * + * @export + * @interface ExternalApiCheckCache200Response + */ +export interface ExternalApiCheckCache200Response { + /** + * JSON-encoded response payload + * @type {any} + * @memberof ExternalApiCheckCache200Response + */ + 'respPayload'?: any; +} +/** + * + * @export + * @interface ExternalApiCheckCacheDefaultResponse + */ +export interface ExternalApiCheckCacheDefaultResponse { + /** + * + * @type {string} + * @memberof ExternalApiCheckCacheDefaultResponse + */ + 'message': string; + /** + * + * @type {string} + * @memberof ExternalApiCheckCacheDefaultResponse + */ + 'code': string; + /** + * + * @type {Array} + * @memberof ExternalApiCheckCacheDefaultResponse + */ + 'issues'?: Array; +} +/** + * + * @export + * @interface ExternalApiCheckCacheDefaultResponseIssuesInner + */ +export interface ExternalApiCheckCacheDefaultResponseIssuesInner { + /** + * + * @type {string} + * @memberof ExternalApiCheckCacheDefaultResponseIssuesInner + */ + 'message': string; +} +/** + * + * @export + * @interface ExternalApiCheckCacheRequest + */ +export interface ExternalApiCheckCacheRequest { + /** + * Unix timestamp in milliseconds + * @type {number} + * @memberof ExternalApiCheckCacheRequest + */ + 'startTime': number; + /** + * JSON-encoded request payload + * @type {any} + * @memberof ExternalApiCheckCacheRequest + */ + 'reqPayload'?: any; + /** + * Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" } + * @type {{ [key: string]: string; }} + * @memberof ExternalApiCheckCacheRequest + */ + 'tags'?: { [key: string]: string; }; +} +/** + * + * @export + * @interface ExternalApiReportRequest + */ +export interface ExternalApiReportRequest { + /** + * Unix timestamp in milliseconds + * @type {number} + * @memberof ExternalApiReportRequest + */ + 'startTime': number; + /** + * Unix timestamp in milliseconds + * @type {number} + * @memberof ExternalApiReportRequest + */ + 'endTime': number; + /** + * JSON-encoded request payload + * @type {any} + * @memberof ExternalApiReportRequest + */ + 'reqPayload'?: any; + /** + * JSON-encoded response payload + * @type {any} + * @memberof ExternalApiReportRequest + */ + 'respPayload'?: any; + /** + * HTTP status code of response + * @type {number} + * @memberof ExternalApiReportRequest + */ + 'respStatus'?: number; + /** + * User-friendly error message + * @type {string} + * @memberof ExternalApiReportRequest + */ + 'error'?: string; + /** + * Extra tags to attach to the call for filtering. Eg { \"userId\": \"123\", \"promptId\": \"populate-title\" } + * @type {{ [key: string]: string; }} + * @memberof ExternalApiReportRequest + */ + 'tags'?: { [key: string]: string; }; +} + +/** + * DefaultApi - axios parameter creator + * @export + */ +export const DefaultApiAxiosParamCreator = function (configuration?: Configuration) { + return { + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiCheckCache: async (externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'externalApiCheckCacheRequest' is not null or undefined + assertParamExists('externalApiCheckCache', 'externalApiCheckCacheRequest', externalApiCheckCacheRequest) + const localVarPath = `/v1/check-cache`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(externalApiCheckCacheRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiReport: async (externalApiReportRequest: ExternalApiReportRequest, options: AxiosRequestConfig = {}): Promise => { + // verify required parameter 'externalApiReportRequest' is not null or undefined + assertParamExists('externalApiReport', 'externalApiReportRequest', externalApiReportRequest) + const localVarPath = `/v1/report`; + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + + + localVarHeaderParameter['Content-Type'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(externalApiReportRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + } +}; + +/** + * DefaultApi - functional programming interface + * @export + */ +export const DefaultApiFp = function(configuration?: Configuration) { + const localVarAxiosParamCreator = DefaultApiAxiosParamCreator(configuration) + return { + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async externalApiCheckCache(externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.externalApiCheckCache(externalApiCheckCacheRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async externalApiReport(externalApiReportRequest: ExternalApiReportRequest, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.externalApiReport(externalApiReportRequest, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + } +}; + +/** + * DefaultApi - factory interface + * @export + */ +export const DefaultApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) { + const localVarFp = DefaultApiFp(configuration) + return { + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiCheckCache(externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options?: any): AxiosPromise { + return localVarFp.externalApiCheckCache(externalApiCheckCacheRequest, options).then((request) => request(axios, basePath)); + }, + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + externalApiReport(externalApiReportRequest: ExternalApiReportRequest, options?: any): AxiosPromise { + return localVarFp.externalApiReport(externalApiReportRequest, options).then((request) => request(axios, basePath)); + }, + }; +}; + +/** + * DefaultApi - object-oriented interface + * @export + * @class DefaultApi + * @extends {BaseAPI} + */ +export class DefaultApi extends BaseAPI { + /** + * Check if a prompt is cached + * @param {ExternalApiCheckCacheRequest} externalApiCheckCacheRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public externalApiCheckCache(externalApiCheckCacheRequest: ExternalApiCheckCacheRequest, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).externalApiCheckCache(externalApiCheckCacheRequest, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Report an API call + * @param {ExternalApiReportRequest} externalApiReportRequest + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof DefaultApi + */ + public externalApiReport(externalApiReportRequest: ExternalApiReportRequest, options?: AxiosRequestConfig) { + return DefaultApiFp(this.configuration).externalApiReport(externalApiReportRequest, options).then((request) => request(this.axios, this.basePath)); + } +} + + diff --git a/client-libs/typescript/codegen/base.js b/client-libs/typescript/codegen/base.js new file mode 100644 index 0000000..5d52771 --- /dev/null +++ b/client-libs/typescript/codegen/base.js @@ -0,0 +1,80 @@ +"use strict"; +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RequiredError = exports.BaseAPI = exports.COLLECTION_FORMATS = exports.BASE_PATH = void 0; +var axios_1 = require("axios"); +exports.BASE_PATH = "https://app.openpipe.ai/api".replace(/\/+$/, ""); +/** + * + * @export + */ +exports.COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; +/** + * + * @export + * @class BaseAPI + */ +var BaseAPI = /** @class */ (function () { + function BaseAPI(configuration, basePath, axios) { + if (basePath === void 0) { basePath = exports.BASE_PATH; } + if (axios === void 0) { axios = axios_1.default; } + this.basePath = basePath; + this.axios = axios; + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } + return BaseAPI; +}()); +exports.BaseAPI = BaseAPI; +; +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +var RequiredError = /** @class */ (function (_super) { + __extends(RequiredError, _super); + function RequiredError(field, msg) { + var _this = _super.call(this, msg) || this; + _this.field = field; + _this.name = "RequiredError"; + return _this; + } + return RequiredError; +}(Error)); +exports.RequiredError = RequiredError; diff --git a/client-libs/typescript/codegen/base.ts b/client-libs/typescript/codegen/base.ts new file mode 100644 index 0000000..33af09a --- /dev/null +++ b/client-libs/typescript/codegen/base.ts @@ -0,0 +1,72 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from './configuration'; +// Some imports not used depending on template conditions +// @ts-ignore +import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios'; +import globalAxios from 'axios'; + +export const BASE_PATH = "https://app.openpipe.ai/api".replace(/\/+$/, ""); + +/** + * + * @export + */ +export const COLLECTION_FORMATS = { + csv: ",", + ssv: " ", + tsv: "\t", + pipes: "|", +}; + +/** + * + * @export + * @interface RequestArgs + */ +export interface RequestArgs { + url: string; + options: AxiosRequestConfig; +} + +/** + * + * @export + * @class BaseAPI + */ +export class BaseAPI { + protected configuration: Configuration | undefined; + + constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) { + if (configuration) { + this.configuration = configuration; + this.basePath = configuration.basePath || this.basePath; + } + } +}; + +/** + * + * @export + * @class RequiredError + * @extends {Error} + */ +export class RequiredError extends Error { + constructor(public field: string, msg?: string) { + super(msg); + this.name = "RequiredError" + } +} diff --git a/client-libs/typescript/codegen/common.js b/client-libs/typescript/codegen/common.js new file mode 100644 index 0000000..fa65488 --- /dev/null +++ b/client-libs/typescript/codegen/common.js @@ -0,0 +1,252 @@ +"use strict"; +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +var __assign = (this && this.__assign) || function () { + __assign = Object.assign || function(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) + t[p] = s[p]; + } + return t; + }; + return __assign.apply(this, arguments); +}; +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +var __generator = (this && this.__generator) || function (thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (g && (g = 0, op[0] && (_ = 0)), _) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.createRequestFunction = exports.toPathString = exports.serializeDataIfNeeded = exports.setSearchParams = exports.setOAuthToObject = exports.setBearerAuthToObject = exports.setBasicAuthToObject = exports.setApiKeyToObject = exports.assertParamExists = exports.DUMMY_BASE_URL = void 0; +var base_1 = require("./base"); +/** + * + * @export + */ +exports.DUMMY_BASE_URL = 'https://example.com'; +/** + * + * @throws {RequiredError} + * @export + */ +var assertParamExists = function (functionName, paramName, paramValue) { + if (paramValue === null || paramValue === undefined) { + throw new base_1.RequiredError(paramName, "Required parameter ".concat(paramName, " was null or undefined when calling ").concat(functionName, ".")); + } +}; +exports.assertParamExists = assertParamExists; +/** + * + * @export + */ +var setApiKeyToObject = function (object, keyParamName, configuration) { + return __awaiter(this, void 0, void 0, function () { + var localVarApiKeyValue, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!(configuration && configuration.apiKey)) return [3 /*break*/, 5]; + if (!(typeof configuration.apiKey === 'function')) return [3 /*break*/, 2]; + return [4 /*yield*/, configuration.apiKey(keyParamName)]; + case 1: + _a = _b.sent(); + return [3 /*break*/, 4]; + case 2: return [4 /*yield*/, configuration.apiKey]; + case 3: + _a = _b.sent(); + _b.label = 4; + case 4: + localVarApiKeyValue = _a; + object[keyParamName] = localVarApiKeyValue; + _b.label = 5; + case 5: return [2 /*return*/]; + } + }); + }); +}; +exports.setApiKeyToObject = setApiKeyToObject; +/** + * + * @export + */ +var setBasicAuthToObject = function (object, configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +}; +exports.setBasicAuthToObject = setBasicAuthToObject; +/** + * + * @export + */ +var setBearerAuthToObject = function (object, configuration) { + return __awaiter(this, void 0, void 0, function () { + var accessToken, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!(configuration && configuration.accessToken)) return [3 /*break*/, 5]; + if (!(typeof configuration.accessToken === 'function')) return [3 /*break*/, 2]; + return [4 /*yield*/, configuration.accessToken()]; + case 1: + _a = _b.sent(); + return [3 /*break*/, 4]; + case 2: return [4 /*yield*/, configuration.accessToken]; + case 3: + _a = _b.sent(); + _b.label = 4; + case 4: + accessToken = _a; + object["Authorization"] = "Bearer " + accessToken; + _b.label = 5; + case 5: return [2 /*return*/]; + } + }); + }); +}; +exports.setBearerAuthToObject = setBearerAuthToObject; +/** + * + * @export + */ +var setOAuthToObject = function (object, name, scopes, configuration) { + return __awaiter(this, void 0, void 0, function () { + var localVarAccessTokenValue, _a; + return __generator(this, function (_b) { + switch (_b.label) { + case 0: + if (!(configuration && configuration.accessToken)) return [3 /*break*/, 5]; + if (!(typeof configuration.accessToken === 'function')) return [3 /*break*/, 2]; + return [4 /*yield*/, configuration.accessToken(name, scopes)]; + case 1: + _a = _b.sent(); + return [3 /*break*/, 4]; + case 2: return [4 /*yield*/, configuration.accessToken]; + case 3: + _a = _b.sent(); + _b.label = 4; + case 4: + localVarAccessTokenValue = _a; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + _b.label = 5; + case 5: return [2 /*return*/]; + } + }); + }); +}; +exports.setOAuthToObject = setOAuthToObject; +function setFlattenedQueryParams(urlSearchParams, parameter, key) { + if (key === void 0) { key = ""; } + if (parameter == null) + return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + parameter.forEach(function (item) { return setFlattenedQueryParams(urlSearchParams, item, key); }); + } + else { + Object.keys(parameter).forEach(function (currentKey) { + return setFlattenedQueryParams(urlSearchParams, parameter[currentKey], "".concat(key).concat(key !== '' ? '.' : '').concat(currentKey)); + }); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} +/** + * + * @export + */ +var setSearchParams = function (url) { + var objects = []; + for (var _i = 1; _i < arguments.length; _i++) { + objects[_i - 1] = arguments[_i]; + } + var searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +}; +exports.setSearchParams = setSearchParams; +/** + * + * @export + */ +var serializeDataIfNeeded = function (value, requestOptions, configuration) { + var nonString = typeof value !== 'string'; + var needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +}; +exports.serializeDataIfNeeded = serializeDataIfNeeded; +/** + * + * @export + */ +var toPathString = function (url) { + return url.pathname + url.search + url.hash; +}; +exports.toPathString = toPathString; +/** + * + * @export + */ +var createRequestFunction = function (axiosArgs, globalAxios, BASE_PATH, configuration) { + return function (axios, basePath) { + if (axios === void 0) { axios = globalAxios; } + if (basePath === void 0) { basePath = BASE_PATH; } + var axiosRequestArgs = __assign(__assign({}, axiosArgs.options), { url: ((configuration === null || configuration === void 0 ? void 0 : configuration.basePath) || basePath) + axiosArgs.url }); + return axios.request(axiosRequestArgs); + }; +}; +exports.createRequestFunction = createRequestFunction; diff --git a/client-libs/typescript/codegen/common.ts b/client-libs/typescript/codegen/common.ts new file mode 100644 index 0000000..6a57377 --- /dev/null +++ b/client-libs/typescript/codegen/common.ts @@ -0,0 +1,150 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +import type { Configuration } from "./configuration"; +import type { RequestArgs } from "./base"; +import type { AxiosInstance, AxiosResponse } from 'axios'; +import { RequiredError } from "./base"; + +/** + * + * @export + */ +export const DUMMY_BASE_URL = 'https://example.com' + +/** + * + * @throws {RequiredError} + * @export + */ +export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) { + if (paramValue === null || paramValue === undefined) { + throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`); + } +} + +/** + * + * @export + */ +export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) { + if (configuration && configuration.apiKey) { + const localVarApiKeyValue = typeof configuration.apiKey === 'function' + ? await configuration.apiKey(keyParamName) + : await configuration.apiKey; + object[keyParamName] = localVarApiKeyValue; + } +} + +/** + * + * @export + */ +export const setBasicAuthToObject = function (object: any, configuration?: Configuration) { + if (configuration && (configuration.username || configuration.password)) { + object["auth"] = { username: configuration.username, password: configuration.password }; + } +} + +/** + * + * @export + */ +export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const accessToken = typeof configuration.accessToken === 'function' + ? await configuration.accessToken() + : await configuration.accessToken; + object["Authorization"] = "Bearer " + accessToken; + } +} + +/** + * + * @export + */ +export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) { + if (configuration && configuration.accessToken) { + const localVarAccessTokenValue = typeof configuration.accessToken === 'function' + ? await configuration.accessToken(name, scopes) + : await configuration.accessToken; + object["Authorization"] = "Bearer " + localVarAccessTokenValue; + } +} + +function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void { + if (parameter == null) return; + if (typeof parameter === "object") { + if (Array.isArray(parameter)) { + (parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key)); + } + else { + Object.keys(parameter).forEach(currentKey => + setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`) + ); + } + } + else { + if (urlSearchParams.has(key)) { + urlSearchParams.append(key, parameter); + } + else { + urlSearchParams.set(key, parameter); + } + } +} + +/** + * + * @export + */ +export const setSearchParams = function (url: URL, ...objects: any[]) { + const searchParams = new URLSearchParams(url.search); + setFlattenedQueryParams(searchParams, objects); + url.search = searchParams.toString(); +} + +/** + * + * @export + */ +export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) { + const nonString = typeof value !== 'string'; + const needsSerialization = nonString && configuration && configuration.isJsonMime + ? configuration.isJsonMime(requestOptions.headers['Content-Type']) + : nonString; + return needsSerialization + ? JSON.stringify(value !== undefined ? value : {}) + : (value || ""); +} + +/** + * + * @export + */ +export const toPathString = function (url: URL) { + return url.pathname + url.search + url.hash +} + +/** + * + * @export + */ +export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) { + return >(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => { + const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url}; + return axios.request(axiosRequestArgs); + }; +} diff --git a/client-libs/typescript/codegen/configuration.js b/client-libs/typescript/codegen/configuration.js new file mode 100644 index 0000000..bbc5bfb --- /dev/null +++ b/client-libs/typescript/codegen/configuration.js @@ -0,0 +1,44 @@ +"use strict"; +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Configuration = void 0; +var Configuration = /** @class */ (function () { + function Configuration(param) { + if (param === void 0) { param = {}; } + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + Configuration.prototype.isJsonMime = function (mime) { + var jsonMime = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + }; + return Configuration; +}()); +exports.Configuration = Configuration; diff --git a/client-libs/typescript/codegen/configuration.ts b/client-libs/typescript/codegen/configuration.ts new file mode 100644 index 0000000..4bd4a54 --- /dev/null +++ b/client-libs/typescript/codegen/configuration.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export interface ConfigurationParameters { + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + username?: string; + password?: string; + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + basePath?: string; + baseOptions?: any; + formDataCtor?: new () => any; +} + +export class Configuration { + /** + * parameter for apiKey security + * @param name security name + * @memberof Configuration + */ + apiKey?: string | Promise | ((name: string) => string) | ((name: string) => Promise); + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + username?: string; + /** + * parameter for basic security + * + * @type {string} + * @memberof Configuration + */ + password?: string; + /** + * parameter for oauth2 security + * @param name security name + * @param scopes oauth2 scope + * @memberof Configuration + */ + accessToken?: string | Promise | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise); + /** + * override base path + * + * @type {string} + * @memberof Configuration + */ + basePath?: string; + /** + * base options for axios calls + * + * @type {any} + * @memberof Configuration + */ + baseOptions?: any; + /** + * The FormData constructor that will be used to create multipart form data + * requests. You can inject this here so that execution environments that + * do not support the FormData class can still run the generated client. + * + * @type {new () => FormData} + */ + formDataCtor?: new () => any; + + constructor(param: ConfigurationParameters = {}) { + this.apiKey = param.apiKey; + this.username = param.username; + this.password = param.password; + this.accessToken = param.accessToken; + this.basePath = param.basePath; + this.baseOptions = param.baseOptions; + this.formDataCtor = param.formDataCtor; + } + + /** + * Check if the given MIME is a JSON MIME. + * JSON MIME examples: + * application/json + * application/json; charset=UTF8 + * APPLICATION/JSON + * application/vnd.company+json + * @param mime - MIME (Multipurpose Internet Mail Extensions) + * @return True if the given MIME is JSON, false otherwise. + */ + public isJsonMime(mime: string): boolean { + const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i'); + return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json'); + } +} diff --git a/client-libs/typescript/codegen/git_push.sh b/client-libs/typescript/codegen/git_push.sh new file mode 100644 index 0000000..f53a75d --- /dev/null +++ b/client-libs/typescript/codegen/git_push.sh @@ -0,0 +1,57 @@ +#!/bin/sh +# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/ +# +# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com" + +git_user_id=$1 +git_repo_id=$2 +release_note=$3 +git_host=$4 + +if [ "$git_host" = "" ]; then + git_host="github.com" + echo "[INFO] No command line input provided. Set \$git_host to $git_host" +fi + +if [ "$git_user_id" = "" ]; then + git_user_id="GIT_USER_ID" + echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id" +fi + +if [ "$git_repo_id" = "" ]; then + git_repo_id="GIT_REPO_ID" + echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id" +fi + +if [ "$release_note" = "" ]; then + release_note="Minor update" + echo "[INFO] No command line input provided. Set \$release_note to $release_note" +fi + +# Initialize the local directory as a Git repository +git init + +# Adds the files in the local repository and stages them for commit. +git add . + +# Commits the tracked changes and prepares them to be pushed to a remote repository. +git commit -m "$release_note" + +# Sets the new remote +git_remote=$(git remote) +if [ "$git_remote" = "" ]; then # git remote not defined + + if [ "$GIT_TOKEN" = "" ]; then + echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment." + git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git + else + git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git + fi + +fi + +git pull origin master + +# Pushes (Forces) the changes in the local repository up to the remote repository +echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git" +git push origin master 2>&1 | grep -v 'To https' diff --git a/client-libs/typescript/codegen/index.js b/client-libs/typescript/codegen/index.js new file mode 100644 index 0000000..519d4f3 --- /dev/null +++ b/client-libs/typescript/codegen/index.js @@ -0,0 +1,31 @@ +"use strict"; +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./api"), exports); +__exportStar(require("./configuration"), exports); diff --git a/client-libs/typescript/codegen/index.ts b/client-libs/typescript/codegen/index.ts new file mode 100644 index 0000000..3bc3def --- /dev/null +++ b/client-libs/typescript/codegen/index.ts @@ -0,0 +1,18 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * OpenPipe API + * The public API for reporting API calls to OpenPipe + * + * The version of the OpenAPI document: 0.1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +export * from "./api"; +export * from "./configuration"; + diff --git a/client-libs/typescript/index.ts b/client-libs/typescript/index.ts new file mode 100644 index 0000000..04c7df2 --- /dev/null +++ b/client-libs/typescript/index.ts @@ -0,0 +1,3 @@ +// main.ts or index.ts at the root level +export * as OpenAI from './openai'; +export * as OpenAILegacy from './openai-legacy'; diff --git a/client-libs/typescript/openai-legacy/index.ts b/client-libs/typescript/openai-legacy/index.ts new file mode 100644 index 0000000..99b8d80 --- /dev/null +++ b/client-libs/typescript/openai-legacy/index.ts @@ -0,0 +1,81 @@ +import * as openPipeClient from "../codegen"; +import * as openai from "openai-legacy"; +import { version } from "../package.json"; + +// Anything we don't override we want to pass through to openai directly +export * as openAILegacy from "openai-legacy"; + +type OPConfigurationParameters = { + apiKey?: string; + basePath?: string; +}; + +export class Configuration extends openai.Configuration { + public qkConfig?: openPipeClient.Configuration; + + constructor(config: openai.ConfigurationParameters & { opParameters?: OPConfigurationParameters }) { + super(config); + if (config.opParameters) { + this.qkConfig = new openPipeClient.Configuration(config.opParameters); + } + } +} + +type CreateChatCompletion = InstanceType["createChatCompletion"]; + +export class OpenAIApi extends openai.OpenAIApi { + public openPipeApi?: openPipeClient.DefaultApi; + + constructor(config: Configuration) { + super(config); + if (config.qkConfig) { + this.openPipeApi = new openPipeClient.DefaultApi(config.qkConfig); + } + } + + public async createChatCompletion( + createChatCompletionRequest: Parameters[0], + options?: Parameters[1] + ): ReturnType { + const startTime = Date.now(); + let resp: Awaited> | null = null; + let respPayload: openai.CreateChatCompletionResponse | null = null; + let respStatus: number | undefined = undefined; + let error: string | undefined; + try { + resp = await super.createChatCompletion(createChatCompletionRequest, options); + respPayload = resp.data; + respStatus = resp.status; + } catch (err) { + console.error("Error in createChatCompletion"); + if ("isAxiosError" in err && err.isAxiosError) { + error = err.response?.data?.error?.message; + respPayload = err.response?.data; + respStatus = err.response?.status; + } else if ("message" in err) { + error = err.message.toString(); + } + throw err; + } finally { + this.openPipeApi + ?.externalApiReport({ + startTime, + endTime: Date.now(), + reqPayload: createChatCompletionRequest, + respPayload: respPayload, + respStatus: respStatus, + error, + tags: { + client: "openai-js", + clientVersion: version, + }, + }) + .catch((err) => { + console.error("Error reporting to QK", err); + }); + } + + console.log("done"); + return resp; + } +} diff --git a/client-libs/typescript/openai/index.ts b/client-libs/typescript/openai/index.ts new file mode 100644 index 0000000..8c20bdb --- /dev/null +++ b/client-libs/typescript/openai/index.ts @@ -0,0 +1,111 @@ +import * as openai from "openai-beta"; +import { readEnv, type RequestOptions } from "openai-beta/core"; +import { CompletionCreateParams } from "openai-beta/resources/chat/completions"; +import axios from "axios"; + +export * as openai from "openai-beta"; +import * as openPipeClient from "../codegen"; + +interface ClientOptions extends openai.ClientOptions { + openPipeApiKey?: string; + openPipeBaseUrl?: string; +} + +export class OpenAI extends openai.OpenAI { + public openPipeApi?: openPipeClient.DefaultApi; + + constructor({ + openPipeApiKey = readEnv("OPENPIPE_API_KEY"), + openPipeBaseUrl = readEnv("OPENPIPE_BASE_URL") ?? + `https://app.openpipe.ai/v1`, + ...opts + }: ClientOptions = {}) { + super({ ...opts }); + + if (openPipeApiKey) { + const axiosInstance = axios.create({ + baseURL: openPipeBaseUrl, + headers: { + 'x-openpipe-api-key': openPipeApiKey, + }, + }); + this.openPipeApi = new openPipeClient.DefaultApi( + new openPipeClient.Configuration({ + apiKey: openPipeApiKey, + basePath: openPipeBaseUrl, + }), + undefined, + axiosInstance + ); + } + + // Override the chat property + this.chat = new ExtendedChat(this); + + if (openPipeApiKey === undefined) { + console.error( + "The OPENPIPE_API_KEY environment variable is missing or empty; either provide it, or instantiate the OpenPipe client with an openPipeApiKey option, like new OpenPipe({ openPipeApiKey: undefined })." + ); + } + } +} + +class ExtendedChat extends openai.OpenAI.Chat { + completions: ExtendedCompletions; + + constructor(openaiInstance: OpenAI) { + super(openaiInstance); + // Initialize the new completions instance + this.completions = new ExtendedCompletions(openaiInstance); + } +} + +class ExtendedCompletions extends openai.OpenAI.Chat.Completions { + private openaiInstance: OpenAI; + + constructor(openaiInstance: OpenAI) { + super(openaiInstance); + this.openaiInstance = openaiInstance; + } + + async create( + params: + | CompletionCreateParams.CreateChatCompletionRequestNonStreaming + | CompletionCreateParams.CreateChatCompletionRequestStreaming, + options?: RequestOptions, + tags?: Record + ): Promise { + // Your pre API call logic here + console.log("Doing pre API call..."); + + // Determine the type of request + if (params.hasOwnProperty("stream") && params.stream === true) { + const result = await super.create( + params as CompletionCreateParams.CreateChatCompletionRequestStreaming, + options + ); + // Your post API call logic here + console.log("Doing post API call for Streaming..."); + return result; + } else { + const startTime = Date.now(); + const result = await super.create( + params as CompletionCreateParams.CreateChatCompletionRequestNonStreaming, + options + ); + await this.openaiInstance.openPipeApi?.externalApiReport({ + startTime, + endTime: Date.now(), + reqPayload: params, + respPayload: result, + respStatus: 200, + error: undefined, + tags, + }); + + // Your post API call logic here + console.log("Doing post API call for NonStreaming..."); + return result; + } + } +} diff --git a/client-libs/typescript/package.json b/client-libs/typescript/package.json new file mode 100644 index 0000000..1173fc2 --- /dev/null +++ b/client-libs/typescript/package.json @@ -0,0 +1,24 @@ +{ + "name": "openpipe", + "version": "0.1.0", + "description": "Metrics and auto-evaluation for LLM calls", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "axios": "^0.26.0", + "openai-beta": "npm:openai@4.0.0-beta.7", + "openai-legacy": "npm:openai@3.3.0" + }, + "devDependencies": { + "@types/node": "^20.4.8", + "dotenv": "^16.3.1", + "tsx": "^3.12.7", + "typescript": "^5.0.4" + } +} diff --git a/client-libs/typescript/pnpm-lock.yaml b/client-libs/typescript/pnpm-lock.yaml new file mode 100644 index 0000000..5d145bb --- /dev/null +++ b/client-libs/typescript/pnpm-lock.yaml @@ -0,0 +1,548 @@ +lockfileVersion: '6.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +dependencies: + axios: + specifier: ^0.26.0 + version: 0.26.0 + openai-beta: + specifier: npm:openai@4.0.0-beta.7 + version: /openai@4.0.0-beta.7 + openai-legacy: + specifier: npm:openai@3.3.0 + version: /openai@3.3.0 + +devDependencies: + '@types/node': + specifier: ^20.4.8 + version: 20.4.8 + dotenv: + specifier: ^16.3.1 + version: 16.3.1 + tsx: + specifier: ^3.12.7 + version: 3.12.7 + typescript: + specifier: ^5.0.4 + version: 5.0.4 + +packages: + + /@esbuild-kit/cjs-loader@2.4.2: + resolution: {integrity: sha512-BDXFbYOJzT/NBEtp71cvsrGPwGAMGRB/349rwKuoxNSiKjPraNNnlK6MIIabViCjqZugu6j+xeMDlEkWdHHJSg==} + dependencies: + '@esbuild-kit/core-utils': 3.1.0 + get-tsconfig: 4.6.2 + dev: true + + /@esbuild-kit/core-utils@3.1.0: + resolution: {integrity: sha512-Uuk8RpCg/7fdHSceR1M6XbSZFSuMrxcePFuGgyvsBn+u339dk5OeL4jv2EojwTN2st/unJGsVm4qHWjWNmJ/tw==} + dependencies: + esbuild: 0.17.19 + source-map-support: 0.5.21 + dev: true + + /@esbuild-kit/esm-loader@2.5.5: + resolution: {integrity: sha512-Qwfvj/qoPbClxCRNuac1Du01r9gvNOT+pMYtJDapfB1eoGN1YlJ1BixLyL9WVENRx5RXgNLdfYdx/CuswlGhMw==} + dependencies: + '@esbuild-kit/core-utils': 3.1.0 + get-tsconfig: 4.6.2 + dev: true + + /@esbuild/android-arm64@0.17.19: + resolution: {integrity: sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.17.19: + resolution: {integrity: sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.17.19: + resolution: {integrity: sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.17.19: + resolution: {integrity: sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.17.19: + resolution: {integrity: sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.17.19: + resolution: {integrity: sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.17.19: + resolution: {integrity: sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.17.19: + resolution: {integrity: sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.17.19: + resolution: {integrity: sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.17.19: + resolution: {integrity: sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.17.19: + resolution: {integrity: sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.17.19: + resolution: {integrity: sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.17.19: + resolution: {integrity: sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.17.19: + resolution: {integrity: sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.17.19: + resolution: {integrity: sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.17.19: + resolution: {integrity: sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.17.19: + resolution: {integrity: sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.17.19: + resolution: {integrity: sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.17.19: + resolution: {integrity: sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.17.19: + resolution: {integrity: sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.17.19: + resolution: {integrity: sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.17.19: + resolution: {integrity: sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@types/node-fetch@2.6.4: + resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} + dependencies: + '@types/node': 20.4.8 + form-data: 3.0.1 + dev: false + + /@types/node@18.17.3: + resolution: {integrity: sha512-2x8HWtFk0S99zqVQABU9wTpr8wPoaDHZUcAkoTKH+nL7kPv3WUI9cRi/Kk5Mz4xdqXSqTkKP7IWNoQQYCnDsTA==} + dev: false + + /@types/node@20.4.8: + resolution: {integrity: sha512-0mHckf6D2DiIAzh8fM8f3HQCvMKDpK94YQ0DSVkfWTG9BZleYIWudw9cJxX8oCk9bM+vAkDyujDV6dmKHbvQpg==} + + /abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + + /agentkeepalive@4.5.0: + resolution: {integrity: sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==} + engines: {node: '>= 8.0.0'} + dependencies: + humanize-ms: 1.2.1 + dev: false + + /asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + dev: false + + /axios@0.26.0: + resolution: {integrity: sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==} + dependencies: + follow-redirects: 1.15.2 + transitivePeerDependencies: + - debug + dev: false + + /base-64@0.1.0: + resolution: {integrity: sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==} + dev: false + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: true + + /charenc@0.0.2: + resolution: {integrity: sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==} + dev: false + + /combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + dependencies: + delayed-stream: 1.0.0 + dev: false + + /crypt@0.0.2: + resolution: {integrity: sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==} + dev: false + + /delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + dev: false + + /digest-fetch@1.3.0: + resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==} + dependencies: + base-64: 0.1.0 + md5: 2.3.0 + dev: false + + /dotenv@16.3.1: + resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} + engines: {node: '>=12'} + dev: true + + /esbuild@0.17.19: + resolution: {integrity: sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.19 + '@esbuild/android-arm64': 0.17.19 + '@esbuild/android-x64': 0.17.19 + '@esbuild/darwin-arm64': 0.17.19 + '@esbuild/darwin-x64': 0.17.19 + '@esbuild/freebsd-arm64': 0.17.19 + '@esbuild/freebsd-x64': 0.17.19 + '@esbuild/linux-arm': 0.17.19 + '@esbuild/linux-arm64': 0.17.19 + '@esbuild/linux-ia32': 0.17.19 + '@esbuild/linux-loong64': 0.17.19 + '@esbuild/linux-mips64el': 0.17.19 + '@esbuild/linux-ppc64': 0.17.19 + '@esbuild/linux-riscv64': 0.17.19 + '@esbuild/linux-s390x': 0.17.19 + '@esbuild/linux-x64': 0.17.19 + '@esbuild/netbsd-x64': 0.17.19 + '@esbuild/openbsd-x64': 0.17.19 + '@esbuild/sunos-x64': 0.17.19 + '@esbuild/win32-arm64': 0.17.19 + '@esbuild/win32-ia32': 0.17.19 + '@esbuild/win32-x64': 0.17.19 + dev: true + + /event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + + /follow-redirects@1.15.2: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + + /form-data-encoder@1.7.2: + resolution: {integrity: sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==} + dev: false + + /form-data@3.0.1: + resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + dev: false + + /formdata-node@4.4.1: + resolution: {integrity: sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==} + engines: {node: '>= 12.20'} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 4.0.0-beta.3 + dev: false + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /get-tsconfig@4.6.2: + resolution: {integrity: sha512-E5XrT4CbbXcXWy+1jChlZmrmCwd5KGx502kDCXJJ7y898TtWW9FwoG5HfOLVRKmlmDGkWN2HM9Ho+/Y8F0sJDg==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + + /humanize-ms@1.2.1: + resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + dependencies: + ms: 2.1.3 + dev: false + + /is-buffer@1.1.6: + resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} + dev: false + + /md5@2.3.0: + resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==} + dependencies: + charenc: 0.0.2 + crypt: 0.0.2 + is-buffer: 1.1.6 + dev: false + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + dev: false + + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: false + + /node-fetch@2.6.12: + resolution: {integrity: sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + dependencies: + whatwg-url: 5.0.0 + dev: false + + /openai@3.3.0: + resolution: {integrity: sha512-uqxI/Au+aPRnsaQRe8CojU0eCR7I0mBiKjD3sNMzY6DaC1ZVrc85u98mtJW6voDug8fgGN+DIZmTDxTthxb7dQ==} + dependencies: + axios: 0.26.0 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: false + + /openai@4.0.0-beta.7: + resolution: {integrity: sha512-jHjwvpMuGkNxiQ3erwLZsOvPEhcVrMtwtfNeYmGCjhbdB+oStVw/7pIhIPkualu8rlhLwgMR7awknIaN3IQcOA==} + dependencies: + '@types/node': 18.17.3 + '@types/node-fetch': 2.6.4 + abort-controller: 3.0.0 + agentkeepalive: 4.5.0 + digest-fetch: 1.3.0 + form-data-encoder: 1.7.2 + formdata-node: 4.4.1 + node-fetch: 2.6.12 + transitivePeerDependencies: + - encoding + dev: false + + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: true + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: true + + /tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + dev: false + + /tsx@3.12.7: + resolution: {integrity: sha512-C2Ip+jPmqKd1GWVQDvz/Eyc6QJbGfE7NrR3fx5BpEHMZsEHoIxHL1j+lKdGobr8ovEyqeNkPLSKp6SCSOt7gmw==} + hasBin: true + dependencies: + '@esbuild-kit/cjs-loader': 2.4.2 + '@esbuild-kit/core-utils': 3.1.0 + '@esbuild-kit/esm-loader': 2.5.5 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /typescript@5.0.4: + resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==} + engines: {node: '>=12.20'} + hasBin: true + dev: true + + /web-streams-polyfill@4.0.0-beta.3: + resolution: {integrity: sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==} + engines: {node: '>= 14'} + dev: false + + /webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + dev: false + + /whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + dev: false diff --git a/client-libs/typescript/tsconfig.json b/client-libs/typescript/tsconfig.json new file mode 100644 index 0000000..60b88bf --- /dev/null +++ b/client-libs/typescript/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "declaration": true, + "target": "es6", + "module": "commonjs", + "noImplicitAny": true, + "resolveJsonModule": true, + "outDir": "dist", + "rootDir": ".", + "skipLibCheck": true + }, + "exclude": ["dist", "node_modules"], +}