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",
{ 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<"/experiments/[id]", { "id": string }>
| StaticRoute<"/experiments">
| StaticRoute<"/">;
| StaticRoute<"/">
| StaticRoute<"/world-champs">
| StaticRoute<"/world-champs/signup">;
interface StaticRoute<Pathname> {
pathname: Pathname;

2
pnpm-lock.yaml generated
View File

@@ -1,4 +1,4 @@
lockfileVersion: '6.0'
lockfileVersion: '6.1'
settings:
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 {
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 {

View File

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

View File

@@ -8,12 +8,16 @@ import {
PopoverTrigger,
PopoverContent,
Link,
useColorMode,
type StackProps,
} from "@chakra-ui/react";
import { type Session } from "next-auth";
import { signOut } from "next-auth/react";
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 ? (
<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}
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}

View File

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

View File

@@ -25,6 +25,7 @@ const MyApp: AppType<{ session: Session | null }> = ({
<meta
name="og:description"
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 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 { 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

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) =>
dayjs.duration(dayjs(date).diff(dayjs())).humanize(true);
export default dayjs;