Merge pull request #36 from LAION-AI/web-link-newui-to-backend

Link new UI to auth and backend
This commit is contained in:
Keith Stevens
2022-12-20 17:52:15 +09:00
committed by GitHub
21 changed files with 4449 additions and 338 deletions
@@ -1,6 +1,7 @@
version: "3.7"
services:
# This DB is for the FastAPI Backend.
db:
extends:
file: ../backend-development/docker-compose.yaml
@@ -10,6 +11,23 @@ services:
interval: 2s
timeout: 2s
retries: 10
# This DB is for Web Authentication and data caching.
webdb:
image: postgres
restart: always
ports:
- 5433:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 2s
timeout: 2s
retries: 10
# This lets you manually inspect the web and backend databases.
adminer:
extends:
file: ../backend-development/docker-compose.yaml
@@ -28,3 +46,15 @@ services:
condition: service_healthy
ports:
- "8080:8080"
# This fakes and SMTP email server. User registration emails can be found by going to
# localhost:1080 and opening the emails listed.
maildev:
image: maildev/maildev
restart: always
environment:
- MAILDEV_WEB_PORT=1080
- MAILDEV_SMTP_PORT=1025
ports:
- "1080:1080"
- "1025:1025"
+14
View File
@@ -0,0 +1,14 @@
# The database created by running the jobs in /scripts/frontend-development/docker-compose.yaml
DATABASE_URL=postgres://postgres:postgres@localhost:5433/ocgpt_website
# The FastAPI backend found by running the jobs in /scripts/frontend-development/docker-compose.yaml
FASTAPI_URL=http://localhost:8080
FASTAPI_KEY=1234
# A dev Auth Secret. Can be exposed if we never use this publically.
NEXTAUTH_SECRET=O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98=
# The SMTP host and port found by running the jobs in /scripts/frontend-development/docker-compose.yaml
EMAIL_SERVER_HOST=localhost
EMAIL_SERVER_PORT=1025
EMAIL_FROM=info@example.com
-8
View File
@@ -1,8 +0,0 @@
FASTAPI_URL=http://xamla.com:8080
FASTAPI_KEY=magic_key
DISCORD_CLIENT_ID=your_discord_bot_client_id
DISCORD_CLIENT_SECRET=your_discord_bot_client_secret
# Run "openssl rand -base64 32" to get this.
NEXTAUTH_SECRET=openssl_result
-1
View File
@@ -26,7 +26,6 @@ yarn-error.log*
.pnpm-debug.log*
# local env files
.env
.env*.local
# vercel
+3887 -45
View File
File diff suppressed because it is too large Load Diff
+5 -1
View File
@@ -10,6 +10,9 @@
"lint": "next lint"
},
"dependencies": {
"@chakra-ui/react": "^2.4.4",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@headlessui/react": "^1.7.7",
"@heroicons/react": "^2.0.13",
"@next-auth/prisma-adapter": "^1.0.5",
@@ -25,13 +28,14 @@
"eslint": "8.29.0",
"eslint-config-next": "13.0.6",
"focus-visible": "^5.2.0",
"framer-motion": "^7.10.2",
"framer-motion": "^6.5.1",
"next": "13.0.6",
"next-auth": "^4.18.6",
"nodemailer": "^6.8.0",
"postcss-focus-visible": "^7.1.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.7.1",
"swr": "^2.0.0",
"tailwindcss": "^3.2.4",
"use-debounce": "^9.0.2"
+28 -16
View File
@@ -1,4 +1,5 @@
import Link from "next/link";
import { signOut, useSession } from "next-auth/react";
import { Popover } from "@headlessui/react";
import { AnimatePresence, motion } from "framer-motion";
@@ -35,6 +36,31 @@ function MobileNavLink({ children, ...props }) {
);
}
function AccountButton() {
const { data: session } = useSession();
if (session) {
return (
<button
type="button"
onClick={() => signOut()}
className="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Log out
</button>
);
}
return (
<Link href="/auth/signin" aria-label="Home" className="flex items-center">
<button
type="button"
className="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Log in
</button>
</Link>
);
}
export function Header() {
return (
<header>
@@ -93,14 +119,7 @@ export function Header() {
<MobileNavLink href="#faqs">FAQs</MobileNavLink>
</div>
<div className="mt-8 flex flex-col gap-4">
<a href="/login">
<button
type="button"
className="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Log in
</button>
</a>
<AccountButton />
</div>
</Popover.Panel>
</>
@@ -109,14 +128,7 @@ export function Header() {
</>
)}
</Popover>
<a href="/login">
<button
type="button"
className="inline-flex items-center rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Log in
</button>
</a>
<AccountButton />
</div>
</Container>
</nav>
@@ -0,0 +1,53 @@
import { chakra, Box, HStack, useRadio, useRadioGroup } from "@chakra-ui/react";
const RatingRadioButton = (props) => {
const { option, ...radioProps } = props;
const { getInputProps, getCheckboxProps } = useRadio(radioProps);
const input = getInputProps();
const checkbox = getCheckboxProps();
return (
<Box as="label">
<input {...input} />
<Box
{...checkbox}
cursor="pointer"
borderWidth="1px"
borderRadius="md"
boxShadow="md"
_checked={{
bg: "blue.200",
color: "white",
borderColor: "blue.200",
}}
_focus={{
boxShadow: "outline",
}}
px={5}
py={3}
>
{option}
</Box>
</Box>
);
};
const RatingRadioGroup = (props) => {
const { min, max, onChange, ...rest } = props;
const { value, getRadioProps, getRootProps } = useRadioGroup({
defaultValue: min,
onChange: onChange,
});
const options = Array.from(new Array(1 + max - min), (x, i) => i + min);
return (
<HStack {...getRootProps()}>
{options.map((option) => (
<RatingRadioButton key={option} option={option} {...getRadioProps({ value: option })} />
))}
</HStack>
);
};
export default RatingRadioGroup;
+8
View File
@@ -0,0 +1,8 @@
import axios from "axios";
/**
* A minimal Axios based fetcher.
*/
const fetcher = (url) => axios.get(url).then((res) => res.data);
export default fetcher;
+8
View File
@@ -0,0 +1,8 @@
const poster = (url, { arg }) => {
return fetch(url, {
method: "POST",
body: JSON.stringify(arg),
});
};
export default poster;
+8
View File
@@ -0,0 +1,8 @@
export { default } from "next-auth/middleware";
/**
* Guards all pages under `/grading` and redirects them to the sign in page.
*/
export const config = {
matcher: ["/grading/:path*"],
};
+6 -3
View File
@@ -1,3 +1,4 @@
import { ChakraProvider } from "@chakra-ui/react";
import { SessionProvider } from "next-auth/react";
import "../styles/globals.css";
@@ -5,9 +6,11 @@ import "focus-visible";
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
<ChakraProvider>
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
</ChakraProvider>
);
}
@@ -0,0 +1,46 @@
import NextAuth from "next-auth";
import DiscordProvider from "next-auth/providers/discord";
import EmailProvider from "next-auth/providers/email";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import prisma from "src/lib/prismadb";
const providers = [];
// Register an email magic link auth method.
providers.push(
EmailProvider({
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
})
);
if (process.env.DISCORD_CLIENT_ID) {
providers.push(
DiscordProvider({
clientId: process.env.DISCORD_CLIENT_ID,
clientSecret: process.env.DISCORD_CLIENT_SECRET,
})
);
}
export const authOptions = {
// Ensure we can store user data in a database.
adapter: PrismaAdapter(prisma),
providers,
pages: {
signIn: "/auth/signin",
},
session: {
strategy: "jwt",
},
};
export default NextAuth(authOptions);
@@ -0,0 +1,71 @@
import { getToken } from "next-auth/jwt";
import { authOptions } from "src/pages/api/auth/[...nextauth]";
/**
* Returns a new task created from the Task Backend. We do a few things here:
*
* 1) Get the task from the backend and register the requesting user.
* 2) Store the task in our local database.
* 3) Send and Ack to the Task Backend with our local id for the task.
* 4) Return everything to the client.
*/
export default async (req, res) => {
const { task_type } = req.query;
const token = await getToken({ req });
// Return nothing if the user isn't registered.
if (!token) {
res.status(401).end();
return;
}
// Fetch the new task.
//
// This needs to be refactored into an easier to use library.
const taskRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/`, {
method: "POST",
headers: {
"X-API-Key": process.env.FASTAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: task_type,
user: {
id: token.sub,
display_name: token.name || token.email,
auth_method: "local",
},
}),
});
const task = await taskRes.json();
// Store the task and link it to the user..
const registeredTask = await prisma.registeredTask.create({
data: {
task,
user: {
connect: {
id: token.sub,
},
},
},
});
// Update the backend with our Task ID
const ackRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/${task.id}/ack`, {
method: "POST",
headers: {
"X-API-Key": process.env.FASTAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
post_id: registeredTask.id,
}),
});
const ack = await ackRes.json();
// Send the results to the client.
res.status(200).json(registeredTask);
};
+78
View File
@@ -0,0 +1,78 @@
import { getToken } from "next-auth/jwt";
import { authOptions } from "src/pages/api/auth/[...nextauth]";
/**
* Stores the task interaction with the Task Backend and then returns the next task generated.
*
* This implicity does a few things:
* 1) Stores the answer with the Task Backend.
* 2) Records the new task in our local database.
* 3) (TODO) Acks the new task with our local task ID to the Task Backend.
* 4) Returns the newly created task to the client.
*/
export default async (req, res) => {
const token = await getToken({ req });
// Return nothing if the user isn't registered.
if (!token) {
res.status(401).end();
return;
}
// Parse out the local task ID and the interaction contents.
const { id, content } = await JSON.parse(req.body);
// Log the interaction locally to create our user_post_id needed by the Task
// Backend.
const interaction = await prisma.taskInteraction.create({
data: {
content,
task: {
connect: {
id,
},
},
},
});
// Send the interaction to the Task Backend. This automatically fetches the
// next task in the sequence (or the done task).
const interactionRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`, {
method: "POST",
headers: {
"X-API-Key": process.env.FASTAPI_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({
type: "post_rating",
user: {
id: token.sub,
display_name: token.name || token.email,
auth_method: "local",
},
post_id: id,
user_post_id: interaction.id,
...content,
}),
});
const newTask = await interactionRes.json();
// Stores the new task with our database.
const newRegisteredTask = await prisma.registeredTask.create({
data: {
task: newTask,
user: {
connect: {
id: token.sub,
},
},
},
});
// TODO: Ack the task with the Task Backend using the newly created local
// task ID.
// Send the next task in the sequence to the client.
res.status(200).json(newRegisteredTask);
};
+62
View File
@@ -0,0 +1,62 @@
import { Button, Input } from "@chakra-ui/react";
import Head from "next/head";
import { FaDiscord, FaGithub, FaMagic } from "react-icons/fa";
import { getCsrfToken, getProviders, signIn } from "next-auth/react";
import { useRef } from "react";
import { AuthLayout } from "src/components/AuthLayout";
export default function Signin({ csrfToken, providers }) {
const { discord, email } = providers;
const emailEl = useRef(null);
const signinWithEmail = () => {
signIn(email.id, { callbackUrl: "/", email: emailEl.current.value });
};
return (
<>
<Head>
<title>Log in</title>
</Head>
<AuthLayout title="Log In" subtitle={<></>}>
<div className="space-y-6 w-100 flex flex-col justify-center items-center ">
{discord && (
<Button
leftIcon={<FaDiscord />}
colorScheme="blue"
size="lg"
w="36"
onClick={() => signIn(discord.id, { callbackUrl: "/" })}
>
Discord
</Button>
)}
<Button w="36" leftIcon={<FaGithub />} colorScheme="blue" size="lg">
Github
</Button>
{email && (
<div>
<Input variant="outline" placeholder="Email Address" ref={emailEl} />
<Button w="36" leftIcon={<FaMagic />} colorScheme="blue" size="lg" onClick={signinWithEmail}>
Email
</Button>
</div>
)}
</div>
</AuthLayout>
</>
);
}
export async function getServerSideProps(context) {
const csrfToken = await getCsrfToken();
const providers = await getProviders();
return {
props: {
csrfToken,
providers,
},
};
}
+129 -109
View File
@@ -1,113 +1,150 @@
import { HStack, Textarea } from "@chakra-ui/react";
import { QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
import { useSession, signIn, signOut } from "next-auth/react";
import { useEffect, useRef, useState } from "react";
import useSWRMutation from "swr/mutation";
import useSWRImmutable from "swr/immutable";
import RatingRadioGroup from "src/components/RatingRadioGroup";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
const GradeOutput = () => {
// Use an array of tasks that record the sequence of steps until a task is
// deemed complete.
const [tasks, setTasks] = useState([]);
const [rating, setRating] = useState(0);
// A quick reference to the input element. This should be factored into the
// component doing the actual task rendering.
const responseEl = useRef(null);
// Fetch the very fist task. We can ignore everything except isLoading
// because the onSuccess handler will update `tasks` when ready.
const { isLoading } = useSWRImmutable("/api/new_task/rate_summary", fetcher, {
onSuccess: (data) => {
console.log(data);
setTasks([data]);
},
});
// Every time we submit an answer to the latest task, let the backend handle
// all the interactions then add the resulting task to the queue. This ends
// when we hit the done task.
const { trigger, isMutating } = useSWRMutation("/api/update_task", poster, {
onSuccess: async (data) => {
const newTask = await data.json();
// This is the more efficient way to update a react state array.
setTasks((oldTasks) => [...oldTasks, newTask]);
},
});
// Trigger a mutation that updates the current task. We should probably
// signal somewhere that this interaction is being processed.
const submitResponse = (t) => {
trigger({
id: t.id,
content: {
rating: rating,
},
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (tasks.length == 0) {
return <div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800"></div>;
}
export default function OutputDetail(): JSX.Element {
return (
<>
<div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">
{/* Instrunction and Output panels */}
<section className="mb-8 lt-lg:mb-12 ">
<div className="grid lg:gap-x-12 lg:grid-cols-2">
{/* Instruction panel */}
<div className="rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Instruction</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{SAMPLE_PROMPT}</div>
<div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">
{/* Instrunction and Output panels */}
<section className="mb-8 lt-lg:mb-12 ">
<div className="grid lg:gap-x-12 lg:grid-cols-2">
{/* Instruction panel */}
<div className="rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Instruction</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">
{tasks[0].task.full_text}
</div>
</div>
</div>
{/* Output panel */}
<div className="mt-6 lg:mt-0 rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Output</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{SAMPLE_OUTPUT}</div>
</div>
{/* Form wrap*/}
<div className="p-6">
<h3 className="text-lg text-center font-medium leading-6 text-gray-900">Rating</h3>
<p className="text-center mt-1 text-sm text-gray-500">(1 = worst, 7 = best)</p>
{/* Output panel */}
<div className="mt-6 lg:mt-0 rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Output</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{tasks[0].task.summary}</div>
</div>
{/* Form wrap*/}
<div className="p-6">
<h3 className="text-lg text-center font-medium leading-6 text-gray-900">Rating</h3>
<p className="text-center mt-1 text-sm text-gray-500">
({tasks[0].task.scale.min} = worst, {tasks[0].task.scale.max} = best)
</p>
{/* Rating buttons */}
<div className="flex justify-center p-6">
<RatingButton rating={1} active={false}></RatingButton>
<RatingButton rating={2} active={false}></RatingButton>
<RatingButton rating={3} active={false}></RatingButton>
<RatingButton rating={4} active={false}></RatingButton>
<RatingButton rating={5} active={false}></RatingButton>
<RatingButton rating={6} active={false}></RatingButton>
<RatingButton rating={7} active={true}></RatingButton>
</div>
</div>
{/* Annotation checkboxes */}
<div className="flex justify-center px-10">
<ul>
{ANNOTATION_FLAGS.map((option, i) => {
return <AnnotationCheckboxLi option={option} key={i}></AnnotationCheckboxLi>;
})}
</ul>
</div>
{/* Rating buttons */}
<div className="flex justify-center p-6">
<textarea
id="notes"
name="notes"
className="mx-1 mb-1 max-w-lg shadow-sm rounded-md focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300"
placeholder="Optional notes"
defaultValue={""}
/>
<RatingRadioGroup min={tasks[0].task.scale.min} max={tasks[0].task.scale.max} onChange={setRating} />
</div>
</div>
</div>
</section>
{/* Info & controls */}
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="flex flex-col justify-self-start text-gray-700">
<div>
<span>
<b>Prompt</b>
</span>
<span className="ml-2">d1fb481a-e6cd-445d-9a15-8e2add854fe1</span>
{/* Annotation checkboxes */}
<div className="flex justify-center px-10">
<ul>
{ANNOTATION_FLAGS.map((option, i) => {
return <AnnotationCheckboxLi option={option} key={i}></AnnotationCheckboxLi>;
})}
</ul>
</div>
<div>
<span>
<b>Output</b>
</span>
<span className="ml-2">a5f85b0a-e11a-472c-bc73-946fdc2a6ec2</span>
<div className="flex justify-center p-6">
<Textarea name="notes" placeholder="Optional notes" />
</div>
</div>
</div>
</section>
{/* Skip / Submit controls */}
<div className="flex justify-center ml-auto">
<button
type="button"
className="mr-2 inline-flex items-center rounded-md border border-transparent bg-indigo-100 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Skip
</button>
<button
type="button"
className="nline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Submit
</button>
{/* Info & controls */}
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="flex flex-col justify-self-start text-gray-700">
<div>
<span>
<b>Prompt</b>
</span>
<span className="ml-2">{tasks[0].id}</span>
</div>
</section>
</div>
</>
);
}
<div>
<span>
<b>Output</b>
</span>
<span className="ml-2">{tasks.length === 2 ? tasks[1].id : "Submit your answer"}</span>
</div>
</div>
function RatingButton(props: { rating: number; active: boolean }): JSX.Element {
const activeClasses =
"inline-flex items-center mx-2 rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2";
const inactiveClasses =
"inline-flex items-center mx-2 rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2";
return (
<button type="button" className={props.active ? activeClasses : inactiveClasses}>
{props.rating}
</button>
{/* Skip / Submit controls */}
<div className="flex justify-center ml-auto">
<button
type="button"
className="mr-2 inline-flex items-center rounded-md border border-transparent bg-indigo-100 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Skip
</button>
<button
type="button"
onClick={() => submitResponse(tasks[0])}
className="nline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Submit
</button>
</div>
</section>
</div>
);
}
};
export default GradeOutput;
function AnnotationCheckboxLi(props: { option: annotationBool }): JSX.Element {
let AdditionalExplanation = null;
@@ -183,20 +220,3 @@ const ANNOTATION_FLAGS: annotationBool[] = [
labelText: "Expresses moral judgement",
},
];
const SAMPLE_PROMPT = "Please make a list of aspects of a good pull request. Briefly describe each aspect.";
const SAMPLE_OUTPUT = `Here are some aspects of a good pull request, which you may use to help your pull requests be good contributions and get accepted:
1. Communicate:
2. Pull request size:
* Summary
* Fix one problem
* Limit the scope
* Use commits
3. Use coding conventions:
4. Perform testing:
5. Rebase onto the master branch before creating your PR
6. Respond to reviews quickly
7. Thank reviewers for their suggestions
`;
+16 -6
View File
@@ -1,6 +1,7 @@
import { useSession } from "next-auth/react";
import Head from "next/head";
import Link from "next/link";
import { CallToAction } from "../components/CallToAction";
import { Faq } from "../components/Faq";
@@ -35,14 +36,23 @@ export default function Home() {
);
}
return (
<div className={styles.App}>
<header className={styles.AppHeader}>
{/* <img src={logo} className="App-logo" alt="logo" /> */}
<>
<Head>
<title>Open Assistant</title>
<meta
name="description"
content="Conversational AI for everyone. An open source project to create a chat enabled GPT LLM run by LAION and contributors around the world."
/>
</Head>
<Header />
<main>
<h2>Open Chat Gpt</h2>
<p>You are logged in</p>
</header>
</div>
<Link href="/grading/grade-output">~Rate a prompt and output now~</Link>
</main>
<Footer />
</>
);
}
-55
View File
@@ -1,55 +0,0 @@
import Head from "next/head";
import Link from "next/link";
import { AuthLayout } from "../components/AuthLayout";
export default function Login() {
return (
<>
<Head>
<title>Log in</title>
</Head>
<AuthLayout title="Log In" subtitle={<></>}>
<form>
<div className="space-y-6 w-100 flex flex-col justify-center items-center ">
<button
type="button"
className="mb-2 ml-6 w-40 flex items-center rounded-md border border-transparent bg-blue-600 px-6 py-3 text-base font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512" className="w-6 h-6">
<path
fill="currentColor"
d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z"
/>
</svg>
<span className="text-lg ml-3">DISCORD</span>
</button>
<button
type="button"
className="mb-2 ml-6 w-40 flex items-center rounded-md border border-transparent bg-blue-600 px-6 py-3 text-base font-medium text-white shadow-sm hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
<svg
className="mr-2 -ml-1 w-6 h-6"
aria-hidden="true"
focusable="false"
data-prefix="fab"
data-icon="github"
role="img"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 496 512"
>
<path
fill="currentColor"
d="M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3 .3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5 .3-6.2 2.3zm44.2-1.7c-2.9 .7-4.9 2.6-4.6 4.9 .3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3 .7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3 .3 2.9 2.3 3.9 1.6 1 3.6 .7 4.3-.7 .7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3 .7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3 .7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z"
></path>
</svg>
<span className="text-lg ml-1">Github</span>
</button>
</div>
</form>
</AuthLayout>
</>
);
}
-94
View File
@@ -1,94 +0,0 @@
import axios from "axios";
import Head from "next/head";
import Image from "next/image";
import { useSession, signIn, signOut } from "next-auth/react";
import { useEffect, useRef, useState } from "react";
import useSWRImmutable from "swr/immutable";
import useSWRMutation from "swr/mutation";
const fetcher = (url) => axios.get(url).then((res) => res.data);
/**
* A helper function to post updates to tasks.
* This ensures the content sent is serialized to JSON.
*/
async function sendRequest(url, { arg }) {
return fetch(url, {
method: "POST",
body: JSON.stringify(arg),
});
}
export default function NewPage() {
// Use an array of tasks that record the sequence of steps until a task is
// deemed complete.
const [tasks, setTasks] = useState([]);
// A quick reference to the input element. This should be factored into the
// component doing the actual task rendering.
const responseEl = useRef(null);
// Fetch the very fist task. We can ignore everything except isLoading
// because the onSuccess handler will update `tasks` when ready.
const { isLoading } = useSWRImmutable("/api/new_task", fetcher, {
onSuccess: (data) => {
setTasks([data]);
},
});
// Every time we submit an answer to the latest task, let the backend handle
// all the interactions then add the resulting task to the queue. This ends
// when we hit the done task.
const { trigger, isMutating } = useSWRMutation("/api/update_task", sendRequest, {
onSuccess: async (data) => {
const newTask = await data.json();
// This is the more efficient way to update a react state array.
setTasks((oldTasks) => [...oldTasks, newTask]);
},
});
// Trigger a mutation that updates the current task. We should probably
// signal somewhere that this interaction is being processed.
const submitResponse = (t) => {
trigger({
id: t.id,
content: {
rating: responseEl.current.value,
},
});
};
// Show something informative while loading the first task.
if (isLoading) {
return <div>Loading</div>;
}
// Iterate through each of the tasks and show it's contents, get a response to it, or show the done state.
//
// Right now this just works for the rating task.
//
// Displaying and fetching results for each task type should be factored into
// different components that handle the presentation and response structures.
// The results should be packaged into a single object with all the fields
// sent to the backend.
return (
<div>
{tasks.map((t) => (
<div key={t.id}>
<div>{t.task.type}</div>
<div>{t.task.text}</div>
{t.task.summary && (
<>
<div>{t.task.summary}</div>
<div>
{t.task.scale.min} to {t.task.scale.max}
</div>
<input type="text" ref={responseEl} />
<button onClick={() => submitResponse(t)}>Submit Response</button>
</>
)}
</div>
))}
</div>
);
}