mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-06-28 16:20:34 +08:00
Merge pull request #19 from LAION-AI/web-fetch-tasks
Update the webapp to fetch and upate tasks in sequence
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
.venv
|
||||
*.pyc
|
||||
*.swp
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": "."
|
||||
}
|
||||
}
|
||||
@@ -36,47 +36,6 @@ export const authOptions = {
|
||||
return session;
|
||||
},
|
||||
},
|
||||
events: {
|
||||
/**
|
||||
* When a new user signs in, we register them with the Labeler backend.
|
||||
*/
|
||||
async signIn({ user, account, profile, isNewUser }) {
|
||||
if (!isNewUser) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
// Register the new user with the Labeler Backend.
|
||||
const res = await fetch(`${process.env.FASTAPI_URL}/api/v1/labelers`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-Key": process.env.FASTAPI_KEY,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
discord_username: user.id,
|
||||
display_name: user.name || user.email,
|
||||
is_enabled: true,
|
||||
notes: account.provider,
|
||||
}),
|
||||
});
|
||||
if (res.status !== 200) {
|
||||
console.error(res.statusText);
|
||||
return;
|
||||
}
|
||||
// Update the User entry with the Labeler Backend's ID so we can
|
||||
// reference it later.
|
||||
const { id: labelerId } = await res.json();
|
||||
await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
labelerId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default NextAuth(authOptions);
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
|
||||
export default function handler(req, res) {
|
||||
res.status(200).json({ name: "John Doe" });
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import { unstable_getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "pages/api/auth/[...nextauth]";
|
||||
|
||||
/**
|
||||
* Returns a new task created from the Task Backend. We do a few things here:
|
||||
*
|
||||
* 1) Get the task from the backend and register the requesting user.
|
||||
* 2) Store the task in our local database.
|
||||
* 3) Send and Ack to the Task Backend with our local id for the task.
|
||||
* 4) Return everything to the client.
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions);
|
||||
|
||||
// Return nothing if the user isn't registered.
|
||||
if (!session) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the new task.
|
||||
//
|
||||
// This needs to be refactored into an easier to use library.
|
||||
const taskRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/tasks/`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-Key": process.env.FASTAPI_KEY,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "rate_summary",
|
||||
user: {
|
||||
id: session.user.id,
|
||||
display_name: session.user.name,
|
||||
auth_method: "local",
|
||||
},
|
||||
}),
|
||||
});
|
||||
const task = await taskRes.json();
|
||||
|
||||
// Store the task and link it to the user..
|
||||
const registeredTask = await prisma.registeredTask.create({
|
||||
data: {
|
||||
task,
|
||||
user: {
|
||||
connect: {
|
||||
id: session.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Update the backend with our Task ID
|
||||
const ackRes = await fetch(
|
||||
`${process.env.FASTAPI_URL}/api/v1/tasks/${task.id}/ack`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-Key": process.env.FASTAPI_KEY,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
post_id: registeredTask.id,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const ack = await ackRes.json();
|
||||
|
||||
// Send the results to the client.
|
||||
res.status(200).json(registeredTask);
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import { unstable_getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "./auth/[...nextauth]";
|
||||
|
||||
/**
|
||||
* Returns a list of prompts from the Labeler Backend.
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions);
|
||||
|
||||
if (!session) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const promptRes = await fetch(`${process.env.FASTAPI_URL}/api/v1/prompts`, {
|
||||
headers: {
|
||||
"X-API-Key": process.env.FASTAPI_KEY,
|
||||
},
|
||||
});
|
||||
const prompts = await promptRes.json();
|
||||
|
||||
res.status(200).json(prompts);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
res.status(500);
|
||||
}
|
||||
res.end();
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
import { unstable_getServerSession } from "next-auth/next";
|
||||
import { authOptions } from "./auth/[...nextauth]";
|
||||
|
||||
/**
|
||||
* Stores the task interaction with the Task Backend and then returns the next task generated.
|
||||
*
|
||||
* This implicity does a few things:
|
||||
* 1) Stores the answer with the Task Backend.
|
||||
* 2) Records the new task in our local database.
|
||||
* 3) (TODO) Acks the new task with our local task ID to the Task Backend.
|
||||
* 4) Returns the newly created task to the client.
|
||||
*/
|
||||
export default async (req, res) => {
|
||||
const session = await unstable_getServerSession(req, res, authOptions);
|
||||
|
||||
// Return nothing if the user isn't registered.
|
||||
if (!session) {
|
||||
res.status(401).end();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse out the local task ID and the interaction contents.
|
||||
const { id, content } = await JSON.parse(req.body);
|
||||
|
||||
// Log the interaction locally to create our user_post_id needed by the Task
|
||||
// Backend.
|
||||
const interaction = await prisma.taskInteraction.create({
|
||||
data: {
|
||||
content,
|
||||
task: {
|
||||
connect: {
|
||||
id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Send the interaction to the Task Backend. This automatically fetches the
|
||||
// next task in the sequence (or the done task).
|
||||
const interactionRes = await fetch(
|
||||
`${process.env.FASTAPI_URL}/api/v1/tasks/interaction`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"X-API-Key": process.env.FASTAPI_KEY,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: "post_rating",
|
||||
user: {
|
||||
id: session.user.id,
|
||||
display_name: session.user.name,
|
||||
auth_method: "local",
|
||||
},
|
||||
post_id: id,
|
||||
user_post_id: interaction.id,
|
||||
...content,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const newTask = await interactionRes.json();
|
||||
|
||||
// Stores the new task with our database.
|
||||
const newRegisteredTask = await prisma.registeredTask.create({
|
||||
data: {
|
||||
task: newTask,
|
||||
user: {
|
||||
connect: {
|
||||
id: session.user.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// TODO: Ack the task with the Task Backend using the newly created local
|
||||
// task ID.
|
||||
|
||||
// Send the next task in the sequence to the client.
|
||||
res.status(200).json(newRegisteredTask);
|
||||
};
|
||||
@@ -5,7 +5,7 @@ import { useSession, signIn, signOut } from "next-auth/react";
|
||||
import { useEffect, useState } from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
import styles from "../styles/Home.module.css";
|
||||
import styles from "styles/Home.module.css";
|
||||
|
||||
const fetcher = (url) => axios.get(url).then((res) => res.data);
|
||||
|
||||
@@ -19,7 +19,6 @@ export default function Home() {
|
||||
return (
|
||||
<div className={styles.App}>
|
||||
<header className={styles.AppHeader}>
|
||||
{/* <img src={logo} className="App-logo" alt="logo" /> */}
|
||||
<h2>Open Assistant</h2>
|
||||
<p>
|
||||
Open Assistant is a project meant to give everyone access to a great
|
||||
@@ -60,8 +59,6 @@ export default function Home() {
|
||||
return (
|
||||
<div className={styles.App}>
|
||||
<header className={styles.AppHeader}>
|
||||
{/* <img src={logo} className="App-logo" alt="logo" /> */}
|
||||
|
||||
<h2>Open Assistant</h2>
|
||||
|
||||
<p>You are logged in</p>
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import axios from "axios";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useSession, signIn, signOut } from "next-auth/react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
|
||||
const fetcher = (url) => axios.get(url).then((res) => res.data);
|
||||
|
||||
/**
|
||||
* A helper function to post updates to tasks.
|
||||
* This ensures the content sent is serialized to JSON.
|
||||
*/
|
||||
async function sendRequest(url, { arg }) {
|
||||
return fetch(url, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(arg),
|
||||
});
|
||||
}
|
||||
|
||||
export default function NewPage() {
|
||||
// Use an array of tasks that record the sequence of steps until a task is
|
||||
// deemed complete.
|
||||
const [tasks, setTasks] = useState([]);
|
||||
|
||||
// A quick reference to the input element. This should be factored into the
|
||||
// component doing the actual task rendering.
|
||||
const responseEl = useRef(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", 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",
|
||||
sendRequest,
|
||||
{
|
||||
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 = (t) => {
|
||||
trigger({
|
||||
id: t.id,
|
||||
content: {
|
||||
rating: responseEl.current.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Show something informative while loading the first task.
|
||||
if (isLoading) {
|
||||
return <div>Loading</div>;
|
||||
}
|
||||
|
||||
// Iterate through each of the tasks and show it's contents, get a response to it, or show the done state.
|
||||
//
|
||||
// Right now this just works for the rating task.
|
||||
//
|
||||
// Displaying and fetching results for each task type should be factored into
|
||||
// different components that handle the presentation and response structures.
|
||||
// The results should be packaged into a single object with all the fields
|
||||
// sent to the backend.
|
||||
return (
|
||||
<div>
|
||||
{tasks.map((t) => (
|
||||
<div key={t.id}>
|
||||
<div>{t.task.type}</div>
|
||||
<div>{t.task.text}</div>
|
||||
{t.task.summary && (
|
||||
<>
|
||||
<div>{t.task.summary}</div>
|
||||
<div>
|
||||
{t.task.scale.min} to {t.task.scale.max}
|
||||
</div>
|
||||
<input type="text" ref={responseEl} />
|
||||
<button onClick={() => submitResponse(t)}>Submit Response</button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -41,11 +41,10 @@ model User {
|
||||
emailVerified DateTime?
|
||||
image String?
|
||||
|
||||
// Records the unique user id stored in the Labeler Backend.
|
||||
labelerId Int?
|
||||
|
||||
accounts Account[]
|
||||
sessions Session[]
|
||||
|
||||
tasks RegisteredTask[]
|
||||
}
|
||||
|
||||
model VerificationToken {
|
||||
@@ -55,3 +54,22 @@ model VerificationToken {
|
||||
|
||||
@@unique([identifier, token])
|
||||
}
|
||||
|
||||
model RegisteredTask {
|
||||
id String @id @default(uuid())
|
||||
task Json
|
||||
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
userId String
|
||||
|
||||
interaction TaskInteraction[]
|
||||
}
|
||||
|
||||
model TaskInteraction {
|
||||
id String @id @default(uuid())
|
||||
|
||||
content Json
|
||||
|
||||
task RegisteredTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
|
||||
taskId String
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user