mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-06-27 16:10:30 +08:00
Merge branch 'main' into cleanup
This commit is contained in:
Vendored
+2
-1
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"python.formatting.provider": "black",
|
||||
"python.analysis.extraPaths": ["${workspaceFolder}/oasst-shared"]
|
||||
"python.analysis.extraPaths": ["${workspaceFolder}/oasst-shared"],
|
||||
"prettier.singleQuote": false
|
||||
}
|
||||
|
||||
@@ -125,6 +125,9 @@ services:
|
||||
- EMAIL_FROM=info@example.com
|
||||
- NEXTAUTH_URL=http://localhost:3000
|
||||
- DEBUG_LOGIN=true
|
||||
- NEXT_PUBLIC_CLOUDFARE_CAPTCHA_SITE_KEY=1x00000000000000000000AA
|
||||
- CLOUDFLARE_CAPTCHA_SERCERT_KEY=1x0000000000000000000000000000000AA
|
||||
- NEXT_PUBLIC_ENABLE_EMAIL_SIGNIN_CAPTCHA=true
|
||||
depends_on:
|
||||
webdb:
|
||||
condition: service_healthy
|
||||
|
||||
@@ -90,3 +90,17 @@ getting permission denied (using root user), you can try the following:
|
||||
# And remove the container
|
||||
docker rm -f <container id>
|
||||
```
|
||||
|
||||
### Docker Port Problems
|
||||
|
||||
Oftentimes people already have some Postgres instance running on the dev
|
||||
machine. To avoid port problems, change the ports in the `docker-compose.yml` to
|
||||
ones excluding `5433`, like:
|
||||
|
||||
1. Change `db.ports` to `- 5431:5431`.
|
||||
2. Add `POSTGRES_PORT: 5431` to `db.environment`
|
||||
3. Change `webdb.ports` to `- 5432:5431`
|
||||
4. Add `POSTGRES_PORT: 5431` to `db.environment`
|
||||
5. Add `- POSTGRES_PORT=5432` to `backend.environment`
|
||||
6. Change `web.environment.DATABASE_URL` to
|
||||
`postgres://postgres:postgres@webdb:5432/oasst_web`
|
||||
|
||||
@@ -17,3 +17,7 @@ NEXTAUTH_SECRET=O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98=
|
||||
EMAIL_SERVER_HOST=localhost
|
||||
EMAIL_SERVER_PORT=1025
|
||||
EMAIL_FROM=info@example.com
|
||||
|
||||
NEXT_PUBLIC_CLOUDFLARE_CAPTCHA_SITE_KEY=1x00000000000000000000AA
|
||||
CLOUDFLARE_CAPTCHA_SERCERT_KEY=1x0000000000000000000000000000000AA
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_SIGNIN_CAPTCHA=false
|
||||
|
||||
@@ -14,11 +14,16 @@ describe("signin flow", () => {
|
||||
cy.request("GET", "/api/auth/csrf")
|
||||
.then((response) => {
|
||||
const csrfToken = response.body.csrfToken;
|
||||
cy.request("POST", "/api/auth/signin/email", {
|
||||
callbackUrl: "/",
|
||||
email: emailAddress,
|
||||
csrfToken,
|
||||
json: "true",
|
||||
cy.request({
|
||||
method: "POST",
|
||||
url: "/api/auth/signin/email",
|
||||
body: {
|
||||
callbackUrl: "/",
|
||||
email: emailAddress,
|
||||
csrfToken,
|
||||
json: "true",
|
||||
captcha: "XXXX.DUMMY.TOKEN.XXXX",
|
||||
},
|
||||
});
|
||||
})
|
||||
.then((response) => {
|
||||
|
||||
@@ -54,11 +54,16 @@ Cypress.Commands.add("signInWithEmail", (emailAddress) => {
|
||||
cy.request("GET", "/api/auth/csrf")
|
||||
.then((response) => {
|
||||
const csrfToken = response.body.csrfToken;
|
||||
cy.request("POST", "/api/auth/signin/email", {
|
||||
callbackUrl: "/",
|
||||
email: emailAddress,
|
||||
csrfToken,
|
||||
json: "true",
|
||||
cy.request({
|
||||
method: "POST",
|
||||
url: "/api/auth/signin/email",
|
||||
body: {
|
||||
callbackUrl: "/",
|
||||
email: emailAddress,
|
||||
csrfToken,
|
||||
json: "true",
|
||||
captcha: "XXXX.DUMMY.TOKEN.XXXX",
|
||||
},
|
||||
});
|
||||
})
|
||||
.then(() => {
|
||||
|
||||
Generated
+16
@@ -15,6 +15,7 @@
|
||||
"@dnd-kit/utilities": "^3.2.1",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@marsidev/react-turnstile": "^0.0.7",
|
||||
"@next-auth/prisma-adapter": "^1.0.5",
|
||||
"@next/font": "^13.1.0",
|
||||
"@prisma/client": "^4.7.1",
|
||||
@@ -5518,6 +5519,15 @@
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@marsidev/react-turnstile": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-0.0.7.tgz",
|
||||
"integrity": "sha512-BWXZ6/ddE96cP/U3jkLO8wbJi6qOpE4wH67h6EkD57TRy4RSauBBYKRZkuUEpfgm2wdWoPukPz4LfnoV5KJGrQ==",
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mdx-js/mdx": {
|
||||
"version": "1.6.22",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz",
|
||||
@@ -42272,6 +42282,12 @@
|
||||
"@jridgewell/sourcemap-codec": "1.4.14"
|
||||
}
|
||||
},
|
||||
"@marsidev/react-turnstile": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@marsidev/react-turnstile/-/react-turnstile-0.0.7.tgz",
|
||||
"integrity": "sha512-BWXZ6/ddE96cP/U3jkLO8wbJi6qOpE4wH67h6EkD57TRy4RSauBBYKRZkuUEpfgm2wdWoPukPz4LfnoV5KJGrQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"@mdx-js/mdx": {
|
||||
"version": "1.6.22",
|
||||
"resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-1.6.22.tgz",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"@dnd-kit/utilities": "^3.2.1",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@marsidev/react-turnstile": "^0.0.7",
|
||||
"@next-auth/prisma-adapter": "^1.0.5",
|
||||
"@next/font": "^13.1.0",
|
||||
"@prisma/client": "^4.7.1",
|
||||
|
||||
@@ -77,5 +77,6 @@
|
||||
"label": "Classify Assistant Reply",
|
||||
"desc": "Provide labels for a prompt.",
|
||||
"overview": "Read the following conversation and then answer the question about the last reply in the discussion."
|
||||
}
|
||||
},
|
||||
"available_task_count": "{{count}} tasks available"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { useColorMode } from "@chakra-ui/react";
|
||||
import { Turnstile, TurnstileInstance, TurnstileProps } from "@marsidev/react-turnstile";
|
||||
import { forwardRef } from "react";
|
||||
|
||||
export const CloudFlareCaptcha = forwardRef<TurnstileInstance, Omit<TurnstileProps, "siteKey">>((props, ref) => {
|
||||
const { colorMode } = useColorMode();
|
||||
return (
|
||||
<Turnstile
|
||||
ref={ref}
|
||||
{...props}
|
||||
siteKey={process.env.NEXT_PUBLIC_CLOUDFLARE_CAPTCHA_SITE_KEY}
|
||||
options={{
|
||||
theme: colorMode,
|
||||
...props.options,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
CloudFlareCaptcha.displayName = "CloudFlareCaptcha";
|
||||
@@ -51,7 +51,7 @@ export const CollapsableText = ({
|
||||
<ModalContent alignItems="center">
|
||||
<ModalHeader>Full Text</ModalHeader>
|
||||
<ModalCloseButton />
|
||||
<ModalBody>{text}</ModalBody>
|
||||
<ModalBody style={{ whiteSpace: "pre-line" }}>{text}</ModalBody>
|
||||
</ModalContent>
|
||||
</ModalOverlay>
|
||||
</Modal>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import {
|
||||
Badge,
|
||||
Box,
|
||||
Card,
|
||||
Flex,
|
||||
GridItem,
|
||||
Heading,
|
||||
@@ -8,7 +10,6 @@ import {
|
||||
SimpleGrid,
|
||||
Spacer,
|
||||
Text,
|
||||
useColorModeValue,
|
||||
} from "@chakra-ui/react";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
@@ -19,19 +20,20 @@ import { TaskCategory, TaskInfo, TaskType } from "src/types/Task";
|
||||
|
||||
import { TaskCategoryLabels, TaskInfos } from "../Tasks/TaskTypes";
|
||||
|
||||
export type TaskCategoryItem = { taskType: TaskType; count: number };
|
||||
|
||||
export interface TasksOptionProps {
|
||||
content: Partial<Record<TaskCategory, TaskType[]>>;
|
||||
content: Partial<Record<TaskCategory, TaskCategoryItem[]>>;
|
||||
}
|
||||
|
||||
export const TaskOption = ({ content }: TasksOptionProps) => {
|
||||
const { t } = useTranslation(["dashboard", "tasks"]);
|
||||
const backgroundColor = useColorModeValue("white", "gray.700");
|
||||
|
||||
const taskInfoMap = useMemo(
|
||||
() =>
|
||||
Object.values(content)
|
||||
.flat()
|
||||
.reduce((obj, taskType) => {
|
||||
.reduce((obj, { taskType }) => {
|
||||
obj[taskType] = TaskInfos.filter((t) => t.type === taskType).pop();
|
||||
return obj;
|
||||
}, {} as Record<TaskType, TaskInfo>),
|
||||
@@ -40,7 +42,7 @@ export const TaskOption = ({ content }: TasksOptionProps) => {
|
||||
|
||||
return (
|
||||
<Box className="flex flex-col gap-14">
|
||||
{Object.entries(content).map(([category, taskTypes]) => (
|
||||
{Object.entries(content).map(([category, items]) => (
|
||||
<div key={category}>
|
||||
<Flex>
|
||||
<Heading size="lg" className="pb-4">
|
||||
@@ -52,24 +54,25 @@ export const TaskOption = ({ content }: TasksOptionProps) => {
|
||||
</ExternalLink>
|
||||
</Flex>
|
||||
<SimpleGrid columns={[1, 1, 2, 2, 3, 4]} gap={4}>
|
||||
{taskTypes
|
||||
.map((taskType) => taskInfoMap[taskType])
|
||||
{items
|
||||
.map(({ taskType, count }) => ({ ...taskInfoMap[taskType], count }))
|
||||
.map((item) => (
|
||||
<Link key={category + item.id} href={item.pathname}>
|
||||
<GridItem
|
||||
bg={backgroundColor}
|
||||
borderRadius="xl"
|
||||
boxShadow="base"
|
||||
className="flex flex-col justify-between h-full"
|
||||
>
|
||||
<Flex className="p-6 pb-10" flexDir="column" gap="3">
|
||||
<Heading size="md">{t(getTypeSafei18nKey(`tasks:${item.id}.label`))}</Heading>
|
||||
<Text size="sm">{t(getTypeSafei18nKey(`tasks:${item.id}.desc`))}</Text>
|
||||
<GridItem as={Card} height="100%" justifyContent="space-between">
|
||||
<Flex px="6" pt="6" flexDir="column" gap="3" justifyContent="space-between" height="100%">
|
||||
<Flex flexDir="column" gap="3">
|
||||
<Heading size="md">{t(getTypeSafei18nKey(`tasks:${item.id}.label`))}</Heading>
|
||||
<Text size="sm">{t(getTypeSafei18nKey(`tasks:${item.id}.desc`))}</Text>
|
||||
</Flex>
|
||||
<Box>
|
||||
<Badge textTransform="none">{t("tasks:available_task_count", { count: item.count })}</Badge>
|
||||
</Box>
|
||||
</Flex>
|
||||
<Text
|
||||
fontWeight="bold"
|
||||
color="white"
|
||||
borderBottomRadius="xl"
|
||||
mt="6"
|
||||
className="px-6 py-2 transition-colors duration-300 bg-blue-500 hover:bg-blue-600"
|
||||
>
|
||||
{t("go")} ->
|
||||
@@ -85,12 +88,20 @@ export const TaskOption = ({ content }: TasksOptionProps) => {
|
||||
};
|
||||
|
||||
export const allTaskOptions: TasksOptionProps["content"] = {
|
||||
[TaskCategory.Random]: [TaskType.random],
|
||||
[TaskCategory.Create]: [TaskType.initial_prompt, TaskType.prompter_reply, TaskType.assistant_reply],
|
||||
[TaskCategory.Evaluate]: [
|
||||
TaskType.rank_initial_prompts,
|
||||
TaskType.rank_prompter_replies,
|
||||
TaskType.rank_assistant_replies,
|
||||
[TaskCategory.Random]: [{ taskType: TaskType.random, count: 0 }],
|
||||
[TaskCategory.Create]: [
|
||||
{ taskType: TaskType.initial_prompt, count: 0 },
|
||||
{ taskType: TaskType.prompter_reply, count: 0 },
|
||||
{ taskType: TaskType.assistant_reply, count: 0 },
|
||||
],
|
||||
[TaskCategory.Evaluate]: [
|
||||
{ taskType: TaskType.rank_initial_prompts, count: 0 },
|
||||
{ taskType: TaskType.rank_prompter_replies, count: 0 },
|
||||
{ taskType: TaskType.rank_assistant_replies, count: 0 },
|
||||
],
|
||||
[TaskCategory.Label]: [
|
||||
{ taskType: TaskType.label_initial_prompt, count: 0 },
|
||||
{ taskType: TaskType.label_prompter_reply, count: 0 },
|
||||
{ taskType: TaskType.label_assistant_reply, count: 0 },
|
||||
],
|
||||
[TaskCategory.Label]: [TaskType.label_initial_prompt, TaskType.label_prompter_reply, TaskType.label_assistant_reply],
|
||||
};
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
import { Box, forwardRef, useColorMode } from "@chakra-ui/react";
|
||||
import { useMemo } from "react";
|
||||
import { Message } from "src/types/Conversation";
|
||||
|
||||
export const MessageView = forwardRef<Partial<Message>, "div">((message: Partial<Message>, ref) => {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
const bgColor = useMemo(() => {
|
||||
if (colorMode === "light") {
|
||||
return message.is_assistant ? "gray.800" : "blue.600";
|
||||
} else {
|
||||
return message.is_assistant ? "black" : "blue.600";
|
||||
}
|
||||
}, [colorMode, message.is_assistant]);
|
||||
|
||||
return (
|
||||
<Box bg={bgColor} ref={ref} className={`p-4 rounded-md text-white whitespace-pre-wrap`}>
|
||||
{message.text}
|
||||
</Box>
|
||||
);
|
||||
});
|
||||
|
||||
MessageView.displayName = "MessageView";
|
||||
@@ -1,13 +1,11 @@
|
||||
import { Box, useBoolean, useColorModeValue } from "@chakra-ui/react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
import { MessageView } from "src/components/Messages";
|
||||
import { LabelInputGroup } from "src/components/Messages/LabelInputGroup";
|
||||
import { MessageTable } from "src/components/Messages/MessageTable";
|
||||
import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
|
||||
import { TaskSurveyProps } from "src/components/Tasks/Task";
|
||||
import { TaskHeader } from "src/components/Tasks/TaskHeader";
|
||||
import { TaskType } from "src/types/Task";
|
||||
import { LabelTaskType } from "src/types/Tasks";
|
||||
|
||||
const isRequired = (labelName: string, requiredLabels?: string[]) => {
|
||||
@@ -56,15 +54,9 @@ export const LabelTask = ({
|
||||
<TwoColumnsWithCards>
|
||||
<>
|
||||
<TaskHeader taskType={taskType} />
|
||||
{task.type !== TaskType.label_initial_prompt ? (
|
||||
<Box mt="4" p={[4, 6]} borderRadius="lg" bg={cardColor}>
|
||||
<MessageTable messages={task.conversation.messages} highlightLastMessage />
|
||||
</Box>
|
||||
) : (
|
||||
<Box mt="4">
|
||||
<MessageView text={task.prompt} is_assistant={false} id={task.message_id} emojis={{}} user_emojis={[]} />
|
||||
</Box>
|
||||
)}
|
||||
<Box mt="4" p={[4, 6]} borderRadius="lg" bg={cardColor}>
|
||||
<MessageTable messages={task.conversation.messages} highlightLastMessage />
|
||||
</Box>
|
||||
</>
|
||||
<LabelInputGroup
|
||||
labels={task.labels}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
type CaptchaErrorCode =
|
||||
| "missing-input-secret"
|
||||
| "invalid-input-secret"
|
||||
| "missing-input-response"
|
||||
| "invalid-input-response"
|
||||
| "bad-request"
|
||||
| "timeout-or-duplicate"
|
||||
| "internal-error";
|
||||
|
||||
type CheckCaptchaResponse = {
|
||||
success: boolean;
|
||||
challenge_ts?: string;
|
||||
hostname: string;
|
||||
"error-codes": CaptchaErrorCode[];
|
||||
action?: string;
|
||||
cdata?: string;
|
||||
};
|
||||
|
||||
// https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
|
||||
export const checkCaptcha = async (
|
||||
token: string,
|
||||
ipAdress: string,
|
||||
options?: { cdata?: string; action?: string }
|
||||
): Promise<CheckCaptchaResponse> => {
|
||||
const data = new FormData();
|
||||
|
||||
data.append("secret", process.env.CLOUDFLARE_CAPTCHA_SERCERT_KEY);
|
||||
data.append("response", token);
|
||||
data.append("remoteip", ipAdress);
|
||||
|
||||
const result = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
|
||||
body: data,
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
const res: CheckCaptchaResponse = await result.json();
|
||||
return {
|
||||
...res,
|
||||
success: getSuccess(res, options?.action, options?.cdata),
|
||||
};
|
||||
};
|
||||
|
||||
// This function hasn't been tested yet, Cloudflare doesn't send `action` and `cdata` with a demo key.
|
||||
const getSuccess = (response: CheckCaptchaResponse, action: string | undefined, cdata: string | undefined) => {
|
||||
if (action === undefined && cdata === undefined) {
|
||||
return response.success;
|
||||
}
|
||||
|
||||
if (action) {
|
||||
if (cdata) {
|
||||
return response.action === action && response.cdata === cdata;
|
||||
}
|
||||
return response.action === action;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
@@ -1,11 +1,13 @@
|
||||
import { PrismaAdapter } from "@next-auth/prisma-adapter";
|
||||
import { boolean } from "boolean";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
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 { checkCaptcha } from "src/lib/captcha";
|
||||
import prisma from "src/lib/prismadb";
|
||||
import { generateUsername } from "unique-username-generator";
|
||||
|
||||
@@ -74,7 +76,7 @@ const adminUserMap = process.env.ADMIN_USERS.split(",").reduce((result, entry) =
|
||||
return result;
|
||||
}, new Map());
|
||||
|
||||
export const authOptions: AuthOptions = {
|
||||
const authOptions: AuthOptions = {
|
||||
// Ensure we can store user data in a database.
|
||||
adapter: PrismaAdapter(prisma),
|
||||
providers,
|
||||
@@ -148,24 +150,41 @@ export const authOptions: AuthOptions = {
|
||||
}
|
||||
},
|
||||
},
|
||||
/*
|
||||
* We maybe need this, we maybe don't. Checking in this uncommented until
|
||||
* it's confirmed we can drop this.
|
||||
cookies: {
|
||||
sessionToken: {
|
||||
name: `next-auth.session-token`,
|
||||
options: {
|
||||
httpOnly: true,
|
||||
sameSite: "none",
|
||||
path: "/",
|
||||
secure: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
*/
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
},
|
||||
};
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
export default function auth(req: NextApiRequest, res: NextApiResponse) {
|
||||
return NextAuth(req, res, {
|
||||
...authOptions,
|
||||
callbacks: {
|
||||
...authOptions.callbacks,
|
||||
async signIn({ account }) {
|
||||
if (account.provider !== "email" || !boolean(process.env.NEXT_PUBLIC_ENABLE_EMAIL_SIGNIN_CAPTCHA)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const captcha = req.body.captcha;
|
||||
|
||||
const res = await checkCaptcha(captcha, getIp(req));
|
||||
|
||||
if (res.success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return "/auth/signin?error=InvalidCaptcha";
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const getIp = (req: NextApiRequest) => {
|
||||
try {
|
||||
// https://stackoverflow.com/questions/66111742/get-the-client-ip-on-nextjs-and-use-ssr
|
||||
const forwarded = req.headers["x-forwarded-for"];
|
||||
return typeof forwarded === "string" ? forwarded.split(/, /)[0] : req.socket.remoteAddress;
|
||||
} catch {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
import { Button, ButtonProps, Input, Stack, useColorModeValue } from "@chakra-ui/react";
|
||||
import { useColorMode } from "@chakra-ui/react";
|
||||
import { TurnstileInstance } from "@marsidev/react-turnstile";
|
||||
import { boolean } from "boolean";
|
||||
import { Bug, Github, Mail } from "lucide-react";
|
||||
import { GetServerSideProps } from "next";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { ClientSafeProvider, getProviders, signIn } from "next-auth/react";
|
||||
import { getProviders, signIn } from "next-auth/react";
|
||||
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { AuthLayout } from "src/components/AuthLayout";
|
||||
import { CloudFlareCaptcha } from "src/components/CloudflareCaptcha";
|
||||
import { Footer } from "src/components/Footer";
|
||||
import { Header } from "src/components/Header";
|
||||
import { Discord } from "src/components/Icons/Discord";
|
||||
@@ -26,6 +29,7 @@ export type SignInErrorTypes =
|
||||
| "EmailSignin"
|
||||
| "CredentialsSignin"
|
||||
| "SessionRequired"
|
||||
| "InvalidCaptcha"
|
||||
| "default";
|
||||
|
||||
const errorMessages: Record<SignInErrorTypes, string> = {
|
||||
@@ -39,6 +43,7 @@ const errorMessages: Record<SignInErrorTypes, string> = {
|
||||
EmailSignin: "The e-mail could not be sent.",
|
||||
CredentialsSignin: "Sign in failed. Check the details you provided are correct.",
|
||||
SessionRequired: "Please sign in to access this page.",
|
||||
InvalidCaptcha: "Invalid captcha",
|
||||
default: "Unable to sign in.",
|
||||
};
|
||||
|
||||
@@ -62,14 +67,10 @@ function Signin({ providers }: SigninProps) {
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
const signinWithEmail = (data: { email: string }) => {
|
||||
signIn(email.id, { callbackUrl: "/dashboard", email: data.email });
|
||||
};
|
||||
|
||||
const { colorMode } = useColorMode();
|
||||
const bgColorClass = colorMode === "light" ? "bg-gray-50" : "bg-chakra-gray-900";
|
||||
const buttonBgColor = colorMode === "light" ? "#2563eb" : "#2563eb";
|
||||
const { register, handleSubmit } = useForm<{ email: string }>();
|
||||
|
||||
return (
|
||||
<div className={bgColorClass}>
|
||||
<Head>
|
||||
@@ -78,24 +79,8 @@ function Signin({ providers }: SigninProps) {
|
||||
</Head>
|
||||
<AuthLayout>
|
||||
<Stack spacing="2">
|
||||
{credentials && <DebugSigninForm credentials={credentials} bgColorClass={bgColorClass} />}
|
||||
{email && (
|
||||
<form onSubmit={handleSubmit(signinWithEmail)}>
|
||||
<Stack>
|
||||
<Input
|
||||
type="email"
|
||||
data-cy="email-address"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
placeholder="Email Address"
|
||||
{...register("email")}
|
||||
/>
|
||||
<SigninButton data-cy="signin-email-button" leftIcon={<Mail />}>
|
||||
Continue with Email
|
||||
</SigninButton>
|
||||
</Stack>
|
||||
</form>
|
||||
)}
|
||||
{credentials && <DebugSigninForm providerId={credentials.id} bgColorClass={bgColorClass} />}
|
||||
{email && <EmailSignInForm providerId={email.id}></EmailSignInForm>}
|
||||
{discord && (
|
||||
<Button
|
||||
bg={buttonBgColor}
|
||||
@@ -160,6 +145,50 @@ Signin.getLayout = (page) => (
|
||||
|
||||
export default Signin;
|
||||
|
||||
const emailSigninCaptcha = boolean(process.env.NEXT_PUBLIC_ENABLE_EMAIL_SIGNIN_CAPTCHA);
|
||||
|
||||
const EmailSignInForm = ({ providerId }: { providerId: string }) => {
|
||||
const { register, handleSubmit } = useForm<{ email: string }>();
|
||||
const captcha = useRef<TurnstileInstance>();
|
||||
const [captchaSuccess, setCaptchaSuccess] = useState(false);
|
||||
const signinWithEmail = (data: { email: string }) => {
|
||||
signIn(providerId, {
|
||||
callbackUrl: "/dashboard",
|
||||
email: data.email,
|
||||
captcha: captcha.current?.getResponse(),
|
||||
});
|
||||
};
|
||||
return (
|
||||
<form onSubmit={handleSubmit(signinWithEmail)}>
|
||||
<Stack>
|
||||
<Input
|
||||
type="email"
|
||||
data-cy="email-address"
|
||||
variant="outline"
|
||||
size="lg"
|
||||
placeholder="Email Address"
|
||||
{...register("email")}
|
||||
/>
|
||||
{emailSigninCaptcha && (
|
||||
<CloudFlareCaptcha
|
||||
options={{ size: "invisible" }}
|
||||
ref={captcha}
|
||||
onSuccess={() => setCaptchaSuccess(true)}
|
||||
></CloudFlareCaptcha>
|
||||
)}
|
||||
<SigninButton
|
||||
data-cy="signin-email-button"
|
||||
leftIcon={<Mail />}
|
||||
mt="4"
|
||||
disabled={!captchaSuccess && emailSigninCaptcha}
|
||||
>
|
||||
Continue with Email
|
||||
</SigninButton>
|
||||
</Stack>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
const SigninButton = (props: ButtonProps) => {
|
||||
const buttonColorScheme = useColorModeValue("blue", "dark-blue-btn");
|
||||
|
||||
@@ -180,7 +209,7 @@ interface DebugSigninFormData {
|
||||
role: Role;
|
||||
}
|
||||
|
||||
const DebugSigninForm = ({ credentials, bgColorClass }: { credentials: ClientSafeProvider; bgColorClass: string }) => {
|
||||
const DebugSigninForm = ({ providerId, bgColorClass }: { providerId: string; bgColorClass: string }) => {
|
||||
const { register, handleSubmit } = useForm<DebugSigninFormData>({
|
||||
defaultValues: {
|
||||
role: "general",
|
||||
@@ -189,7 +218,7 @@ const DebugSigninForm = ({ credentials, bgColorClass }: { credentials: ClientSaf
|
||||
});
|
||||
|
||||
function signinWithDebugCredentials(data: DebugSigninFormData) {
|
||||
signIn(credentials.id, {
|
||||
signIn(providerId, {
|
||||
callbackUrl: "/dashboard",
|
||||
...data,
|
||||
});
|
||||
|
||||
@@ -5,8 +5,9 @@ import { useEffect, useMemo, useState } from "react";
|
||||
import { LeaderboardWidget, TaskOption, WelcomeCard } from "src/components/Dashboard";
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { get } from "src/lib/api";
|
||||
import { AvailableTasks, TaskCategory, TaskType } from "src/types/Task";
|
||||
import { AvailableTasks, TaskCategory } from "src/types/Task";
|
||||
export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_props";
|
||||
import { TaskCategoryItem } from "src/components/Dashboard/TaskOption";
|
||||
import useSWR from "swr";
|
||||
|
||||
const Dashboard = () => {
|
||||
@@ -59,4 +60,4 @@ const filterAvailableTasks = (availableTasks: Partial<AvailableTasks>) =>
|
||||
Object.entries(availableTasks)
|
||||
.filter(([, count]) => count > 0)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.map(([taskType]) => taskType) as TaskType[];
|
||||
.map(([taskType, count]) => ({ taskType, count })) as TaskCategoryItem[];
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { defineStyleConfig } from "@chakra-ui/react";
|
||||
|
||||
export const badgeTheme = defineStyleConfig({
|
||||
baseStyle: {
|
||||
borderRadius: "lg",
|
||||
px: 2,
|
||||
py: 0.5,
|
||||
fontWeight: "600",
|
||||
},
|
||||
defaultProps: {
|
||||
variant: "solid",
|
||||
colorScheme: "blue",
|
||||
},
|
||||
});
|
||||
@@ -2,6 +2,7 @@ import { type ThemeConfig, extendTheme } from "@chakra-ui/react";
|
||||
import { Styles } from "@chakra-ui/theme-tools";
|
||||
|
||||
import { colors } from "./colors";
|
||||
import { badgeTheme } from "./components/Badge";
|
||||
import { cardTheme } from "./components/Card";
|
||||
import { containerTheme } from "./components/Container";
|
||||
|
||||
@@ -12,6 +13,7 @@ const config: ThemeConfig = {
|
||||
};
|
||||
|
||||
const components = {
|
||||
Badge: badgeTheme,
|
||||
Container: containerTheme,
|
||||
Card: cardTheme,
|
||||
};
|
||||
|
||||
@@ -46,6 +46,7 @@ export interface Label {
|
||||
|
||||
export interface BaseLabelTask extends BaseTask {
|
||||
message_id: string;
|
||||
conversation: Conversation;
|
||||
labels: Label[];
|
||||
valid_labels: string[];
|
||||
disposition: "spam" | "quality";
|
||||
@@ -55,14 +56,12 @@ export interface BaseLabelTask extends BaseTask {
|
||||
|
||||
export interface LabelAssistantReplyTask extends BaseLabelTask {
|
||||
type: TaskType.label_assistant_reply;
|
||||
conversation: Conversation;
|
||||
reply_message: Message;
|
||||
reply: string;
|
||||
}
|
||||
|
||||
export interface LabelPrompterReplyTask extends BaseLabelTask {
|
||||
type: TaskType.label_prompter_reply;
|
||||
conversation: Conversation;
|
||||
reply_message: Message;
|
||||
reply: string;
|
||||
}
|
||||
|
||||
Vendored
+12
@@ -0,0 +1,12 @@
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface ProcessEnv {
|
||||
NODE_ENV: "development" | "production";
|
||||
NEXT_PUBLIC_CLOUDFLARE_CAPTCHA_SITE_KEY: string;
|
||||
CLOUDFLARE_CAPTCHA_SERCERT_KEY: string;
|
||||
NEXT_PUBLIC_ENABLE_EMAIL_SIGNIN_CAPTCHA: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
Reference in New Issue
Block a user