Setting a simpler task path structure, Adding some layout to the task types, and fixing a ui bug in the rating task

This commit is contained in:
Keith Stevens
2022-12-27 11:04:17 +09:00
parent a8e7cee73c
commit 530599d59f
7 changed files with 275 additions and 223 deletions
+14
View File
@@ -105,6 +105,20 @@ When writing code for the website, we have a few best practices:
1. Define functional React components (with types for all properties when
feasible).
### URL Paths
To use stable and consistent URL paths, we recommend the following strategy for new tasks:
1. For any task that involves writing a free-form response, put the page under
`website/src/pages/create` with a page name matching the task type, such as
`summarize_story.tsx`.
1. For any task that evaluates, rates, or ranks content, put the page under
`website/src/pages/evaluate` with a page name matching the task type such
as `rate_summary.tsx`.
With this we'll be able to ensure these contribution pages are hidden from
logged out users but accessible to logged in users.
## Learn More
To learn more about Next.js, take a look at the following resources:
+19 -12
View File
@@ -1,8 +1,7 @@
import { chakra, Box, HStack, useRadio, useRadioGroup } from "@chakra-ui/react";
import { Box, HStack, useRadio, useRadioGroup } from "@chakra-ui/react";
const RatingRadioButton = (props) => {
const { option, ...radioProps } = props;
const { getInputProps, getCheckboxProps } = useRadio(radioProps);
const { state, getInputProps, getCheckboxProps } = useRadio(props);
const input = getInputProps();
const checkbox = getCheckboxProps();
@@ -27,25 +26,33 @@ const RatingRadioButton = (props) => {
px={5}
py={3}
>
{option}
{props.children}
</Box>
</Box>
);
};
const RatingRadioGroup = (props) => {
const { min, max, onChange, ...rest } = props;
const { value, getRadioProps, getRootProps } = useRadioGroup({
defaultValue: min,
const { min, max, onChange } = props;
const { getRadioProps, getRootProps } = useRadioGroup({
name: "rating",
defaultValue: `${min}`,
onChange: onChange,
});
const options = Array.from(new Array(1 + max - min), (x, i) => i + min);
const group = getRootProps();
const options = Array.from(new Array(1 + max - min), (x, i) => `${i + min}`);
return (
<HStack {...getRootProps()}>
{options.map((option) => (
<RatingRadioButton key={option} option={option} {...getRadioProps({ value: option })} />
))}
<HStack {...group}>
{options.map((option) => {
const radio = getRadioProps({ value: option });
return (
<RatingRadioButton key={option} {...radio}>
{option}
</RatingRadioButton>
);
})}
</HStack>
);
};
+1 -1
View File
@@ -4,5 +4,5 @@ export { default } from "next-auth/middleware";
* Guards all pages under `/grading` and redirects them to the sign in page.
*/
export const config = {
matcher: ["/grading/:path*"],
matcher: ["/create/:path*", "/evaluate/:path*"],
};
@@ -0,0 +1,131 @@
import { Textarea } from "@chakra-ui/react";
import Head from "next/head";
import { useRef, useState } from "react";
import useSWRImmutable from "swr/immutable";
import useSWRMutation from "swr/mutation";
import { Footer } from "src/components/Footer";
import { Header } from "src/components/Header";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
const SummarizeStory = () => {
// Use an array of tasks that record the sequence of steps until a task is
// deemed complete.
const [tasks, setTasks] = useState([]);
const inputRef = useRef<HTMLTextAreaElement>(null);
// Fetch the very fist task. We can ignore everything except isLoading
// because the onSuccess handler will update `tasks` when ready.
const { isLoading } = useSWRImmutable("/api/new_task/summarize_story", fetcher, {
onSuccess: (data) => {
setTasks([data]);
},
});
// Every time we submit an answer to the latest task, let the backend handle
// all the interactions then add the resulting task to the queue. This ends
// when we hit the done task.
const { trigger, isMutating } = useSWRMutation("/api/update_task", poster, {
onSuccess: async (data) => {
const newTask = await data.json();
// This is the more efficient way to update a react state array.
setTasks((oldTasks) => [...oldTasks, newTask]);
},
});
// Trigger a mutation that updates the current task. We should probably
// signal somewhere that this interaction is being processed.
const submitResponse = (task: { id: string }) => {
const text = inputRef.current.value.trim();
trigger({
id: task.id,
update_type: "text_reply_to_post",
content: {
text,
},
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (tasks.length == 0) {
return <div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">Loading...</div>;
}
return (
<>
<Head>
<title>Summarize A Story</title>
<meta name="description" content="Summarize a story to train our model." />
</Head>
<Header />
<main className="h-3/4 z-0 bg-white flex flex-col items-center justify-center gap-2">
<div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">
{/* Instrunction and Output panels */}
<section className="mb-8 lt-lg:mb-12 ">
<div className="grid lg:gap-x-12 lg:grid-cols-2">
{/* Instruction panel */}
<div className="rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold">Instruction</h5>
<p className="text-lg py-1">Summarize the following story</p>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">
{tasks[0].task.story}
</div>
</div>
</div>
{/* Output panel */}
<div className="mt-6 lg:mt-0 rounded-lg shadow-lg h-full block bg-white">
<div className="flex justify-center p-6">
<Textarea name="summary" placeholder="Summary" ref={inputRef} />
</div>
</div>
</div>
</section>
{/* Info & controls */}
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="flex flex-col justify-self-start text-gray-700">
<div>
<span>
<b>Prompt</b>
</span>
<span className="ml-2">{tasks[0].id}</span>
</div>
<div>
<span>
<b>Output</b>
</span>
<span className="ml-2">Submit your answer</span>
</div>
</div>
{/* Skip / Submit controls */}
<div className="flex justify-center ml-auto">
<button
type="button"
className="mr-2 inline-flex items-center rounded-md border border-transparent bg-indigo-100 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Skip
</button>
<button
type="button"
onClick={() => submitResponse(tasks[0])}
className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Submit
</button>
</div>
</section>
</div>
</main>
<Footer />
</>
);
};
export default SummarizeStory;
@@ -1,15 +1,18 @@
import { HStack, Textarea } from "@chakra-ui/react";
import { QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
import Head from "next/head";
import { useSession, signIn, signOut } from "next-auth/react";
import { useEffect, useRef, useState } from "react";
import useSWRMutation from "swr/mutation";
import useSWRImmutable from "swr/immutable";
import useSWRMutation from "swr/mutation";
import { Footer } from "src/components/Footer";
import { Header } from "src/components/Header";
import RatingRadioGroup from "src/components/RatingRadioGroup";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
const GradeOutput = () => {
const RateSummary = () => {
// Use an array of tasks that record the sequence of steps until a task is
// deemed complete.
const [tasks, setTasks] = useState([]);
@@ -58,93 +61,109 @@ const GradeOutput = () => {
}
return (
<div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">
{/* Instrunction and Output panels */}
<section className="mb-8 lt-lg:mb-12 ">
<div className="grid lg:gap-x-12 lg:grid-cols-2">
{/* Instruction panel */}
<div className="rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Instruction</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">
{tasks[0].task.full_text}
<>
<Head>
<title>Rate A Summary</title>
<meta name="description" content="Rate a proposed story summary." />
</Head>
<Header />
<main className="z-0 bg-white flex flex-col items-center justify-center gap-2">
<div className=" p-6 mx-auto bg-slate-100 text-gray-800">
{/* Instrunction and Output panels */}
<section className="mb-8 lt-lg:mb-12 ">
<div className="grid lg:gap-x-12 lg:grid-cols-2">
{/* Instruction panel */}
<div className="rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Instruction</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">
{tasks[0].task.full_text}
</div>
</div>
</div>
{/* Output panel */}
<div className="mt-6 lg:mt-0 rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Output</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">
{tasks[0].task.summary}
</div>
</div>
{/* Form wrap*/}
<div className="p-6">
<h3 className="text-lg text-center font-medium leading-6 text-gray-900">Rating</h3>
<p className="text-center mt-1 text-sm text-gray-500">
({tasks[0].task.scale.min} = worst, {tasks[0].task.scale.max} = best)
</p>
{/* Rating buttons */}
<div className="flex justify-center p-6">
<RatingRadioGroup
min={tasks[0].task.scale.min}
max={tasks[0].task.scale.max}
onChange={setRating}
/>
</div>
</div>
{/* Annotation checkboxes */}
<div className="flex justify-center px-10">
<ul>
{ANNOTATION_FLAGS.map((option, i) => {
return <AnnotationCheckboxLi option={option} key={i}></AnnotationCheckboxLi>;
})}
</ul>
</div>
<div className="flex justify-center p-6">
<Textarea name="notes" placeholder="Optional notes" />
</div>
</div>
</div>
</div>
</section>
{/* Output panel */}
<div className="mt-6 lg:mt-0 rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold mb-4">Output</h5>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{tasks[0].task.summary}</div>
</div>
{/* Form wrap*/}
<div className="p-6">
<h3 className="text-lg text-center font-medium leading-6 text-gray-900">Rating</h3>
<p className="text-center mt-1 text-sm text-gray-500">
({tasks[0].task.scale.min} = worst, {tasks[0].task.scale.max} = best)
</p>
{/* Rating buttons */}
<div className="flex justify-center p-6">
<RatingRadioGroup min={tasks[0].task.scale.min} max={tasks[0].task.scale.max} onChange={setRating} />
{/* Info & controls */}
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="flex flex-col justify-self-start text-gray-700">
<div>
<span>
<b>Prompt</b>
</span>
<span className="ml-2">{tasks[0].id}</span>
</div>
<div>
<span>
<b>Output</b>
</span>
<span className="ml-2">{tasks.length === 2 ? tasks[1].id : "Submit your answer"}</span>
</div>
</div>
{/* Annotation checkboxes */}
<div className="flex justify-center px-10">
<ul>
{ANNOTATION_FLAGS.map((option, i) => {
return <AnnotationCheckboxLi option={option} key={i}></AnnotationCheckboxLi>;
})}
</ul>
{/* Skip / Submit controls */}
<div className="flex justify-center ml-auto">
<button
type="button"
className="mr-2 inline-flex items-center rounded-md border border-transparent bg-indigo-100 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Skip
</button>
<button
type="button"
onClick={() => submitResponse(tasks[0])}
className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Submit
</button>
</div>
<div className="flex justify-center p-6">
<Textarea name="notes" placeholder="Optional notes" />
</div>
</div>
</section>
</div>
</section>
{/* Info & controls */}
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="flex flex-col justify-self-start text-gray-700">
<div>
<span>
<b>Prompt</b>
</span>
<span className="ml-2">{tasks[0].id}</span>
</div>
<div>
<span>
<b>Output</b>
</span>
<span className="ml-2">{tasks.length === 2 ? tasks[1].id : "Submit your answer"}</span>
</div>
</div>
{/* Skip / Submit controls */}
<div className="flex justify-center ml-auto">
<button
type="button"
className="mr-2 inline-flex items-center rounded-md border border-transparent bg-indigo-100 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Skip
</button>
<button
type="button"
onClick={() => submitResponse(tasks[0])}
className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Submit
</button>
</div>
</section>
</div>
</main>
<Footer />
</>
);
};
export default GradeOutput;
export default RateSummary;
function AnnotationCheckboxLi(props: { option: annotationBool }): JSX.Element {
let AdditionalExplanation = null;
+13 -14
View File
@@ -1,18 +1,15 @@
import { useSession } from "next-auth/react";
import { Button, Input, Stack } from "@chakra-ui/react";
import Head from "next/head";
import Link from "next/link";
import { Button, Input, Stack } from "@chakra-ui/react";
import { useSession } from "next-auth/react";
import { CallToAction } from "../components/CallToAction";
import { Faq } from "../components/Faq";
import { Footer } from "../components/Footer";
import { Header } from "../components/Header";
import { Hero } from "../components/Hero";
import { CallToAction } from "src/components/CallToAction";
import { Faq } from "src/components/Faq";
import { Footer } from "src/components/Footer";
import { Header } from "src/components/Header";
import { Hero } from "src/components/Hero";
import styles from "../styles/Home.module.css";
export default function Home() {
const Home = () => {
const { data: session } = useSession();
if (!session) {
@@ -48,13 +45,15 @@ export default function Home() {
<Header />
<main className="h-3/4 z-0 bg-white flex flex-col items-center justify-center gap-2">
<Button size="lg" colorScheme="blue" className="drop-shadow">
<Link href="/grading/grade-output">Rate a prompt and output now</Link>
<Link href="/evaluate/rate_summary">Rate a Summary</Link>
</Button>
<Button size="lg" colorScheme="blue" className="drop-shadow">
<Link href="/summarize/story">Summarize a story</Link>
<Link href="/create/summarize_story">Summarize a story</Link>
</Button>
</main>
<Footer />
</>
);
}
};
export default Home;
-118
View File
@@ -1,118 +0,0 @@
// TODO(#65): Unify and simplify the task paths
import { Textarea } from "@chakra-ui/react";
import { useRef, useState } from "react";
import useSWRMutation from "swr/mutation";
import useSWRImmutable from "swr/immutable";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
const SummarizeStory = () => {
// Use an array of tasks that record the sequence of steps until a task is
// deemed complete.
const [tasks, setTasks] = useState([]);
const inputRef = useRef<HTMLTextAreaElement>(null);
// Fetch the very fist task. We can ignore everything except isLoading
// because the onSuccess handler will update `tasks` when ready.
const { isLoading } = useSWRImmutable("/api/new_task/summarize_story", fetcher, {
onSuccess: (data) => {
console.log(data);
setTasks([data]);
},
});
// Every time we submit an answer to the latest task, let the backend handle
// all the interactions then add the resulting task to the queue. This ends
// when we hit the done task.
const { trigger, isMutating } = useSWRMutation("/api/update_task", poster, {
onSuccess: async (data) => {
const newTask = await data.json();
// This is the more efficient way to update a react state array.
setTasks((oldTasks) => [...oldTasks, newTask]);
},
});
// Trigger a mutation that updates the current task. We should probably
// signal somewhere that this interaction is being processed.
const submitResponse = (task: { id: string }) => {
const text = inputRef.current.value.trim();
trigger({
id: task.id,
content: {
update_type: "text_reply_to_post",
text,
},
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (tasks.length == 0) {
return <div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">Loading...</div>;
}
return (
<div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">
{/* Instrunction and Output panels */}
<section className="mb-8 lt-lg:mb-12 ">
<div className="grid lg:gap-x-12 lg:grid-cols-2">
{/* Instruction panel */}
<div className="rounded-lg shadow-lg h-full block bg-white">
<div className="p-6">
<h5 className="text-lg font-semibold">Instruction</h5>
<p className="text-lg py-1">Summarize the following story</p>
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{tasks[0].task.story}</div>
</div>
</div>
{/* Output panel */}
<div className="mt-6 lg:mt-0 rounded-lg shadow-lg h-full block bg-white">
<div className="flex justify-center p-6">
<Textarea name="summary" placeholder="Summary" ref={inputRef} />
</div>
</div>
</div>
</section>
{/* Info & controls */}
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="flex flex-col justify-self-start text-gray-700">
<div>
<span>
<b>Prompt</b>
</span>
<span className="ml-2">{tasks[0].id}</span>
</div>
<div>
<span>
<b>Output</b>
</span>
<span className="ml-2">Submit your answer</span>
</div>
</div>
{/* Skip / Submit controls */}
<div className="flex justify-center ml-auto">
<button
type="button"
className="mr-2 inline-flex items-center rounded-md border border-transparent bg-indigo-100 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Skip
</button>
<button
type="button"
onClick={() => submitResponse(tasks[0])}
className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
>
Submit
</button>
</div>
</section>
</div>
);
};
export default SummarizeStory;