+
{taskTypeComponent()}
{
);
};
+
+const scrollToTop = (element: HTMLElement) => {
+ while (element) {
+ element.scrollTop = 0;
+ element = element.parentElement;
+ }
+};
diff --git a/website/src/components/Tasks/Task/index.tsx b/website/src/components/Tasks/Task/index.tsx
new file mode 100644
index 00000000..92a057a4
--- /dev/null
+++ b/website/src/components/Tasks/Task/index.tsx
@@ -0,0 +1 @@
+export * from "./Task";
diff --git a/website/src/components/Tasks/TaskHeader/TaskHeader.tsx b/website/src/components/Tasks/TaskHeader/TaskHeader.tsx
new file mode 100644
index 00000000..33c2bd97
--- /dev/null
+++ b/website/src/components/Tasks/TaskHeader/TaskHeader.tsx
@@ -0,0 +1,35 @@
+import { HStack, IconButton, Link, Stack, Text, useColorModeValue } from "@chakra-ui/react";
+import { FiHelpCircle } from "react-icons/fi";
+import type { TaskInfo } from "src/components/Tasks/TaskTypes";
+
+interface TaskHeaderProps {
+ /**
+ * The `TaskInfo` representing how we present the task to a user.
+ */
+ taskType: TaskInfo;
+}
+
+/**
+ * Presents the Task label, instructions, and help link
+ */
+const TaskHeader = ({ taskType }: TaskHeaderProps) => {
+ const labelColor = useColorModeValue("gray.600", "gray.400");
+ const titleColor = useColorModeValue("gray.800", "gray.300");
+ return (
+
+
+
+ {taskType.label}
+
+
+ } />
+
+
+
+ {taskType.overview}
+
+
+ );
+};
+
+export { TaskHeader };
diff --git a/website/src/components/Tasks/TaskHeader/index.tsx b/website/src/components/Tasks/TaskHeader/index.tsx
new file mode 100644
index 00000000..d328d709
--- /dev/null
+++ b/website/src/components/Tasks/TaskHeader/index.tsx
@@ -0,0 +1 @@
+export * from "./TaskHeader";
diff --git a/website/src/components/Tasks/TaskTypes.tsx b/website/src/components/Tasks/TaskTypes.tsx
index 6f0bd2e1..868a9fb8 100644
--- a/website/src/components/Tasks/TaskTypes.tsx
+++ b/website/src/components/Tasks/TaskTypes.tsx
@@ -11,6 +11,8 @@ export interface TaskInfo {
category: TaskCategory;
pathname: string;
type: string;
+ help_link: string;
+ mode?: string;
overview?: string;
instruction?: string;
update_type: string;
@@ -25,6 +27,7 @@ export const TaskTypes: TaskInfo[] = [
desc: "Help us improve Open Assistant by starting a random task.",
category: TaskCategory.Tasks,
pathname: "/tasks/random",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
type: "random",
update_type: "random",
},
@@ -34,6 +37,7 @@ export const TaskTypes: TaskInfo[] = [
desc: "Write initial prompts to help Open Assistant to try replying to diverse messages.",
category: TaskCategory.Create,
pathname: "/create/initial_prompt",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
type: "initial_prompt",
overview: "Create an initial message to send to the assistant",
instruction: "Provide the initial prompt",
@@ -44,6 +48,7 @@ export const TaskTypes: TaskInfo[] = [
desc: "Chat with Open Assistant and help improve it’s responses as you interact with it.",
category: TaskCategory.Create,
pathname: "/create/user_reply",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/reply_as_user",
type: "prompter_reply",
overview: "Given the following conversation, provide an adequate reply",
instruction: "Provide the user's reply",
@@ -54,6 +59,7 @@ export const TaskTypes: TaskInfo[] = [
desc: "Help Open Assistant improve its responses to conversations with other users.",
category: TaskCategory.Create,
pathname: "/create/assistant_reply",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/reply_as_assistant",
type: "assistant_reply",
overview: "Given the following conversation, provide an adequate reply",
instruction: "Provide the assistant's reply",
@@ -65,6 +71,8 @@ export const TaskTypes: TaskInfo[] = [
category: TaskCategory.Evaluate,
desc: "Help Open Assistant improve its responses to conversations with other users.",
pathname: "/evaluate/rank_user_replies",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
+ overview: "Given the following User replies, sort them from best to worst, best being first, worst being last.",
type: "rank_prompter_replies",
update_type: "message_ranking",
unchanged_title: "Order Unchanged",
@@ -75,6 +83,9 @@ export const TaskTypes: TaskInfo[] = [
desc: "Score prompts given by Open Assistant based on their accuracy and readability.",
category: TaskCategory.Evaluate,
pathname: "/evaluate/rank_assistant_replies",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
+ overview:
+ "Given the following Assistant replies, sort them from best to worst, best being first, worst being last.",
type: "rank_assistant_replies",
update_type: "message_ranking",
unchanged_title: "Order Unchanged",
@@ -85,19 +96,23 @@ export const TaskTypes: TaskInfo[] = [
desc: "Score prompts given by Open Assistant based on their accuracy and readability.",
category: TaskCategory.Evaluate,
pathname: "/evaluate/rank_initial_prompts",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
+ overview: "Given the following inital prompts, sort them from best to worst, best being first, worst being last.",
type: "rank_initial_prompts",
update_type: "message_ranking",
unchanged_title: "Order Unchanged",
unchanged_message: "You have not changed the order of the prompts. Are you sure you would like to continue?",
},
- // label
+ // label (full)
{
label: "Label Initial Prompt",
desc: "Provide labels for a prompt.",
category: TaskCategory.Label,
pathname: "/label/label_initial_prompt",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
overview: "Provide labels for the following prompt",
type: "label_initial_prompt",
+ mode: "full",
update_type: "text_labels",
},
{
@@ -105,8 +120,10 @@ export const TaskTypes: TaskInfo[] = [
desc: "Provide labels for a prompt.",
category: TaskCategory.Label,
pathname: "/label/label_prompter_reply",
- overview: "Given the following discussion, provide labels for the final promp",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/label_prompter_reply",
+ overview: "Given the following discussion, provide labels for the final prompt",
type: "label_prompter_reply",
+ mode: "full",
update_type: "text_labels",
},
{
@@ -114,8 +131,44 @@ export const TaskTypes: TaskInfo[] = [
desc: "Provide labels for a prompt.",
category: TaskCategory.Label,
pathname: "/label/label_assistant_reply",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/tasks/label_assistant_reply",
overview: "Given the following discussion, provide labels for the final prompt.",
type: "label_assistant_reply",
+ mode: "full",
+ update_type: "text_labels",
+ },
+ // label (simple)
+ {
+ label: "Classify Initial Prompt",
+ desc: "Provide labels for a prompt.",
+ category: TaskCategory.Label,
+ pathname: "/label/label_initial_prompt",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
+ overview: "Read the following prompt and then answer the question about it.",
+ type: "label_initial_prompt",
+ mode: "simple",
+ update_type: "text_labels",
+ },
+ {
+ label: "Classify Prompter Reply",
+ desc: "Provide labels for a prompt.",
+ category: TaskCategory.Label,
+ pathname: "/label/label_prompter_reply",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
+ overview: "Read the following conversation and then answer the question about the last prompt in the discussion.",
+ type: "label_prompter_reply",
+ mode: "simple",
+ update_type: "text_labels",
+ },
+ {
+ label: "Classify Assistant Reply",
+ desc: "Provide labels for a prompt.",
+ category: TaskCategory.Label,
+ pathname: "/label/label_assistant_reply",
+ help_link: "https://projects.laion.ai/Open-Assistant/docs/guides/prompting",
+ overview: "Read the following conversation and then answer the question about the last prompt in the discussion.",
+ type: "label_assistant_reply",
+ mode: "simple",
update_type: "text_labels",
},
];
diff --git a/website/src/lib/auth.ts b/website/src/lib/auth.ts
index 803550a5..42c6cf79 100644
--- a/website/src/lib/auth.ts
+++ b/website/src/lib/auth.ts
@@ -1,11 +1,12 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { getToken, JWT } from "next-auth/jwt";
+import { Role } from "src/components/RoleSelect";
/**
* Wraps any API Route handler and verifies that the user does not have the
* specified role. Returns a 403 if they do, otherwise runs the handler.
*/
-const withoutRole = (role: string, handler: (arg0: NextApiRequest, arg1: NextApiResponse, arg2: JWT) => void) => {
+const withoutRole = (role: Role, handler: (arg0: NextApiRequest, arg1: NextApiResponse, arg2: JWT) => void) => {
return async (req: NextApiRequest, res: NextApiResponse) => {
const token = await getToken({ req });
if (!token || token.role === role) {
@@ -20,7 +21,7 @@ const withoutRole = (role: string, handler: (arg0: NextApiRequest, arg1: NextApi
* Wraps any API Route handler and verifies that the user has the appropriate
* role before running the handler. Returns a 403 otherwise.
*/
-const withRole = (role: string, handler: (arg0: NextApiRequest, arg1: NextApiResponse) => void) => {
+const withRole = (role: Role, handler: (arg0: NextApiRequest, arg1: NextApiResponse) => void) => {
return async (req: NextApiRequest, res: NextApiResponse) => {
const token = await getToken({ req });
if (!token || token.role !== role) {
diff --git a/website/src/lib/oasst_api_client.ts b/website/src/lib/oasst_api_client.ts
index f94d8b10..d48a987c 100644
--- a/website/src/lib/oasst_api_client.ts
+++ b/website/src/lib/oasst_api_client.ts
@@ -16,7 +16,13 @@ export class OasstError {
}
export class OasstApiClient {
- constructor(private readonly oasstApiUrl: string, private readonly oasstApiKey: string) {}
+ oasstApiUrl: string;
+ oasstApiKey: string;
+
+ constructor(oasstApiUrl: string, oasstApiKey: string) {
+ this.oasstApiUrl = oasstApiUrl;
+ this.oasstApiKey = oasstApiKey;
+ }
private async post(path: string, body: any): Promise
{
const resp = await fetch(`${this.oasstApiUrl}${path}`, {
@@ -107,7 +113,7 @@ export class OasstApiClient {
type: taskType,
user: {
id: userToken.sub,
- display_name: userToken.name || userToken.email,
+ display_name: userToken.name,
auth_method: "local",
},
});
@@ -140,7 +146,7 @@ export class OasstApiClient {
type: updateType,
user: {
id: userToken.sub,
- display_name: userToken.name || userToken.email,
+ display_name: userToken.name,
auth_method: "local",
},
task_id: taskId,
@@ -170,7 +176,7 @@ export class OasstApiClient {
const params = new URLSearchParams();
params.append("max_count", max_count.toString());
- // The backend API uses different query paramters depending on the
+ // The backend API uses different query parameters depending on the
// pagination direction but they both take the same cursor value.
// Depending on direction, pick the right query param.
if (cursor !== "") {
diff --git a/website/src/pages/404.tsx b/website/src/pages/404.tsx
index c3d01510..afe1d080 100644
--- a/website/src/pages/404.tsx
+++ b/website/src/pages/404.tsx
@@ -1,14 +1,10 @@
-import { Box, Button, Center, Link, Text, useColorModeValue } from "@chakra-ui/react";
+import { Box, Button, Center, Link, Text } from "@chakra-ui/react";
import Head from "next/head";
-import { useRouter } from "next/router";
import { FiAlertTriangle } from "react-icons/fi";
-import { PageEmptyState } from "src/components/EmptyState";
+import { EmptyState } from "src/components/EmptyState";
import { getTransparentHeaderLayout } from "src/components/Layout";
function Error() {
- const router = useRouter();
- const backgroundColor = useColorModeValue("white", "gray.800");
-
return (
<>
@@ -16,7 +12,7 @@ function Error() {
-
+
If you were trying to contribute data but ended up here, please file a bug.
500 - Open Assistant
-
-
- Sorry, We encountered a server error. We're not sure what went wrong
- Please file a but below and describe what you were trying to accomplish
- } variant="solid">
+
+
+
+ If you were trying to contribute data but ended up here, please file a bug.
+ }
+ variant="solid"
+ size="xs"
+ >
Report a Bug
-
-
+
+
>
);
}
+
+ServerError.getLayout = getTransparentHeaderLayout;
+
+export default ServerError;
diff --git a/website/src/pages/_app.tsx b/website/src/pages/_app.tsx
index 69d212e8..adadfe56 100644
--- a/website/src/pages/_app.tsx
+++ b/website/src/pages/_app.tsx
@@ -3,16 +3,24 @@ import "focus-visible";
import type { AppProps } from "next/app";
import { SessionProvider } from "next-auth/react";
+import { appWithTranslation } from "next-i18next";
import { FlagsProvider } from "react-feature-flags";
import { getDefaultLayout, NextPageWithLayout } from "src/components/Layout";
import flags from "src/flags";
+import { SWRConfig, SWRConfiguration } from "swr";
+import nextI18NextConfig from "../../next-i18next.config.js";
import { Chakra, getServerSideProps } from "../styles/Chakra";
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};
+const swrConfig: SWRConfiguration = {
+ revalidateOnFocus: false,
+ revalidateOnMount: true,
+};
+
function MyApp({ Component, pageProps: { session, cookies, ...pageProps } }: AppPropsWithLayout) {
const getLayout = Component.getLayout ?? getDefaultLayout;
const page = getLayout( );
@@ -20,10 +28,12 @@ function MyApp({ Component, pageProps: { session, cookies, ...pageProps } }: App
return (
- {page}
+
+ {page}
+
);
}
export { getServerSideProps };
-export default MyApp;
+export default appWithTranslation(MyApp, nextI18NextConfig);
diff --git a/website/src/pages/account/edit.tsx b/website/src/pages/account/edit.tsx
index 497e8238..fe8e8981 100644
--- a/website/src/pages/account/edit.tsx
+++ b/website/src/pages/account/edit.tsx
@@ -2,27 +2,11 @@ import { Button, Input, InputGroup } from "@chakra-ui/react";
import Head from "next/head";
import Router from "next/router";
import { useSession } from "next-auth/react";
-import React, { useState } from "react";
+import React from "react";
+import { Control, useForm, useWatch } from "react-hook-form";
export default function Account() {
const { data: session } = useSession();
- const [username, setUsername] = useState("");
-
- const updateUser = async (e: React.SyntheticEvent) => {
- e.preventDefault();
- try {
- const body = { username };
- await fetch("/api/username", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify(body),
- });
- session.user.name = username;
- await Router.push("/account");
- } catch (error) {
- console.error(error);
- }
- };
if (!session) {
return;
@@ -39,21 +23,52 @@ export default function Account() {
{session.user.name || "No username"}
-
+
>
);
}
+
+const EditForm = () => {
+ const { data: session } = useSession();
+
+ const updateUser = async ({ username }: { username: string }) => {
+ try {
+ const body = { username };
+ await fetch("/api/username", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body),
+ });
+ session.user.name = username;
+ await Router.push("/account");
+ } catch (error) {
+ console.error(error);
+ }
+ };
+
+ const { register, handleSubmit, control } = useForm<{ username: string }>({
+ defaultValues: {
+ username: session?.user.name,
+ },
+ });
+
+ return (
+
+ );
+};
+
+const SubmitButton = ({ control }: { control: Control<{ username: string }> }) => {
+ const username = useWatch({ control, name: "username" });
+ return (
+
+ Submit
+
+ );
+};
diff --git a/website/src/pages/admin/manage_user/[id].tsx b/website/src/pages/admin/manage_user/[id].tsx
index 90698f4a..88bfced4 100644
--- a/website/src/pages/admin/manage_user/[id].tsx
+++ b/website/src/pages/admin/manage_user/[id].tsx
@@ -1,17 +1,28 @@
-import { Button, Container, FormControl, FormLabel, Input, Select, Stack, useToast } from "@chakra-ui/react";
-import { Field, Form, Formik } from "formik";
+import { Button, Card, CardBody, Container, FormControl, FormLabel, Input, Stack, useToast } from "@chakra-ui/react";
+import { InferGetServerSidePropsType } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import { useSession } from "next-auth/react";
import { useEffect } from "react";
+import { useForm } from "react-hook-form";
import { getAdminLayout } from "src/components/Layout";
+import { Role, RoleSelect } from "src/components/RoleSelect";
import { UserMessagesCell } from "src/components/UserMessagesCell";
import { post } from "src/lib/api";
import { oasstApiClient } from "src/lib/oasst_api_client";
import prisma from "src/lib/prismadb";
import useSWRMutation from "swr/mutation";
-const ManageUser = ({ user }) => {
+interface UserForm {
+ user_id: string;
+ id: string;
+ auth_method: string;
+ display_name: string;
+ role: Role;
+ notes: string;
+}
+
+const ManageUser = ({ user }: InferGetServerSidePropsType) => {
const toast = useToast();
const router = useRouter();
const { data: session, status } = useSession();
@@ -51,6 +62,10 @@ const ManageUser = ({ user }) => {
},
});
+ const { register, handleSubmit } = useForm({
+ defaultValues: user,
+ });
+
return (
<>
@@ -61,50 +76,31 @@ const ManageUser = ({ user }) => {
/>
-
- {
- trigger(values);
- }}
- >
-
-
+
+
+
+
+
+
@@ -125,7 +121,7 @@ export async function getServerSideProps({ query }) {
});
const user = {
...backend_user,
- role: local_user?.role || "general",
+ role: (local_user?.role || "general") as Role,
};
return {
props: {
diff --git a/website/src/pages/api/auth/[...nextauth].ts b/website/src/pages/api/auth/[...nextauth].ts
index 58b4d8dd..3d3dbaa4 100644
--- a/website/src/pages/api/auth/[...nextauth].ts
+++ b/website/src/pages/api/auth/[...nextauth].ts
@@ -2,12 +2,14 @@ import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { boolean } from "boolean";
import type { AuthOptions } from "next-auth";
import NextAuth from "next-auth";
+import { Provider } from "next-auth/providers";
import CredentialsProvider from "next-auth/providers/credentials";
import DiscordProvider from "next-auth/providers/discord";
import EmailProvider from "next-auth/providers/email";
import prisma from "src/lib/prismadb";
+import { generateUsername } from "unique-username-generator";
-const providers = [];
+const providers: Provider[] = [];
// Register an email magic link auth method.
providers.push(
@@ -39,11 +41,13 @@ if (boolean(process.env.DEBUG_LOGIN) || process.env.NODE_ENV === "development")
name: "Debug Credentials",
credentials: {
username: { label: "Username", type: "text" },
+ role: { label: "Role", type: "text" },
},
async authorize(credentials) {
const user = {
id: credentials.username,
name: credentials.username,
+ role: credentials.role,
};
// save the user to the database
await prisma.user.upsert({
@@ -87,6 +91,7 @@ export const authOptions: AuthOptions = {
async session({ session, token }) {
session.user.role = token.role;
session.user.isNew = token.isNew;
+ session.user.name = token.name;
return session;
},
/**
@@ -94,10 +99,11 @@ export const authOptions: AuthOptions = {
* This let's use forward the role to the session object.
*/
async jwt({ token }) {
- const { isNew, role } = await prisma.user.findUnique({
+ const { isNew, name, role } = await prisma.user.findUnique({
where: { id: token.sub },
- select: { role: true, isNew: true },
+ select: { name: true, role: true, isNew: true },
});
+ token.name = name;
token.role = role;
token.isNew = isNew;
return token;
@@ -107,7 +113,18 @@ export const authOptions: AuthOptions = {
/**
* Update the user's role after they have successfully signed in
*/
- async signIn({ user, account }) {
+ async signIn({ user, account, isNewUser }) {
+ if (isNewUser && account.provider === "email") {
+ await prisma.user.update({
+ data: {
+ name: generateUsername(),
+ },
+ where: {
+ id: user.id,
+ },
+ });
+ }
+
// Get the admin list for the user's auth type.
const adminForAccountType = adminUserMap.get(account.provider);
diff --git a/website/src/pages/api/leaderboard.ts b/website/src/pages/api/leaderboard.ts
index 10a9a3a7..592f3da5 100644
--- a/website/src/pages/api/leaderboard.ts
+++ b/website/src/pages/api/leaderboard.ts
@@ -6,7 +6,7 @@ import { LeaderboardTimeFrame } from "src/types/Leaderboard";
* Returns the set of valid labels that can be applied to messages.
*/
const handler = withoutRole("banned", async (req, res) => {
- const time_frame = req.query.time_frame as LeaderboardTimeFrame;
+ const time_frame = (req.query.time_frame as LeaderboardTimeFrame) || LeaderboardTimeFrame.day;
const { leaderboard } = await oasstApiClient.fetch_leaderboard(time_frame);
res.status(200).json(leaderboard);
});
diff --git a/website/src/pages/api/new_task/[task_type].ts b/website/src/pages/api/new_task/[task_type].ts
index 8fd2b26b..e77c5eb2 100644
--- a/website/src/pages/api/new_task/[task_type].ts
+++ b/website/src/pages/api/new_task/[task_type].ts
@@ -20,6 +20,7 @@ const handler = withoutRole("banned", async (req, res, token) => {
} catch (err) {
console.error(err);
res.status(500).json(err);
+ return;
}
// Store the task and link it to the user..
diff --git a/website/src/pages/api/username.ts b/website/src/pages/api/username.ts
new file mode 100644
index 00000000..ba71c4b5
--- /dev/null
+++ b/website/src/pages/api/username.ts
@@ -0,0 +1,20 @@
+import { withoutRole } from "src/lib/auth";
+import prisma from "src/lib/prismadb";
+
+/**
+ * Updates the user's `name` field in the `User` table.
+ */
+const handler = withoutRole("banned", async (req, res, token) => {
+ const { username } = req.body;
+ const { name } = await prisma.user.update({
+ where: {
+ id: token.sub,
+ },
+ data: {
+ name: username,
+ },
+ });
+ res.json({ name });
+});
+
+export default handler;
diff --git a/website/src/pages/api/username.tsx b/website/src/pages/api/username.tsx
deleted file mode 100644
index 6cf362c2..00000000
--- a/website/src/pages/api/username.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { getSession } from "next-auth/react";
-import prisma from "src/lib/prismadb";
-
-// POST /api/post
-// Required fields in body: title
-// Optional fields in body: content
-export default async function handle(req, res) {
- const { username } = req.body;
-
- const session = await getSession({ req });
- const result = await prisma.user.update({
- where: {
- email: session.user.email,
- },
- data: {
- name: username,
- },
- });
- res.json({ name: result.name });
-}
diff --git a/website/src/pages/auth/signin.tsx b/website/src/pages/auth/signin.tsx
index c2f05e69..ccadf55f 100644
--- a/website/src/pages/auth/signin.tsx
+++ b/website/src/pages/auth/signin.tsx
@@ -1,14 +1,17 @@
-import { Button, Input, Stack } from "@chakra-ui/react";
+import { Button, ButtonProps, Input, Stack, useColorModeValue } from "@chakra-ui/react";
import { useColorMode } from "@chakra-ui/react";
+import { GetServerSideProps } from "next";
import Head from "next/head";
import Link from "next/link";
import { useRouter } from "next/router";
-import { getCsrfToken, getProviders, signIn } from "next-auth/react";
+import { ClientSafeProvider, getProviders, signIn } from "next-auth/react";
import React, { useEffect, useRef, useState } from "react";
+import { useForm } from "react-hook-form";
import { FaBug, FaDiscord, FaEnvelope, FaGithub } from "react-icons/fa";
import { AuthLayout } from "src/components/AuthLayout";
import { Footer } from "src/components/Footer";
import { Header } from "src/components/Header";
+import { Role, RoleSelect } from "src/components/RoleSelect";
export type SignInErrorTypes =
| "Signin"
@@ -37,8 +40,11 @@ const errorMessages: Record = {
default: "Unable to sign in.",
};
-// eslint-disable-next-line @typescript-eslint/no-unused-vars
-function Signin({ csrfToken, providers }) {
+interface SigninProps {
+ providers: Awaited>;
+}
+
+function Signin({ providers }: SigninProps) {
const router = useRouter();
const { discord, email, github, credentials } = providers;
const emailEl = useRef(null);
@@ -55,23 +61,14 @@ function Signin({ csrfToken, providers }) {
}
}, [router]);
- const signinWithEmail = (ev: React.FormEvent) => {
- ev.preventDefault();
- signIn(email.id, { callbackUrl: "/dashboard", email: emailEl.current.value });
+ const signinWithEmail = (data: { email: string }) => {
+ signIn(email.id, { callbackUrl: "/dashboard", email: data.email });
};
- const debugUsernameEl = useRef(null);
- function signinWithDebugCredentials(ev: React.FormEvent) {
- ev.preventDefault();
- signIn(credentials.id, { callbackUrl: "/dashboard", username: debugUsernameEl.current.value });
- }
-
const { colorMode } = useColorMode();
const bgColorClass = colorMode === "light" ? "bg-gray-50" : "bg-chakra-gray-900";
const buttonBgColor = colorMode === "light" ? "#2563eb" : "#2563eb";
-
- const buttonColorScheme = colorMode === "light" ? "blue" : "dark-blue-btn";
-
+ const { register, handleSubmit } = useForm<{ email: string }>();
return (
@@ -80,19 +77,9 @@ function Signin({ csrfToken, providers }) {
- {credentials && (
-
- )}
+ {credentials && }
{email && (
-
)}
@@ -179,13 +159,61 @@ Signin.getLayout = (page) => (
export default Signin;
-export async function getServerSideProps() {
- const csrfToken = await getCsrfToken();
+const SigninButton = (props: ButtonProps) => {
+ const buttonColorScheme = useColorModeValue("blue", "dark-blue-btn");
+
+ return (
+ }
+ type="submit"
+ colorScheme={buttonColorScheme}
+ color="white"
+ {...props}
+ >
+ );
+};
+
+interface DebugSigninFormData {
+ username: string;
+ role: Role;
+}
+
+const DebugSigninForm = ({ credentials, bgColorClass }: { credentials: ClientSafeProvider; bgColorClass: string }) => {
+ const { register, handleSubmit } = useForm({
+ defaultValues: {
+ role: "general",
+ username: "dev",
+ },
+ });
+
+ function signinWithDebugCredentials(data: DebugSigninFormData) {
+ signIn(credentials.id, {
+ callbackUrl: "/dashboard",
+ ...data,
+ });
+ }
+
+ return (
+
+ );
+};
+
+export const getServerSideProps: GetServerSideProps = async () => {
const providers = await getProviders();
return {
props: {
- csrfToken,
providers,
},
};
-}
+};
diff --git a/website/src/pages/create/initial_prompt.tsx b/website/src/pages/create/initial_prompt.tsx
index dc4429d0..6a51ca25 100644
--- a/website/src/pages/create/initial_prompt.tsx
+++ b/website/src/pages/create/initial_prompt.tsx
@@ -19,8 +19,8 @@ const InitialPrompt = () => {
return (
<>
- Reply as Assistant
-
+ Initial Prompt
+
>
diff --git a/website/src/pages/create/user_reply.tsx b/website/src/pages/create/user_reply.tsx
index b28b7442..8d2981e5 100644
--- a/website/src/pages/create/user_reply.tsx
+++ b/website/src/pages/create/user_reply.tsx
@@ -19,8 +19,8 @@ const UserReply = () => {
return (
<>
- Reply as Assistant
-
+ Reply as User
+
>
diff --git a/website/src/pages/dashboard.tsx b/website/src/pages/dashboard.tsx
index 42481f1e..78a47fd4 100644
--- a/website/src/pages/dashboard.tsx
+++ b/website/src/pages/dashboard.tsx
@@ -1,23 +1,21 @@
+import { Flex } from "@chakra-ui/react";
import Head from "next/head";
-import { useSession } from "next-auth/react";
-import { LeaderboardTable, TaskOption } from "src/components/Dashboard";
+import { LeaderboardTable, TaskOption, WelcomeCard } from "src/components/Dashboard";
import { getDashboardLayout } from "src/components/Layout";
import { TaskCategory } from "src/components/Tasks/TaskTypes";
const Dashboard = () => {
- const { data: session } = useSession();
-
- // TODO(#670): Do something more meaningful when the user is new.
- console.log(session?.user?.isNew);
-
return (
<>
Dashboard - Open Assistant
-
-
+
+
+
+
+
>
);
};
diff --git a/website/src/pages/index.tsx b/website/src/pages/index.tsx
index 64b1a0d5..8fe5d852 100644
--- a/website/src/pages/index.tsx
+++ b/website/src/pages/index.tsx
@@ -1,6 +1,9 @@
+import { Box } from "@chakra-ui/react";
import Head from "next/head";
import { useRouter } from "next/router";
import { useSession } from "next-auth/react";
+import { useTranslation } from "next-i18next";
+import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useEffect } from "react";
import { CallToAction } from "src/components/CallToAction";
import { Faq } from "src/components/Faq";
@@ -10,6 +13,7 @@ import { getTransparentHeaderLayout } from "src/components/Layout";
const Home = () => {
const router = useRouter();
const { status } = useSession();
+ const { t } = useTranslation("index");
useEffect(() => {
if (status === "authenticated") {
router.push("/dashboard");
@@ -19,21 +23,24 @@ const Home = () => {
return (
<>
- Open Assistant
-
+ {t("title")}
+
-
+
-
+
>
);
};
Home.getLayout = getTransparentHeaderLayout;
+export const getStaticProps = async ({ locale }) => ({
+ props: {
+ ...(await serverSideTranslations(locale, ["index", "common"])),
+ },
+});
+
export default Home;
diff --git a/website/src/pages/leaderboard.tsx b/website/src/pages/leaderboard.tsx
index 131778c9..e53b0c52 100644
--- a/website/src/pages/leaderboard.tsx
+++ b/website/src/pages/leaderboard.tsx
@@ -1,4 +1,4 @@
-import { Box, Heading, Tabs, TabList, TabPanels, Tab, TabPanel } from "@chakra-ui/react";
+import { Box, Heading, Tab, TabList, TabPanel, TabPanels, Tabs } from "@chakra-ui/react";
import Head from "next/head";
import { getDashboardLayout } from "src/components/Layout";
import { LeaderboardGridCell } from "src/components/LeaderboardGridCell";
diff --git a/website/src/pages/messages/[id]/index.tsx b/website/src/pages/messages/[id]/index.tsx
index 7d47873f..f55c03cc 100644
--- a/website/src/pages/messages/[id]/index.tsx
+++ b/website/src/pages/messages/[id]/index.tsx
@@ -1,25 +1,17 @@
import { Box, Text, useColorModeValue } from "@chakra-ui/react";
import Head from "next/head";
-import { useState } from "react";
import { getDashboardLayout } from "src/components/Layout";
import { MessageLoading } from "src/components/Loading/MessageLoading";
import { MessageTableEntry } from "src/components/Messages/MessageTableEntry";
import { MessageWithChildren } from "src/components/Messages/MessageWithChildren";
import { get } from "src/lib/api";
-import useSWR from "swr";
+import { Message } from "src/types/Conversation";
+import useSWRImmutable from "swr/immutable";
-const MessageDetail = ({ id }) => {
+const MessageDetail = ({ id }: { id: string }) => {
const backgroundColor = useColorModeValue("white", "gray.800");
- const [parent, setParent] = useState(null);
- const { isLoading: isLoadingParent } = useSWR(id ? `/api/messages/${id}/parent` : null, get, {
- onSuccess: (data) => {
- setParent(data);
- },
- onError: () => {
- setParent(null);
- },
- });
+ const { isLoading: isLoadingParent, data: parent } = useSWRImmutable(`/api/messages/${id}/parent`, get);
if (isLoadingParent) {
return ;
diff --git a/website/src/pages/privacy-policy.tsx b/website/src/pages/privacy-policy.tsx
index cac820fb..1c94b669 100644
--- a/website/src/pages/privacy-policy.tsx
+++ b/website/src/pages/privacy-policy.tsx
@@ -88,7 +88,7 @@ const PrivacyPolicy = () => {
{
number: "4",
title: "Inquiries",
- desc: "When you contact us via e-mail, telephone or telefax, your inquiry, including all personal data arising thereof will be stored by us for the purpose of processing your request. We will not pass on these data without your consent. The processing of these data is based on Article 6 (1) (1) (b) GDPR, if your inquiry is related to the fulfilment of a contract concluded with us or required for the implementation of pre-contractual measures. Furthermore, the processing is based on Article 6 (1) (1) (f) GDPR, because we have a legitimate interest in the effective handling of requests sent to us. In addition, according to Article 6 (1) (1) (c) GDPR we are also entitled to the processing of the above-mentioned data, because we are legally bound to enable fast electronic contact and immediate communication. Of course, your data will only be used strictly according to purpose and only for processing and responding to your request. After final processing, your data will immediately be anonymized or deleted, unless we are bound by a legally prescribed storage period.",
+ desc: "When you contact us via e-mail, telephone or telefax, your inquiry, including all personal data arising thereof will be stored by us for the purpose of processing your request. We will not pass on these data without your consent. The processing of these data is based on Article 6 (1) (1) (b) GDPR, if your inquiry is related to the fulfillment of a contract concluded with us or required for the implementation of pre-contractual measures. Furthermore, the processing is based on Article 6 (1) (1) (f) GDPR, because we have a legitimate interest in the effective handling of requests sent to us. In addition, according to Article 6 (1) (1) (c) GDPR we are also entitled to the processing of the above-mentioned data, because we are legally bound to enable fast electronic contact and immediate communication. Of course, your data will only be used strictly according to purpose and only for processing and responding to your request. After final processing, your data will immediately be anonymized or deleted, unless we are bound by a legally prescribed storage period.",
sections: [],
},
{
diff --git a/website/src/pages/terms-of-service.tsx b/website/src/pages/terms-of-service.tsx
index f58a9084..b0e298ba 100644
--- a/website/src/pages/terms-of-service.tsx
+++ b/website/src/pages/terms-of-service.tsx
@@ -14,7 +14,7 @@ const TermsOfService = () => {
{
number: "1.1",
title: "",
- desc: `LAION (association in formation), Marie-Henning-Weg 143, 21035 Hamburg (hereinafter referred to as: "LAION") operates an online portal for the producing a machine learning model called Open Assistant using crowdsourced data.`,
+ desc: `LAION (association in formation), Marie-Henning-Weg 143, 21035 Hamburg (hereinafter referred to as: "LAION") operates an online portal for the producing a machine learning model called Open Assistant using crowd-sourced data.`,
},
{
number: "1.2",
diff --git a/website/src/styles/Home.module.css b/website/src/styles/Home.module.css
deleted file mode 100644
index e69de29b..00000000
diff --git a/website/src/styles/Theme/components/Card.ts b/website/src/styles/Theme/components/Card.ts
new file mode 100644
index 00000000..8cd66031
--- /dev/null
+++ b/website/src/styles/Theme/components/Card.ts
@@ -0,0 +1,27 @@
+import { cardAnatomy } from "@chakra-ui/anatomy";
+import { createMultiStyleConfigHelpers } from "@chakra-ui/react";
+
+const { definePartsStyle, defineMultiStyleConfig } = createMultiStyleConfigHelpers(cardAnatomy.keys);
+
+export const cardTheme = defineMultiStyleConfig({
+ baseStyle: definePartsStyle(({ colorMode }) => {
+ const isLightMode = colorMode === "light";
+ return {
+ container: {
+ backgroundColor: isLightMode ? "white" : "gray.700",
+ },
+ header: {},
+ body: {
+ padding: 6,
+ },
+ footer: {},
+ };
+ }),
+ variants: {
+ elevated: definePartsStyle({
+ container: {
+ borderRadius: "xl",
+ },
+ }),
+ },
+});
diff --git a/website/src/styles/Theme/index.ts b/website/src/styles/Theme/index.ts
index 718eab7f..37ca424d 100644
--- a/website/src/styles/Theme/index.ts
+++ b/website/src/styles/Theme/index.ts
@@ -2,6 +2,7 @@ import { type ThemeConfig, extendTheme } from "@chakra-ui/react";
import { Styles } from "@chakra-ui/theme-tools";
import { colors } from "./colors";
+import { cardTheme } from "./components/Card";
import { containerTheme } from "./components/Container";
const config: ThemeConfig = {
@@ -12,6 +13,7 @@ const config: ThemeConfig = {
const components = {
Container: containerTheme,
+ Card: cardTheme,
};
const breakpoints = {
diff --git a/website/src/test_pages/README.md b/website/src/test_pages/README.md
new file mode 100644
index 00000000..5e3e27a7
--- /dev/null
+++ b/website/src/test_pages/README.md
@@ -0,0 +1,4 @@
+# Page Tests
+
+Put all page tests in this directory with the patter `MyPage.test.jsx`. We can't place them in `src/pages` due to how
+NextJS generates page routes.
diff --git a/website/src/pages.test/about.test.tsx b/website/src/test_pages/about.test.tsx
similarity index 100%
rename from website/src/pages.test/about.test.tsx
rename to website/src/test_pages/about.test.tsx
diff --git a/website/src/types/Leaderboard.ts b/website/src/types/Leaderboard.ts
index 2e72fafc..21c91766 100644
--- a/website/src/types/Leaderboard.ts
+++ b/website/src/types/Leaderboard.ts
@@ -16,7 +16,7 @@ export interface LeaderboardReply {
}
export interface LeaderboardEntity {
- user_rank: number;
+ rank: number;
user_id: string;
username: string;
auth_method: string;
diff --git a/website/src/types/TaskReplyState.ts b/website/src/types/TaskReplyState.ts
index 648dae3c..100aed11 100644
--- a/website/src/types/TaskReplyState.ts
+++ b/website/src/types/TaskReplyState.ts
@@ -1,3 +1,7 @@
+export interface TaskReplyNotSubmittable {
+ content: T;
+ state: "NOT_SUBMITTABLE";
+}
export interface TaskReplyValid {
content: T;
state: "VALID";
@@ -11,4 +15,8 @@ export interface TaskReplyInValid {
state: "INVALID";
}
-export type TaskReplyState = TaskReplyValid | TaskReplyDefault | TaskReplyInValid;
+export type TaskReplyState =
+ | TaskReplyNotSubmittable
+ | TaskReplyValid
+ | TaskReplyDefault
+ | TaskReplyInValid;
diff --git a/website/src/types/utils.ts b/website/src/types/utils.ts
new file mode 100644
index 00000000..82c35036
--- /dev/null
+++ b/website/src/types/utils.ts
@@ -0,0 +1,3 @@
+// https://github.com/ts-essentials/ts-essentials/blob/25cae45c162f8784e3cdae8f43783d0c66370a57/lib/types.ts#L437
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export type ElementOf = T extends readonly (infer ET)[] ? ET : never;