diff --git a/website/src/components/Loading/LoadingScreen.jsx b/website/src/components/Loading/LoadingScreen.jsx
index 02aabe7a..3595b3c4 100644
--- a/website/src/components/Loading/LoadingScreen.jsx
+++ b/website/src/components/Loading/LoadingScreen.jsx
@@ -1,7 +1,7 @@
import { Progress } from "@chakra-ui/react";
import { useColorMode } from "@chakra-ui/react";
-export const LoadingScreen = ({ text }) => {
+export const LoadingScreen = ({ text = "Loading..." } = {}) => {
const { colorMode } = useColorMode();
const mainClasses = colorMode === "light" ? "bg-slate-300 text-gray-800" : "bg-slate-900 text-white";
diff --git a/website/src/components/Tasks/LabelTask.tsx b/website/src/components/Tasks/LabelTask.tsx
new file mode 100644
index 00000000..bb9d417c
--- /dev/null
+++ b/website/src/components/Tasks/LabelTask.tsx
@@ -0,0 +1,100 @@
+import { Grid, Slider, SliderFilledTrack, SliderThumb, SliderTrack } from "@chakra-ui/react";
+import { useColorMode } from "@chakra-ui/react";
+import { ReactNode, useEffect, useId, useMemo, useState } from "react";
+import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
+import { colors } from "styles/Theme/colors";
+
+export const LabelTask = ({
+ title,
+ desc,
+ messages,
+ inputs,
+ controls,
+}: {
+ title: string;
+ desc: string;
+ messages: ReactNode;
+ inputs: ReactNode;
+ controls: ReactNode;
+}) => {
+ const { colorMode } = useColorMode();
+ const mainBgClasses = colorMode === "light" ? "bg-slate-300 text-gray-800" : "bg-slate-900 text-white";
+
+ const card = useMemo(
+ () => (
+ <>
+
{title}
+ {desc}
+ {messages}
+ >
+ ),
+ [title, desc, messages]
+ );
+
+ return (
+
+
+ {card}
+ {inputs}
+
+ {controls}
+
+ );
+};
+
+// TODO: consolidate with FlaggableElement
+interface LabelSliderGroupProps {
+ labelIDs: Array;
+ onChange: (sliderValues: number[]) => unknown;
+}
+
+export const LabelSliderGroup = ({ labelIDs, onChange }: LabelSliderGroupProps) => {
+ const [sliderValues, setSliderValues] = useState(Array.from({ length: labelIDs.length }).map(() => 0));
+
+ useEffect(() => {
+ onChange(sliderValues);
+ }, [sliderValues, onChange]);
+
+ return (
+
+ {labelIDs.map((labelId, idx) => (
+ {
+ const newState = sliderValues.slice();
+ newState[idx] = sliderValue;
+ setSliderValues(newState);
+ }}
+ />
+ ))}
+
+ );
+};
+
+function CheckboxSliderItem(props: {
+ labelId: string;
+ sliderValue: number;
+ sliderHandler: (newVal: number) => unknown;
+}) {
+ const id = useId();
+ const { colorMode } = useColorMode();
+
+ const labelTextClass = colorMode === "light" ? `text-${colors.light.text}` : `text-${colors.dark.text}`;
+
+ return (
+ <>
+
+ props.sliderHandler(val / 100)}>
+
+
+
+
+
+ >
+ );
+}
diff --git a/website/src/hooks/tasks/useGenericTaskAPI.tsx b/website/src/hooks/tasks/useGenericTaskAPI.tsx
new file mode 100644
index 00000000..1a6c0be9
--- /dev/null
+++ b/website/src/hooks/tasks/useGenericTaskAPI.tsx
@@ -0,0 +1,42 @@
+import { useEffect, useState } from "react";
+import fetcher from "src/lib/fetcher";
+import poster from "src/lib/poster";
+import useSWRImmutable from "swr/immutable";
+import useSWRMutation from "swr/mutation";
+
+// TODO: type & centralize types for all tasks
+
+export interface TaskResponse {
+ id: string;
+ userId: string;
+ task: TaskType;
+}
+
+export const useGenericTaskAPI = (taskApiEndpoint: string) => {
+ type ConcreteTaskResponse = TaskResponse;
+
+ const [tasks, setTasks] = useState([]);
+
+ const { isLoading, mutate, error } = useSWRImmutable(
+ "/api/new_task/" + taskApiEndpoint,
+ fetcher,
+ {
+ onSuccess: (data) => setTasks([data]),
+ }
+ );
+
+ useEffect(() => {
+ if (tasks.length === 0 && !isLoading && !error) {
+ mutate();
+ }
+ }, [tasks, isLoading, mutate, error]);
+
+ const { trigger } = useSWRMutation("/api/update_task", poster, {
+ onSuccess: async (response) => {
+ const newTask: ConcreteTaskResponse = await response.json();
+ setTasks((oldTasks) => [...oldTasks, newTask]);
+ },
+ });
+
+ return { tasks, isLoading, trigger, error, reset: mutate };
+};
diff --git a/website/src/hooks/tasks/useLabelInitialPrompt.tsx b/website/src/hooks/tasks/useLabelInitialPrompt.tsx
new file mode 100644
index 00000000..5d6ca372
--- /dev/null
+++ b/website/src/hooks/tasks/useLabelInitialPrompt.tsx
@@ -0,0 +1,27 @@
+import { TaskResponse, useGenericTaskAPI } from "./useGenericTaskAPI";
+
+export interface LabelInitialPromptTask {
+ id: string;
+ type: "label_initial_prompt";
+ message_id: string;
+ valid_labels: string[];
+ prompt: string;
+}
+
+export type LabelInitialPromptTaskResponse = TaskResponse;
+
+export const useLabelInitialPromptTask = () => {
+ const { tasks, isLoading, trigger, reset, error } = useGenericTaskAPI("label_initial_prompt");
+
+ const submit = (id: string, message_id: string, text: string, validLabels: string[], labelWeights: number[]) => {
+ console.assert(validLabels.length === labelWeights.length);
+ const labels = validLabels.reduce(
+ (obj, label, i) => ((obj[label] = labelWeights[i]), obj),
+ {} as Record
+ );
+
+ return trigger({ id, update_type: "text_labels", content: { labels, text, message_id } });
+ };
+
+ return { tasks, isLoading, submit, reset, error };
+};
diff --git a/website/src/hooks/useLabelingTask.ts b/website/src/hooks/useLabelingTask.ts
deleted file mode 100644
index 872909b7..00000000
--- a/website/src/hooks/useLabelingTask.ts
+++ /dev/null
@@ -1,52 +0,0 @@
-import { useEffect, useState } from "react";
-import fetcher from "src/lib/fetcher";
-import poster from "src/lib/poster";
-import useSWRImmutable from "swr/immutable";
-import useSWRMutation from "swr/mutation";
-
-// TODO: type & centralize types for all tasks
-interface TaskResponse {
- id: string;
- userId: string;
- task: TaskType;
-}
-
-export interface LabelInitialPromptTask {
- id: string;
- message_id: string;
- prompt: string;
- type: string;
- valid_labels: string[];
-}
-
-export type LabelInitialPromptTaskResponse = TaskResponse;
-
-export const useLabelingTask = ({ taskApiEndpoint }: { taskApiEndpoint: "label_initial_prompt" }) => {
- type ConcreteTaskResponse = TaskResponse;
-
- const [tasks, setTasks] = useState>([]);
-
- const { isLoading, mutate, error } = useSWRImmutable("/api/new_task/" + taskApiEndpoint, fetcher, {
- onSuccess: (data: ConcreteTaskResponse) => {
- setTasks([data]);
- },
- });
-
- useEffect(() => {
- if (tasks.length === 0 && !isLoading && !error) {
- mutate();
- }
- }, [tasks, isLoading, mutate, error]);
-
- const { trigger } = useSWRMutation("/api/update_task", poster, {
- onSuccess: async (reply) => {
- const newTask: ConcreteTaskResponse = await reply.json();
- setTasks((oldTasks) => [...oldTasks, newTask]);
- },
- });
-
- const submit = (id: string, message_id: string, text: string, labels: Record) =>
- trigger({ id, update_type: "text_labels", content: { labels, text, message_id } });
-
- return { tasks, isLoading, submit, error, reset: mutate };
-};
diff --git a/website/src/pages/label/label_initial_prompt.tsx b/website/src/pages/label/label_initial_prompt.tsx
index e400e8fd..346362dc 100644
--- a/website/src/pages/label/label_initial_prompt.tsx
+++ b/website/src/pages/label/label_initial_prompt.tsx
@@ -1,113 +1,38 @@
-import { Container, Grid, Slider, SliderFilledTrack, SliderThumb, SliderTrack } from "@chakra-ui/react";
-import { useColorMode } from "@chakra-ui/react";
-import { useEffect, useId, useState } from "react";
+import { useState } from "react";
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { MessageView } from "src/components/Messages";
import { TaskControls } from "src/components/Survey/TaskControls";
-import { TwoColumnsWithCards } from "src/components/Survey/TwoColumnsWithCards";
-import { LabelInitialPromptTask, LabelInitialPromptTaskResponse, useLabelingTask } from "src/hooks/useLabelingTask";
-import { colors } from "styles/Theme/colors";
+import { LabelSliderGroup, LabelTask } from "src/components/Tasks/LabelTask";
+import { LabelInitialPromptTaskResponse, useLabelInitialPromptTask } from "src/hooks/tasks/useLabelInitialPrompt";
const LabelInitialPrompt = () => {
const [sliderValues, setSliderValues] = useState([]);
- const { tasks, isLoading, submit, reset } = useLabelingTask({
- taskApiEndpoint: "label_initial_prompt",
- });
+ const { tasks, isLoading, submit, reset } = useLabelInitialPromptTask();
- const submitResponse = ({ id, task }: LabelInitialPromptTaskResponse) => {
- const labels = task.valid_labels.reduce((obj, label, i) => {
- obj[label] = sliderValues[i].toString();
- return obj;
- }, {} as Record);
-
- submit(id, task.message_id, task.prompt, labels);
- };
-
- const { colorMode } = useColorMode();
- const mainBgClasses = colorMode === "light" ? "bg-slate-300 text-gray-800" : "bg-slate-900 text-white";
-
- if (isLoading) {
- return ;
- }
-
- if (tasks.length === 0) {
- return No tasks found...;
+ if (isLoading || tasks.length === 0) {
+ return ;
}
const task = tasks[0].task;
return (
-
-
- <>
- Label Initial Prompt
- Provide labels for the following prompt
-
- >
-
-
-
-
+ }
+ inputs={}
+ controls={
+
+ submit(id, task.message_id, task.prompt, task.valid_labels, sliderValues)
+ }
+ />
+ }
+ />
);
};
export default LabelInitialPrompt;
-
-// TODO: consolidate with FlaggableElement
-
-interface CheckboxSliderGroupProps {
- labelIDs: Array;
- onChange: (sliderValues: number[]) => unknown;
-}
-
-const CheckboxSliderGroup = ({ labelIDs, onChange }: CheckboxSliderGroupProps) => {
- const [sliderValues, setSliderValues] = useState(Array.from({ length: labelIDs.length }).map(() => 0));
-
- useEffect(() => {
- onChange(sliderValues);
- }, [sliderValues, onChange]);
-
- return (
-
- {labelIDs.map((labelId, idx) => (
- {
- const newState = sliderValues.slice();
- newState[idx] = sliderValue;
- setSliderValues(newState);
- }}
- />
- ))}
-
- );
-};
-
-function CheckboxSliderItem(props: {
- labelId: string;
- sliderValue: number;
- sliderHandler: (newVal: number) => unknown;
-}) {
- const id = useId();
- const { colorMode } = useColorMode();
-
- const labelTextClass = colorMode === "light" ? `text-${colors.light.text}` : `text-${colors.dark.text}`;
-
- return (
- <>
-
- props.sliderHandler(val / 100)}>
-
-
-
-
-
- >
- );
-}