world champs signup

Basic landing page to sign up for the "world champs"
This commit is contained in:
Kyle Corbitt
2023-08-01 13:03:30 -07:00
parent c88266bcd4
commit 65a76cddc5
14 changed files with 323 additions and 26 deletions

View File

@@ -37,6 +37,7 @@ const config = {
"warn", "warn",
{ vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" }, { vars: "all", varsIgnorePattern: "^_", args: "after-used", argsIgnorePattern: "^_" },
], ],
"react/no-unescaped-entities": "off",
}, },
}; };

View File

@@ -17,7 +17,9 @@ declare module "nextjs-routes" {
| DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }> | DynamicRoute<"/api/trpc/[trpc]", { "trpc": string }>
| DynamicRoute<"/experiments/[id]", { "id": string }> | DynamicRoute<"/experiments/[id]", { "id": string }>
| StaticRoute<"/experiments"> | StaticRoute<"/experiments">
| StaticRoute<"/">; | StaticRoute<"/">
| StaticRoute<"/world-champs">
| StaticRoute<"/world-champs/signup">;
interface StaticRoute<Pathname> { interface StaticRoute<Pathname> {
pathname: Pathname; pathname: Pathname;

2
pnpm-lock.yaml generated
View File

@@ -1,4 +1,4 @@
lockfileVersion: '6.0' lockfileVersion: '6.1'
settings: settings:
autoInstallPeers: true autoInstallPeers: true

View File

@@ -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;

View File

@@ -112,24 +112,24 @@ model ScenarioVariantCell {
model ModelResponse { model ModelResponse {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
inputHash String inputHash String
requestedAt DateTime? requestedAt DateTime?
receivedAt DateTime? receivedAt DateTime?
output Json? output Json?
cost Float? cost Float?
promptTokens Int? promptTokens Int?
completionTokens Int? completionTokens Int?
statusCode Int? statusCode Int?
errorMessage String? errorMessage String?
retryTime DateTime? retryTime DateTime?
outdated Boolean @default(false) outdated Boolean @default(false)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
scenarioVariantCellId String @db.Uuid scenarioVariantCellId String @db.Uuid
scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade) scenarioVariantCell ScenarioVariantCell @relation(fields: [scenarioVariantCellId], references: [id], onDelete: Cascade)
outputEvaluations OutputEvaluation[] outputEvaluations OutputEvaluation[]
@@index([inputHash]) @@index([inputHash])
} }
@@ -150,8 +150,8 @@ model Evaluation {
experimentId String @db.Uuid experimentId String @db.Uuid
experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade) experiment Experiment @relation(fields: [experimentId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
outputEvaluations OutputEvaluation[] outputEvaluations OutputEvaluation[]
} }
@@ -162,7 +162,7 @@ model OutputEvaluation {
result Float result Float
details String? details String?
modelResponseId String @db.Uuid modelResponseId String @db.Uuid
modelResponse ModelResponse @relation(fields: [modelResponseId], references: [id], onDelete: Cascade) modelResponse ModelResponse @relation(fields: [modelResponseId], references: [id], onDelete: Cascade)
evaluationId String @db.Uuid evaluationId String @db.Uuid
@@ -179,8 +179,8 @@ model Organization {
personalOrgUserId String? @unique @db.Uuid 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()) createdAt DateTime @default(now())
updatedAt DateTime @updatedAt updatedAt DateTime @updatedAt
organizationUsers OrganizationUser[] organizationUsers OrganizationUser[]
experiments Experiment[] experiments Experiment[]
} }
@@ -208,6 +208,20 @@ model OrganizationUser {
@@unique([organizationId, userId]) @@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 { model Account {
id String @id @default(uuid()) @db.Uuid id String @id @default(uuid()) @db.Uuid
userId String @db.Uuid userId String @db.Uuid
@@ -245,6 +259,7 @@ model User {
sessions Session[] sessions Session[]
organizationUsers OrganizationUser[] organizationUsers OrganizationUser[]
organizations Organization[] organizations Organization[]
worldChampEntrant WorldChampEntrant?
} }
model VerificationToken { model VerificationToken {

View File

@@ -84,7 +84,11 @@ const NavSidebar = () => {
/> />
)} )}
</VStack> </VStack>
{user ? <UserMenu user={user} /> : <Divider />} {user ? (
<UserMenu user={user} borderColor={"gray.200"} borderTopWidth={1} borderBottomWidth={1} />
) : (
<Divider />
)}
<VStack spacing={0} align="center"> <VStack spacing={0} align="center">
<Link <Link
href="https://github.com/openpipe/openpipe" href="https://github.com/openpipe/openpipe"

View File

@@ -8,12 +8,16 @@ import {
PopoverTrigger, PopoverTrigger,
PopoverContent, PopoverContent,
Link, Link,
useColorMode,
type StackProps,
} from "@chakra-ui/react"; } from "@chakra-ui/react";
import { type Session } from "next-auth"; import { type Session } from "next-auth";
import { signOut } from "next-auth/react"; import { signOut } from "next-auth/react";
import { BsBoxArrowRight, BsChevronRight, BsPersonCircle } from "react-icons/bs"; import { BsBoxArrowRight, BsChevronRight, BsPersonCircle } from "react-icons/bs";
export default function UserMenu({ user }: { user: Session }) { export default function UserMenu({ user, ...rest }: { user: Session } & StackProps) {
const { colorMode } = useColorMode();
const profileImage = user.user.image ? ( const profileImage = user.user.image ? (
<Image src={user.user.image} alt="profile picture" boxSize={8} borderRadius="50%" /> <Image src={user.user.image} alt="profile picture" boxSize={8} borderRadius="50%" />
) : ( ) : (
@@ -29,12 +33,10 @@ export default function UserMenu({ user }: { user: Session }) {
px={3} px={3}
spacing={3} spacing={3}
py={2} py={2}
borderColor={"gray.200"} {...rest}
borderTopWidth={1}
borderBottomWidth={1}
cursor="pointer" cursor="pointer"
_hover={{ _hover={{
bgColor: "gray.200", bgColor: colorMode === "light" ? "gray.200" : "gray.700",
}} }}
> >
{profileImage} {profileImage}

View File

@@ -10,7 +10,7 @@ const replicate = new Replicate({
const modelIds: Record<ReplicateLlama2Input["model"], string> = { const modelIds: Record<ReplicateLlama2Input["model"], string> = {
"7b-chat": "5ec5fdadd80ace49f5a2b2178cceeb9f2f77c493b85b1131002c26e6b2b13184", "7b-chat": "5ec5fdadd80ace49f5a2b2178cceeb9f2f77c493b85b1131002c26e6b2b13184",
"13b-chat": "6b4da803a2382c08868c5af10a523892f38e2de1aafb2ee55b020d9efef2fdb8", "13b-chat": "6b4da803a2382c08868c5af10a523892f38e2de1aafb2ee55b020d9efef2fdb8",
"70b-chat": "2d19859030ff705a87c746f7e96eea03aefb71f166725aee39692f1476566d48", "70b-chat": "2c1608e18606fad2812020dc541930f2d0495ce32eee50074220b87300bc16e1",
}; };
export async function getCompletion( export async function getCompletion(

View File

@@ -25,6 +25,7 @@ const MyApp: AppType<{ session: Session | null }> = ({
<meta <meta
name="og:description" name="og:description"
content="OpenPipe is a powerful playground for quickly optimizing performance, cost, and speed across models." content="OpenPipe is a powerful playground for quickly optimizing performance, cost, and speed across models."
key="description"
/> />
<meta name="og:image" content="/og.png" key="og-image" /> <meta name="og:image" content="/og.png" key="og-image" />
<meta property="og:image:height" content="630" /> <meta property="og:image:height" content="630" />

View File

@@ -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;
}

View File

@@ -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 (
<Text {...rest}>
<Text as="span" fontWeight="bold">
Kickoff in
</Text>{" "}
{days}d {hours}h {minutes}m {seconds}s
</Text>
);
}
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) => (
<Box {...props} {...wrapperProps} minH="120px" alignItems="center" justifyItems="center" />
),
[props],
);
if (user === null) {
return (
<Wrapper>
<Button onClick={onSignIn} colorScheme="orange" leftIcon={<Icon as={BsGithub} />}>
Connect GitHub to apply
</Button>
</Wrapper>
);
} else if (user) {
return (
<Wrapper>
<HStack spacing={8}>
<UserMenu user={user} borderRadius={2} borderColor={"gray.700"} borderWidth={1} pr={6} />
<Box flex={1}>
{entrant?.approved ? (
<Text fontSize="sm">
You're accepted! We'll send you more details before August 14th.
</Text>
) : entrant ? (
<Text fontSize="sm">
Application submitted successfully! We'll notify you by email before August 14th.
</Text>
) : (
<Button onClick={onApply} colorScheme="orange">
Apply to compete
</Button>
)}
</Box>
</HStack>
</Wrapper>
);
}
return <Wrapper />;
}
export default function Signup() {
return (
<DarkMode>
<GlobalStyle />
<Head>
<title>🏆 Prompt Engineering World Championships</title>
<meta property="og:title" content="🏆 Prompt Engineering World Championships" key="title" />
<meta
property="og:description"
content="Think you have what it takes to be the best? Compete with the world's top prompt engineers and see where you rank!"
key="description"
/>
</Head>
<Box bgColor="gray.900" color="gray.200" minH="100vh" w="full">
<VStack mx="auto" py={24} maxW="2xl" align="start" fontSize="lg">
<Heading size="lg">🏆 Prompt Engineering World Championships</Heading>
<CountdownTimer
date={new Date("2023-08-14T00:00:00Z")}
fontSize="2xl"
alignSelf="center"
color="gray.500"
/>
<ApplicationStatus py={8} alignSelf="center" />
<Text fontSize="lg">
Think you have what it takes to be the best? Compete with the world's top prompt
engineers and see where you rank!
</Text>
<Heading size="lg" pt={12} alignSelf="left">
Event Details
</Heading>
<Table variant="simple">
<Tbody>
<Tr>
<Th>Kickoff</Th>
<Td>August 14</Td>
</Tr>
<Tr>
<Th>Prize</Th>
<Td>$15,000 grand prize + smaller category prizes.</Td>
</Tr>
<Tr>
<Th>Events</Th>
<Td>
Optimize prompts for multiple tasks selected from academic benchmarks and
real-world applications.
</Td>
</Tr>
<Tr>
<Th>Models</Th>
<Td>Separate "weight classes" for GPT 3.5, Claude Instant, and Llama 2.</Td>
</Tr>
<Tr>
<Th>Qualifications</Th>
<Td>Open to entrants with any level of experience.</Td>
</Tr>
<Tr>
<Th>Certificates</Th>
<Td>Certificate of mastery for all qualifying participants.</Td>
</Tr>
<Tr>
<Th>Cost</Th>
<Td>
<strong>Free</strong>. We'll cover your inference budget.
</Td>
</Tr>
<Tr>
<Th>Questions?</Th>
<Td>
<Link href="mailto:world-champs@openpipe.ai" textDecor="underline">
Email us
</Link>{" "}
with any follow-up questions!
</Td>
</Tr>
</Tbody>
</Table>
</VStack>
</Box>
</DarkMode>
);
}

View File

@@ -5,6 +5,7 @@ import { scenariosRouter } from "./routers/scenarios.router";
import { scenarioVariantCellsRouter } from "./routers/scenarioVariantCells.router"; import { scenarioVariantCellsRouter } from "./routers/scenarioVariantCells.router";
import { templateVarsRouter } from "./routers/templateVariables.router"; import { templateVarsRouter } from "./routers/templateVariables.router";
import { evaluationsRouter } from "./routers/evaluations.router"; import { evaluationsRouter } from "./routers/evaluations.router";
import { worldChampsRouter } from "./routers/worldChamps.router";
/** /**
* This is the primary router for your server. * This is the primary router for your server.
@@ -18,6 +19,7 @@ export const appRouter = createTRPCRouter({
scenarioVariantCells: scenarioVariantCellsRouter, scenarioVariantCells: scenarioVariantCellsRouter,
templateVars: templateVarsRouter, templateVars: templateVarsRouter,
evaluations: evaluationsRouter, evaluations: evaluationsRouter,
worldChamps: worldChampsRouter,
}); });
// export type definition of API // export type definition of API

View File

@@ -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,
},
});
}),
});

View File

@@ -7,3 +7,5 @@ dayjs.extend(relativeTime);
export const formatTimePast = (date: Date) => export const formatTimePast = (date: Date) =>
dayjs.duration(dayjs(date).diff(dayjs())).humanize(true); dayjs.duration(dayjs(date).diff(dayjs())).humanize(true);
export default dayjs;