mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-07-05 17:30:48 +08:00
Merge pull request #36 from LAION-AI/web-link-newui-to-backend
Link new UI to auth and backend
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -26,7 +26,6 @@ yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env*.local
|
||||
|
||||
# vercel
|
||||
|
||||
Generated
+3887
-45
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -0,0 +1,8 @@
|
||||
const poster = (url, { arg }) => {
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(arg),
|
||||
});
|
||||
};
|
||||
|
||||
export default poster;
|
||||
@@ -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*"],
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
`;
|
||||
|
||||
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user