diff --git a/.eslintrc.cjs b/.eslintrc.cjs index b9e381e..9386a4f 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -37,6 +37,7 @@ const config = { "warn", { vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" }, ], + "react/no-unescaped-entities": "off", }, }; diff --git a/@types/nextjs-routes.d.ts b/@types/nextjs-routes.d.ts index 18f5d78..b828b0f 100644 --- a/@types/nextjs-routes.d.ts +++ b/@types/nextjs-routes.d.ts @@ -17,7 +17,9 @@ declare module "nextjs-routes" { | DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }> | DynamicRoute<"/experiments/[id]", { "id": string }> | StaticRoute<"/experiments"> - | StaticRoute<"/">; + | StaticRoute<"/"> + | StaticRoute<"/world-champs"> + | StaticRoute<"/world-champs/signup">; interface StaticRoute { pathname: Pathname; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1b2919f..98ccc43 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,4 +1,4 @@ -lockfileVersion: '6.0' +lockfileVersion: '6.1' settings: autoInstallPeers: true diff --git a/prisma/migrations/20230801195916_add_world_champs/migration.sql b/prisma/migrations/20230801195916_add_world_champs/migration.sql new file mode 100644 index 0000000..2c08e5f --- /dev/null +++ b/prisma/migrations/20230801195916_add_world_champs/migration.sql @@ -0,0 +1,16 @@ +-- CreateTable +CREATE TABLE "WorldChampEntrant" ( + "id" UUID NOT NULL, + "userId" UUID NOT NULL, + "approved" BOOLEAN NOT NULL DEFAULT false, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "WorldChampEntrant_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "WorldChampEntrant_userId_key" ON "WorldChampEntrant"("userId"); + +-- AddForeignKey +ALTER TABLE "WorldChampEntrant" ADD CONSTRAINT "WorldChampEntrant_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 11c6760..80e4ee7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -112,24 +112,24 @@ model ScenarioVariantCell { model ModelResponse { id String @id @default(uuid()) @db.Uuid - inputHash String - requestedAt DateTime? - receivedAt DateTime? - output Json? - cost Float? - promptTokens Int? - completionTokens Int? - statusCode Int? - errorMessage String? - retryTime DateTime? - outdated Boolean @default(false) + inputHash String + requestedAt DateTime? + receivedAt DateTime? + output Json? + cost Float? + promptTokens Int? + completionTokens Int? + statusCode Int? + errorMessage String? + retryTime DateTime? + outdated Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt scenarioVariantCellId String @db.Uuid scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade) - outputEvaluations OutputEvaluation[] + outputEvaluations OutputEvaluation[] @@index([inputHash]) } @@ -150,8 +150,8 @@ model Evaluation { experimentId String @db.Uuid experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt outputEvaluations OutputEvaluation[] } @@ -162,7 +162,7 @@ model OutputEvaluation { result Float details String? - modelResponseId String @db.Uuid + modelResponseId String @db.Uuid modelResponse ModelResponse @relation(fields: [modelResponseId], references: [id], onDelete: Cascade) evaluationId String @db.Uuid @@ -179,8 +179,8 @@ model Organization { personalOrgUserId String? @unique @db.Uuid PersonalOrgUser User? @relation(fields: [personalOrgUserId], references: [id], onDelete: Cascade) - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt organizationUsers OrganizationUser[] experiments Experiment[] } @@ -208,6 +208,20 @@ model OrganizationUser { @@unique([organizationId, userId]) } +model WorldChampEntrant { + id String @id @default(uuid()) @db.Uuid + + userId String @db.Uuid + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + approved Boolean @default(false) + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([userId]) +} + model Account { id String @id @default(uuid()) @db.Uuid userId String @db.Uuid @@ -245,6 +259,7 @@ model User { sessions Session[] organizationUsers OrganizationUser[] organizations Organization[] + worldChampEntrant WorldChampEntrant? } model VerificationToken { diff --git a/src/components/nav/AppShell.tsx b/src/components/nav/AppShell.tsx index 5059be4..5610286 100644 --- a/src/components/nav/AppShell.tsx +++ b/src/components/nav/AppShell.tsx @@ -84,7 +84,11 @@ const NavSidebar = () => { /> )} - {user ? : } + {user ? ( + + ) : ( + + )} ) : ( @@ -29,12 +33,10 @@ export default function UserMenu({ user }: { user: Session }) { px={3} spacing={3} py={2} - borderColor={"gray.200"} - borderTopWidth={1} - borderBottomWidth={1} + {...rest} cursor="pointer" _hover={{ - bgColor: "gray.200", + bgColor: colorMode === "light" ? "gray.200" : "gray.700", }} > {profileImage} diff --git a/src/modelProviders/replicate-llama2/getCompletion.ts b/src/modelProviders/replicate-llama2/getCompletion.ts index 4cf1161..cdf9b1f 100644 --- a/src/modelProviders/replicate-llama2/getCompletion.ts +++ b/src/modelProviders/replicate-llama2/getCompletion.ts @@ -10,7 +10,7 @@ const replicate = new Replicate({ const modelIds: Record = { "7b-chat": "5ec5fdadd80ace49f5a2b2178cceeb9f2f77c493b85b1131002c26e6b2b13184", "13b-chat": "6b4da803a2382c08868c5af10a523892f38e2de1aafb2ee55b020d9efef2fdb8", - "70b-chat": "2d19859030ff705a87c746f7e96eea03aefb71f166725aee39692f1476566d48", + "70b-chat": "2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1", }; export async function getCompletion( diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index c160c90..7a67cd0 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -25,6 +25,7 @@ const MyApp: AppType<{ session: Session | null }> = ({ diff --git a/src/pages/world-champs/index.tsx b/src/pages/world-champs/index.tsx new file mode 100644 index 0000000..e9ea66f --- /dev/null +++ b/src/pages/world-champs/index.tsx @@ -0,0 +1,15 @@ +import { type GetServerSideProps } from "next"; + +// eslint-disable-next-line @typescript-eslint/require-await +export const getServerSideProps: GetServerSideProps = async () => { + return { + redirect: { + destination: "/world-champs/signup", + permanent: false, + }, + }; +}; + +export default function WorldChamps() { + return null; +} diff --git a/src/pages/world-champs/signup.tsx b/src/pages/world-champs/signup.tsx new file mode 100644 index 0000000..4495716 --- /dev/null +++ b/src/pages/world-champs/signup.tsx @@ -0,0 +1,201 @@ +import { + Box, + type BoxProps, + Button, + DarkMode, + GlobalStyle, + HStack, + Heading, + Icon, + Link, + Table, + Tbody, + Td, + Text, + type TextProps, + Th, + Tr, + VStack, + useInterval, +} from "@chakra-ui/react"; +import { signIn, useSession } from "next-auth/react"; +import Head from "next/head"; +import { useCallback, useState } from "react"; +import { BsGithub } from "react-icons/bs"; +import UserMenu from "~/components/nav/UserMenu"; +import { api } from "~/utils/api"; +import dayjs from "~/utils/dayjs"; +import { useHandledAsyncCallback } from "~/utils/hooks"; + +// Shows how long until the competition starts. Refreshes every second +function CountdownTimer(props: { date: Date } & TextProps) { + const [now, setNow] = useState(dayjs(0)); + + useInterval(() => { + setNow(dayjs()); + }, 1000); + + const { date, ...rest } = props; + + const kickoff = dayjs(props.date); + const diff = kickoff.diff(now, "second"); + const days = Math.floor(diff / 86400); + const hours = Math.floor((diff % 86400) / 3600); + const minutes = Math.floor((diff % 3600) / 60); + const seconds = Math.floor(diff % 60); + + return ( + + + Kickoff in + {" "} + {days}d {hours}h {minutes}m {seconds}s + + ); +} + +function ApplicationStatus(props: BoxProps) { + const user = useSession().data; + const entrant = api.worldChamps.userStatus.useQuery().data; + const applyMutation = api.worldChamps.apply.useMutation(); + + const utils = api.useContext(); + + const [onSignIn] = useHandledAsyncCallback(async () => { + await signIn("github"); + }, []); + + const [onApply] = useHandledAsyncCallback(async () => { + await applyMutation.mutateAsync(); + await utils.worldChamps.userStatus.invalidate(); + }, []); + + const Wrapper = useCallback( + (wrapperProps: BoxProps) => ( + + ), + [props], + ); + + if (user === null) { + return ( + + + + ); + } else if (user) { + return ( + + + + + {entrant?.approved ? ( + + You're accepted! We'll send you more details before August 14th. + + ) : entrant ? ( + + Application submitted successfully! We'll notify you by email before August 14th. + + ) : ( + + )} + + + + ); + } + + return ; +} + +export default function Signup() { + return ( + + + + + 🏆 Prompt Engineering World Championships + + + + + + + 🏆 Prompt Engineering World Championships + + + + + + Think you have what it takes to be the best? Compete with the world's top prompt + engineers and see where you rank! + + + + Event Details + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
KickoffAugust 14
Prize$15,000 grand prize + smaller category prizes.
Events + Optimize prompts for multiple tasks selected from academic benchmarks and + real-world applications. +
ModelsSeparate "weight classes" for GPT 3.5, Claude Instant, and Llama 2.
QualificationsOpen to entrants with any level of experience.
CertificatesCertificate of mastery for all qualifying participants.
Cost + Free. We'll cover your inference budget. +
Questions? + + Email us + {" "} + with any follow-up questions! +
+
+
+
+ ); +} diff --git a/src/server/api/root.router.ts b/src/server/api/root.router.ts index 271879e..345e301 100644 --- a/src/server/api/root.router.ts +++ b/src/server/api/root.router.ts @@ -5,6 +5,7 @@ import { scenariosRouter } from "./routers/scenarios.router"; import { scenarioVariantCellsRouter } from "./routers/scenarioVariantCells.router"; import { templateVarsRouter } from "./routers/templateVariables.router"; import { evaluationsRouter } from "./routers/evaluations.router"; +import { worldChampsRouter } from "./routers/worldChamps.router"; /** * This is the primary router for your server. @@ -18,6 +19,7 @@ export const appRouter = createTRPCRouter({ scenarioVariantCells: scenarioVariantCellsRouter, templateVars: templateVarsRouter, evaluations: evaluationsRouter, + worldChamps: worldChampsRouter, }); // export type definition of API diff --git a/src/server/api/routers/worldChamps.router.ts b/src/server/api/routers/worldChamps.router.ts new file mode 100644 index 0000000..bd760a3 --- /dev/null +++ b/src/server/api/routers/worldChamps.router.ts @@ -0,0 +1,36 @@ +import { createTRPCRouter, protectedProcedure, publicProcedure } from "~/server/api/trpc"; +import { prisma } from "~/server/db"; +import { requireNothing } from "~/utils/accessControl"; + +export const worldChampsRouter = createTRPCRouter({ + userStatus: publicProcedure.query(async ({ input, ctx }) => { + const userId = ctx.session?.user.id; + + if (!userId) { + return null; + } + + return await prisma.worldChampEntrant.findUnique({ + where: { userId }, + }); + }), + + apply: protectedProcedure.mutation(async ({ ctx }) => { + const userId = ctx.session.user.id; + requireNothing(ctx); + + const existingEntrant = await prisma.worldChampEntrant.findUnique({ + where: { userId }, + }); + + if (existingEntrant) { + return existingEntrant; + } + + return await prisma.worldChampEntrant.create({ + data: { + userId, + }, + }); + }), +}); diff --git a/src/utils/dayjs.ts b/src/utils/dayjs.ts index 2425333..1d84bff 100644 --- a/src/utils/dayjs.ts +++ b/src/utils/dayjs.ts @@ -7,3 +7,5 @@ dayjs.extend(relativeTime); export const formatTimePast = (date: Date) => dayjs.duration(dayjs(date).diff(dayjs())).humanize(true); + +export default dayjs;