Merge branch 'main' into cleanup

This commit is contained in:
notmd
2023-02-01 17:31:03 +07:00
22 changed files with 301 additions and 118 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
{
"python.formatting.provider": "black",
"python.analysis.extraPaths": ["${workspaceFolder}/oasst-shared"]
"python.analysis.extraPaths": ["${workspaceFolder}/oasst-shared"],
"prettier.singleQuote": false
}
+3
View File
@@ -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
+14
View File
@@ -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`
+4
View File
@@ -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
+10 -5
View File
@@ -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) => {
+10 -5
View File
@@ -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(() => {
+16
View File
@@ -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",
+1
View File
@@ -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",
+2 -1
View File
@@ -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";
+1 -1
View File
@@ -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>
+34 -23
View File
@@ -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")} -&gt;
@@ -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],
};
-23
View File
@@ -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}
+57
View File
@@ -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;
};
+36 -17
View File
@@ -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 "";
}
};
+56 -27
View File
@@ -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,
});
+3 -2
View File
@@ -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
View File
@@ -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,
};
+1 -2
View File
@@ -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;
}
+12
View File
@@ -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 {};