diff --git a/.vscode/settings.json b/.vscode/settings.json index a9d566c..d012460 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,3 @@ { - "eslint.format.enable": true, - "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - } + "eslint.format.enable": true } diff --git a/package.json b/package.json index ceec72b..a4bf463 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "lodash-es": "^4.17.21", "next": "^13.4.2", "next-auth": "^4.22.1", + "next-query-params": "^4.2.3", "nextjs-routes": "^2.0.1", "openai": "4.0.0-beta.2", "pluralize": "^8.0.0", @@ -79,6 +80,7 @@ "superjson": "1.12.2", "tsx": "^3.12.7", "type-fest": "^4.0.0", + "use-query-params": "^2.2.1", "vite-tsconfig-paths": "^4.2.0", "zod": "^3.21.4", "zustand": "^4.3.9" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d2d34b7..7b4f17e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -119,6 +119,9 @@ dependencies: next-auth: specifier: ^4.22.1 version: 4.22.1(next@13.4.2)(react-dom@18.2.0)(react@18.2.0) + 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-routes: specifier: ^2.0.1 version: 2.0.1(next@13.4.2) @@ -179,6 +182,9 @@ dependencies: type-fest: specifier: ^4.0.0 version: 4.0.0 + use-query-params: + specifier: ^2.2.1 + version: 2.2.1(react-dom@18.2.0)(react@18.2.0) vite-tsconfig-paths: specifier: ^4.2.0 version: 4.2.0(typescript@5.0.4) @@ -6037,6 +6043,19 @@ packages: uuid: 8.3.2 dev: false + /next-query-params@4.2.3(next@13.4.2)(react@18.2.0)(use-query-params@2.2.1): + resolution: {integrity: sha512-hGNCYRH8YyA5ItiBGSKrtMl21b2MAqfPkdI1mvwloNVqSU142IaGzqHN+OTovyeLIpQfonY01y7BAHb/UH4POg==} + peerDependencies: + next: ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + use-query-params: ^2.0.0 + 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 + use-query-params: 2.2.1(react-dom@18.2.0)(react@18.2.0) + dev: false + /next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: false @@ -7147,6 +7166,10 @@ packages: randombytes: 2.1.0 dev: true + /serialize-query-params@2.0.2: + resolution: {integrity: sha512-1chMo1dST4pFA9RDXAtF0Rbjaut4is7bzFbI1Z26IuMub68pNCILku85aYmeFhvnY//BXUPUhoRMjYcsT93J/Q==} + dev: false + /serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} @@ -7824,6 +7847,24 @@ packages: use-isomorphic-layout-effect: 1.1.2(@types/react@18.2.6)(react@18.2.0) dev: false + /use-query-params@2.2.1(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-i6alcyLB8w9i3ZK3caNftdb+UnbfBRNPDnc89CNQWkGRmDrm/gfydHvMBfVsQJRq3NoHOM2dt/ceBWG2397v1Q==} + peerDependencies: + '@reach/router': ^1.2.1 + react: '>=16.8.0' + react-dom: '>=16.8.0' + react-router-dom: '>=5' + peerDependenciesMeta: + '@reach/router': + optional: true + react-router-dom: + optional: true + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + serialize-query-params: 2.0.2 + dev: false + /use-sidecar@1.1.2(@types/react@18.2.6)(react@18.2.0): resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} engines: {node: '>=10'} diff --git a/prisma/seed.ts b/prisma/seed.ts index 0e5ac9c..abdc071 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -7,9 +7,13 @@ const defaultId = "11111111-1111-1111-1111-111111111111"; await prisma.organization.deleteMany({ where: { id: defaultId }, }); -await prisma.organization.create({ - data: { id: defaultId }, -}); + +// If there's an existing org, just seed into it +const org = + (await prisma.organization.findFirst({})) ?? + (await prisma.organization.create({ + data: { id: defaultId }, + })); await prisma.experiment.deleteMany({ where: { @@ -21,7 +25,7 @@ await prisma.experiment.create({ data: { id: defaultId, label: "Country Capitals Example", - organizationId: defaultId, + organizationId: org.id, }, }); @@ -103,30 +107,41 @@ await prisma.testScenario.deleteMany({ }, }); +const countries = [ + "Afghanistan", + "Albania", + "Algeria", + "Andorra", + "Angola", + "Antigua and Barbuda", + "Argentina", + "Armenia", + "Australia", + "Austria", + "Austrian Empire", + "Azerbaijan", + "Baden", + "Bahamas, The", + "Bahrain", + "Bangladesh", + "Barbados", + "Bavaria", + "Belarus", + "Belgium", + "Belize", + "Benin (Dahomey)", + "Bolivia", + "Bosnia and Herzegovina", + "Botswana", +]; await prisma.testScenario.createMany({ - data: [ - { - experimentId: defaultId, - sortIndex: 0, - variableValues: { - country: "Spain", - }, + data: countries.map((country, i) => ({ + experimentId: defaultId, + sortIndex: i, + variableValues: { + country: country, }, - { - experimentId: defaultId, - sortIndex: 1, - variableValues: { - country: "USA", - }, - }, - { - experimentId: defaultId, - sortIndex: 2, - variableValues: { - country: "Chile", - }, - }, - ], + })), }); const variants = await prisma.promptVariant.findMany({ diff --git a/src/components/OutputsTable/ScenarioPaginator.tsx b/src/components/OutputsTable/ScenarioPaginator.tsx new file mode 100644 index 0000000..264d803 --- /dev/null +++ b/src/components/OutputsTable/ScenarioPaginator.tsx @@ -0,0 +1,74 @@ +import { Box, HStack, IconButton } from "@chakra-ui/react"; +import { + BsChevronDoubleLeft, + BsChevronDoubleRight, + BsChevronLeft, + BsChevronRight, +} from "react-icons/bs"; +import { usePage, useScenarios } from "~/utils/hooks"; + +const ScenarioPaginator = () => { + const [page, setPage] = usePage(); + const { data } = useScenarios(); + + if (!data) return null; + + const { scenarios, startIndex, lastPage, count } = data; + + const nextPage = () => { + if (page < lastPage) { + setPage(page + 1, "replace"); + } + }; + + const prevPage = () => { + if (page > 1) { + setPage(page - 1, "replace"); + } + }; + + const goToLastPage = () => setPage(lastPage, "replace"); + const goToFirstPage = () => setPage(1, "replace"); + + return ( + + } + /> + } + /> + + {startIndex}-{startIndex + scenarios.length - 1} / {count} + + } + /> + } + /> + + ); +}; + +export default ScenarioPaginator; diff --git a/src/components/OutputsTable/ScenariosHeader.tsx b/src/components/OutputsTable/ScenariosHeader.tsx index f3ccdc6..37bb7ce 100644 --- a/src/components/OutputsTable/ScenariosHeader.tsx +++ b/src/components/OutputsTable/ScenariosHeader.tsx @@ -12,7 +12,12 @@ import { Spinner, } from "@chakra-ui/react"; import { cellPadding } from "../constants"; -import { useExperiment, useExperimentAccess, useHandledAsyncCallback } from "~/utils/hooks"; +import { + useExperiment, + useExperimentAccess, + useHandledAsyncCallback, + useScenarios, +} from "~/utils/hooks"; import { BsGear, BsPencil, BsPlus, BsStars } from "react-icons/bs"; import { useAppStore } from "~/state/store"; import { api } from "~/utils/api"; @@ -21,9 +26,10 @@ export const ActionButton = (props: ButtonProps) => (