mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-07-05 17:30:48 +08:00
Merge pull request #994 from LAION-AI/refactor
Consolidate API calls to TaskContext
This commit is contained in:
@@ -15,9 +15,9 @@ import Link from "next/link";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useMemo } from "react";
|
||||
import { getTypeSafei18nKey } from "src/lib/i18n";
|
||||
import { TaskType } from "src/types/Task";
|
||||
import { TaskCategory, TaskInfo, TaskType } from "src/types/Task";
|
||||
|
||||
import { TaskCategory, TaskCategoryLabels, TaskInfo, TaskInfos } from "../Tasks/TaskTypes";
|
||||
import { TaskCategoryLabels, TaskInfos } from "../Tasks/TaskTypes";
|
||||
|
||||
export interface TasksOptionProps {
|
||||
content: Partial<Record<TaskCategory, TaskType[]>>;
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import Head from "next/head";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useMemo } from "react";
|
||||
import { TaskEmptyState } from "src/components/EmptyState";
|
||||
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
|
||||
import { Task } from "src/components/Tasks/Task";
|
||||
import { TaskInfos } from "src/components/Tasks/TaskTypes";
|
||||
import { TaskContext } from "src/context/TaskContext";
|
||||
import { taskApiHooks } from "src/lib/constants";
|
||||
import { getTypeSafei18nKey } from "src/lib/i18n";
|
||||
import { TaskType } from "src/types/Task";
|
||||
import { KnownTaskType } from "src/types/Tasks";
|
||||
|
||||
type TaskPageProps = {
|
||||
type: TaskType;
|
||||
@@ -15,32 +16,32 @@ type TaskPageProps = {
|
||||
|
||||
export const TaskPage = ({ type }: TaskPageProps) => {
|
||||
const { t } = useTranslation(["tasks", "common"]);
|
||||
const taskApiHook = taskApiHooks[type];
|
||||
const { response, isLoading, completeTask, skipTask } = taskApiHook(type);
|
||||
const taskApiHook = useMemo(() => taskApiHooks[type], [type]);
|
||||
const hookState = taskApiHook(type);
|
||||
|
||||
const body = useMemo(() => {
|
||||
const { response } = hookState;
|
||||
switch (response.taskAvailability) {
|
||||
case "AWAITING_INITIAL":
|
||||
return <LoadingScreen text={t("common:loading")} />;
|
||||
|
||||
case "NONE_AVAILABLE":
|
||||
return <TaskEmptyState />;
|
||||
|
||||
case "AVAILABLE": {
|
||||
const { task, taskInfo } = response;
|
||||
const context = { ...hookState, task, taskInfo };
|
||||
return (
|
||||
<TaskContext.Provider value={context}>
|
||||
<Task key={response.id} />
|
||||
</TaskContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [hookState, t]);
|
||||
|
||||
// NOTE: this is independent of the fetched task type, it is usually identical, but not for the random task.
|
||||
const taskInfo = TaskInfos.find((taskType) => taskType.type === type);
|
||||
|
||||
let body;
|
||||
switch (response.taskAvailability) {
|
||||
case "AWAITING_INITIAL":
|
||||
body = <LoadingScreen text={t("common:loading")} />;
|
||||
break;
|
||||
case "NONE_AVAILABLE":
|
||||
body = <TaskEmptyState />;
|
||||
break;
|
||||
case "AVAILABLE":
|
||||
body = (
|
||||
<Task
|
||||
key={response.task.id}
|
||||
frontendId={response.id}
|
||||
task={response.task as KnownTaskType}
|
||||
isLoading={isLoading}
|
||||
completeTask={completeTask}
|
||||
skipTask={skipTask}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
import React from "react";
|
||||
|
||||
import { Task } from "./Task";
|
||||
import { Task } from "src/components/Tasks/Task";
|
||||
import { TaskInfos } from "src/components/Tasks/TaskTypes";
|
||||
import { TaskContext } from "src/context/TaskContext";
|
||||
|
||||
export default {
|
||||
title: "tasks/Task",
|
||||
component: Task,
|
||||
};
|
||||
|
||||
const Template = ({ frontendId, task, isLoading, completeTask, skipTask }) => {
|
||||
const Template = ({ providerValue }) => {
|
||||
return (
|
||||
<Task frontendId={frontendId} task={task} isLoading={isLoading} completeTask={completeTask} skipTask={skipTask} />
|
||||
<TaskContext.Provider value={providerValue}>
|
||||
<Task />
|
||||
</TaskContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
const exampleProviderValue = {
|
||||
frontendId: "1234",
|
||||
task: {
|
||||
conversation: [],
|
||||
@@ -25,11 +27,18 @@ Default.args = {
|
||||
type: "label_prompter_reply",
|
||||
valid_labels: ["spam", "fails_task"],
|
||||
},
|
||||
taskInfo: TaskInfos.find((t) => t.type === "label_prompter_reply"),
|
||||
isLoading: false,
|
||||
completeTask: (id, update_type, content) => {
|
||||
completeTask: (content) => {
|
||||
console.log(content);
|
||||
},
|
||||
skipTask: () => {
|
||||
console.log("skip");
|
||||
},
|
||||
rejectTask: () => {
|
||||
console.log("reject");
|
||||
},
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = { providerValue: exampleProviderValue };
|
||||
|
||||
@@ -5,13 +5,12 @@ import { TaskControls } from "src/components/Survey/TaskControls";
|
||||
import { CreateTask } from "src/components/Tasks/CreateTask";
|
||||
import { EvaluateTask } from "src/components/Tasks/EvaluateTask";
|
||||
import { LabelTask } from "src/components/Tasks/LabelTask";
|
||||
import { TaskCategory, TaskInfo, TaskInfos } from "src/components/Tasks/TaskTypes";
|
||||
import { UnchangedWarning } from "src/components/Tasks/UnchangedWarning";
|
||||
import { post } from "src/lib/api";
|
||||
import { useTaskContext } from "src/context/TaskContext";
|
||||
import { getTypeSafei18nKey } from "src/lib/i18n";
|
||||
import { TaskCategory, TaskInfo } from "src/types/Task";
|
||||
import { BaseTask, TaskContent, TaskReplyValidity } from "src/types/Task";
|
||||
import { CreateTaskType, KnownTaskType, LabelTaskType, RankTaskType } from "src/types/Tasks";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import { CreateTaskType, LabelTaskType, RankTaskType } from "src/types/Tasks";
|
||||
|
||||
interface EditMode {
|
||||
mode: "EDIT";
|
||||
@@ -63,16 +62,11 @@ export interface TaskSurveyProps<TaskType extends BaseTask, T> {
|
||||
onValidityChanged: (validity: TaskReplyValidity) => void;
|
||||
}
|
||||
|
||||
interface TaskProps {
|
||||
frontendId: string;
|
||||
task: KnownTaskType;
|
||||
isLoading: boolean;
|
||||
completeTask: (TaskContent) => void;
|
||||
skipTask: () => void;
|
||||
}
|
||||
|
||||
export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: TaskProps) => {
|
||||
export const Task = () => {
|
||||
const { t } = useTranslation("tasks");
|
||||
const rootEl = useRef<HTMLDivElement>(null);
|
||||
const replyContent = useRef<TaskContent>(null);
|
||||
const { rejectTask, completeTask, isLoading, task, taskInfo } = useTaskContext();
|
||||
const [taskStatus, taskEvent] = useReducer(
|
||||
(
|
||||
status: TaskStatus,
|
||||
@@ -114,7 +108,6 @@ export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: Ta
|
||||
{ mode: "EDIT", replyValidity: "INVALID" }
|
||||
);
|
||||
|
||||
const replyContent = useRef<TaskContent>(null);
|
||||
const updateValidity = useCallback(
|
||||
(replyValidity: TaskReplyValidity) => taskEvent({ action: "UPDATE_VALIDITY", replyValidity }),
|
||||
[taskEvent]
|
||||
@@ -122,26 +115,7 @@ export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: Ta
|
||||
|
||||
useEffect(() => {
|
||||
taskEvent({ action: "NEW_TASK" });
|
||||
}, [task.id, updateValidity]);
|
||||
|
||||
const rootEl = useRef<HTMLDivElement>(null);
|
||||
|
||||
const taskType = useMemo(() => {
|
||||
return TaskInfos.find((taskType) => taskType.type === task.type);
|
||||
}, [task.type]);
|
||||
|
||||
const { trigger: sendRejection } = useSWRMutation("/api/reject_task", post, {
|
||||
onSuccess: async () => {
|
||||
skipTask();
|
||||
},
|
||||
});
|
||||
|
||||
const rejectTask = (reason: string) => {
|
||||
sendRejection({
|
||||
id: frontendId,
|
||||
reason,
|
||||
});
|
||||
};
|
||||
}, [task.id]);
|
||||
|
||||
const onReplyChanged = useCallback(
|
||||
(content: TaskContent) => {
|
||||
@@ -150,25 +124,21 @@ export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: Ta
|
||||
[replyContent]
|
||||
);
|
||||
|
||||
const submitResponse = () => {
|
||||
const submitResponse = useCallback(async () => {
|
||||
if (taskStatus.mode === "REVIEW") {
|
||||
completeTask({
|
||||
id: frontendId,
|
||||
update_type: taskType.update_type,
|
||||
content: replyContent.current,
|
||||
});
|
||||
taskEvent({ action: "SET_SUBMITTED" });
|
||||
await completeTask(replyContent.current);
|
||||
scrollToTop(rootEl.current);
|
||||
}
|
||||
};
|
||||
}, [taskStatus.mode, completeTask]);
|
||||
|
||||
const taskTypeComponent = useMemo(() => {
|
||||
switch (taskType.category) {
|
||||
switch (taskInfo.category) {
|
||||
case TaskCategory.Create:
|
||||
return (
|
||||
<CreateTask
|
||||
task={task as CreateTaskType}
|
||||
taskType={taskType}
|
||||
taskType={taskInfo}
|
||||
isEditable={taskStatus.mode === "EDIT"}
|
||||
isDisabled={taskStatus.mode === "SUBMITTED"}
|
||||
onReplyChanged={onReplyChanged}
|
||||
@@ -179,7 +149,7 @@ export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: Ta
|
||||
return (
|
||||
<EvaluateTask
|
||||
task={task as RankTaskType}
|
||||
taskType={taskType}
|
||||
taskType={taskInfo}
|
||||
isEditable={taskStatus.mode === "EDIT"}
|
||||
isDisabled={taskStatus.mode === "SUBMITTED"}
|
||||
onReplyChanged={onReplyChanged}
|
||||
@@ -190,7 +160,7 @@ export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: Ta
|
||||
return (
|
||||
<LabelTask
|
||||
task={task as LabelTaskType}
|
||||
taskType={taskType}
|
||||
taskType={taskInfo}
|
||||
isEditable={taskStatus.mode === "EDIT"}
|
||||
isDisabled={taskStatus.mode === "SUBMITTED"}
|
||||
onReplyChanged={onReplyChanged}
|
||||
@@ -198,7 +168,7 @@ export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: Ta
|
||||
/>
|
||||
);
|
||||
}
|
||||
}, [task, taskType, taskStatus.mode, onReplyChanged, updateValidity]);
|
||||
}, [taskInfo, task, taskStatus.mode, onReplyChanged, updateValidity]);
|
||||
|
||||
return (
|
||||
<div ref={rootEl}>
|
||||
@@ -214,8 +184,8 @@ export const Task = ({ frontendId, task, isLoading, completeTask, skipTask }: Ta
|
||||
/>
|
||||
<UnchangedWarning
|
||||
show={taskStatus.mode === "DEFAULT_WARN"}
|
||||
title={t(getTypeSafei18nKey(`${taskType.id}.unchanged_title`)) || t("default.unchanged_title")}
|
||||
message={t(getTypeSafei18nKey(`${taskType.id}.unchanged_message`)) || t("default.unchanged_message")}
|
||||
title={t(getTypeSafei18nKey(`${taskInfo.id}.unchanged_title`)) || t("default.unchanged_title")}
|
||||
message={t(getTypeSafei18nKey(`${taskInfo.id}.unchanged_message`)) || t("default.unchanged_message")}
|
||||
continueButtonText={"Continue anyway"}
|
||||
onClose={() => taskEvent({ action: "RETURN_EDIT" })}
|
||||
onContinueAnyway={() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { HStack, IconButton, Link, Stack, Text, useColorModeValue } from "@chakra-ui/react";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import type { TaskInfo } from "src/components/Tasks/TaskTypes";
|
||||
import { getTypeSafei18nKey } from "src/lib/i18n";
|
||||
import { TaskInfo } from "src/types/Task";
|
||||
|
||||
interface TaskHeaderProps {
|
||||
/**
|
||||
|
||||
@@ -1,28 +1,4 @@
|
||||
import { TaskType } from "src/types/Task";
|
||||
|
||||
export enum TaskCategory {
|
||||
Create = "Create",
|
||||
Evaluate = "Evaluate",
|
||||
Label = "Label",
|
||||
Random = "Random",
|
||||
}
|
||||
|
||||
export enum TaskUpdateType {
|
||||
MessageRanking = "message_ranking",
|
||||
Random = "random",
|
||||
TextLabels = "text_labels",
|
||||
TextReplyToMessage = "text_reply_to_message",
|
||||
}
|
||||
|
||||
export interface TaskInfo {
|
||||
category: TaskCategory;
|
||||
help_link: string;
|
||||
id: string;
|
||||
mode?: string;
|
||||
pathname: string;
|
||||
type: string;
|
||||
update_type: string;
|
||||
}
|
||||
import { TaskCategory, TaskInfo, TaskType, TaskUpdateType } from "src/types/Task";
|
||||
|
||||
export const TaskCategoryLabels: { [key in TaskCategory]: string } = {
|
||||
[TaskCategory.Random]: "grab_a_task",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import { TaskApiHook } from "src/types/Hooks";
|
||||
import { BaseTask, TaskInfo } from "src/types/Task";
|
||||
|
||||
export interface TaskContextType<Task extends BaseTask, ResponseContent>
|
||||
extends Omit<TaskApiHook<Task, ResponseContent>, "response"> {
|
||||
task: Task;
|
||||
taskInfo: TaskInfo;
|
||||
}
|
||||
|
||||
export const TaskContext = createContext<TaskContextType<BaseTask, unknown>>(null);
|
||||
|
||||
export const useTaskContext = () => useContext(TaskContext);
|
||||
@@ -1,38 +1,75 @@
|
||||
import { useState } from "react";
|
||||
import { useCallback, useState } from "react";
|
||||
import { TaskInfos } from "src/components/Tasks/TaskTypes";
|
||||
import { get, post } from "src/lib/api";
|
||||
import { TaskApiHook } from "src/types/Hooks";
|
||||
import { BaseTask, TaskAvailableResponse, TaskResponse, TaskType as TaskTypeEnum } from "src/types/Task";
|
||||
import { BaseTask, ServerTaskResponse, TaskResponse, TaskType as TaskTypeEnum } from "src/types/Task";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
|
||||
export const useGenericTaskAPI = <TaskType extends BaseTask>(taskType: TaskTypeEnum): TaskApiHook<TaskType> => {
|
||||
const [response, setReponse] = useState<TaskResponse<TaskType>>({ taskAvailability: "AWAITING_INITIAL" });
|
||||
// TODO: provide type for the content reply, this will be much harder since the replies vary vastly
|
||||
export const useGenericTaskAPI = <TaskType extends BaseTask, ResponseContent = any>(
|
||||
taskType: TaskTypeEnum
|
||||
): TaskApiHook<TaskType, ResponseContent> => {
|
||||
const [response, setResponse] = useState<TaskResponse<TaskType>>({ taskAvailability: "AWAITING_INITIAL" });
|
||||
|
||||
// Note: We use isValidating to indiate we are loading beause it signals eash load, not just the first one.
|
||||
const { isValidating: isLoading, mutate: requestNewTask } = useSWRImmutable<TaskAvailableResponse<TaskType>>(
|
||||
const { isValidating: isLoading, mutate: requestNewTask } = useSWRImmutable<ServerTaskResponse<TaskType>>(
|
||||
"/api/new_task/" + taskType,
|
||||
get,
|
||||
{
|
||||
onSuccess: (response) => {
|
||||
setReponse({ taskAvailability: "AVAILABLE", ...response });
|
||||
onSuccess: (taskResponse) => {
|
||||
setResponse({
|
||||
...taskResponse,
|
||||
taskAvailability: "AVAILABLE",
|
||||
taskInfo: TaskInfos.find((taskType) => taskType.type === taskResponse.task.type),
|
||||
});
|
||||
},
|
||||
onError: () => {
|
||||
// We could check for code 503 here for truely unavailable, but we need to do something with other errors anyway.
|
||||
setReponse({ taskAvailability: "NONE_AVAILABLE" });
|
||||
// We could check for code 503 here for truly unavailable, but we need to do something with other errors anyway.
|
||||
setResponse({ taskAvailability: "NONE_AVAILABLE" });
|
||||
},
|
||||
revalidateOnMount: true,
|
||||
dedupingInterval: 500,
|
||||
}
|
||||
);
|
||||
|
||||
const { trigger: completeTask } = useSWRMutation<TaskAvailableResponse<TaskType>>("/api/update_task", post, {
|
||||
const { trigger: sendTaskContent } = useSWRMutation("/api/update_task", post, {
|
||||
onSuccess: () => {
|
||||
requestNewTask();
|
||||
},
|
||||
onError: () => {
|
||||
// We could check for code 503 here for truely unavailable, but we need to do something with other errors anyway.
|
||||
setReponse({ taskAvailability: "NONE_AVAILABLE" });
|
||||
// We could check for code 503 here for truly unavailable, but we need to do something with other errors anyway.
|
||||
setResponse({ taskAvailability: "NONE_AVAILABLE" });
|
||||
},
|
||||
});
|
||||
|
||||
return { response, isLoading, completeTask, skipTask: requestNewTask };
|
||||
// NOTE: it might make sense to split this hook into 2 parts
|
||||
|
||||
// makes sure that requestNewTask is always called without parameters:
|
||||
const skipTask = useCallback(async () => {
|
||||
await requestNewTask();
|
||||
}, [requestNewTask]);
|
||||
|
||||
const { trigger: sendRejection } = useSWRMutation("/api/reject_task", post, { onSuccess: skipTask });
|
||||
const rejectTask = useCallback(
|
||||
async (reason: string) => {
|
||||
if (response.taskAvailability !== "AVAILABLE") {
|
||||
throw new Error("Cannot reject task that is not yet ready");
|
||||
}
|
||||
await sendRejection({ id: response.id, reason });
|
||||
},
|
||||
[response, sendRejection]
|
||||
);
|
||||
|
||||
const completeTask = useCallback(
|
||||
async (content: ResponseContent) => {
|
||||
if (response.taskAvailability !== "AVAILABLE") {
|
||||
throw new Error("Cannot complete task that is not yet ready");
|
||||
}
|
||||
await sendTaskContent({ id: response.id, update_type: response.taskInfo.update_type, content });
|
||||
},
|
||||
[response, sendTaskContent]
|
||||
);
|
||||
|
||||
return { response, isLoading, rejectTask, completeTask, skipTask };
|
||||
};
|
||||
|
||||
@@ -4,9 +4,8 @@ import { useTranslation } from "next-i18next";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { LeaderboardWidget, TaskOption, WelcomeCard } from "src/components/Dashboard";
|
||||
import { getDashboardLayout } from "src/components/Layout";
|
||||
import { TaskCategory } from "src/components/Tasks/TaskTypes";
|
||||
import { get } from "src/lib/api";
|
||||
import { AvailableTasks, TaskType } from "src/types/Task";
|
||||
import { AvailableTasks, TaskCategory, TaskType } from "src/types/Task";
|
||||
export { getDefaultStaticProps as getStaticProps } from "src/lib/default_static_props";
|
||||
import useSWR from "swr";
|
||||
|
||||
|
||||
@@ -1,16 +1,11 @@
|
||||
import { BaseTask, TaskContent, TaskResponse, TaskType } from "src/types/Task";
|
||||
import { BaseTask, TaskResponse, TaskType } from "src/types/Task";
|
||||
|
||||
interface TaskInteraction {
|
||||
id: string;
|
||||
update_type: string;
|
||||
content: TaskContent;
|
||||
}
|
||||
|
||||
export type TaskApiHook<Task extends BaseTask> = {
|
||||
export type TaskApiHook<Task extends BaseTask, ResponseContent> = {
|
||||
response: TaskResponse<Task>;
|
||||
isLoading: boolean;
|
||||
completeTask: (interaction: TaskInteraction) => void;
|
||||
skipTask: () => void;
|
||||
completeTask: (interaction: ResponseContent) => Promise<void>;
|
||||
skipTask: () => Promise<void>;
|
||||
rejectTask: (reason: string) => Promise<void>;
|
||||
};
|
||||
|
||||
export type TaskApiHooks = Record<TaskType, (args: TaskType) => TaskApiHook<BaseTask>>;
|
||||
export type TaskApiHooks = Record<TaskType, (args: TaskType) => TaskApiHook<BaseTask, any>>;
|
||||
|
||||
@@ -14,6 +14,30 @@ export enum TaskType {
|
||||
random = "random",
|
||||
}
|
||||
|
||||
export enum TaskCategory {
|
||||
Create = "Create",
|
||||
Evaluate = "Evaluate",
|
||||
Label = "Label",
|
||||
Random = "Random",
|
||||
}
|
||||
|
||||
export interface TaskInfo {
|
||||
category: TaskCategory;
|
||||
help_link: string;
|
||||
id: string;
|
||||
mode?: string;
|
||||
pathname: string;
|
||||
type: string;
|
||||
update_type: string;
|
||||
}
|
||||
|
||||
export enum TaskUpdateType {
|
||||
MessageRanking = "message_ranking",
|
||||
Random = "random",
|
||||
TextLabels = "text_labels",
|
||||
TextReplyToMessage = "text_reply_to_message",
|
||||
}
|
||||
|
||||
// we need to reconsider how to handle task content types
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type TaskContent = any;
|
||||
@@ -29,14 +53,15 @@ export interface BaseTask {
|
||||
type: TaskType;
|
||||
}
|
||||
|
||||
export interface TaskAvailableResponse<Task extends BaseTask> {
|
||||
export interface ServerTaskResponse<Task extends BaseTask> {
|
||||
id: string;
|
||||
userId: string;
|
||||
task: Task;
|
||||
}
|
||||
|
||||
interface TaskAvailable<Task extends BaseTask> extends TaskAvailableResponse<Task> {
|
||||
interface TaskAvailable<Task extends BaseTask> extends ServerTaskResponse<Task> {
|
||||
taskAvailability: "AVAILABLE";
|
||||
taskInfo: TaskInfo;
|
||||
}
|
||||
|
||||
interface AwaitingInitialTask {
|
||||
|
||||
Reference in New Issue
Block a user