diff --git a/docker/Dockerfile.website b/docker/Dockerfile.website new file mode 100644 index 00000000..afb18baf --- /dev/null +++ b/docker/Dockerfile.website @@ -0,0 +1,62 @@ +# Install dependencies only when needed +FROM node:16.19 AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +# RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Install dependencies based on the preferred package manager +COPY ./website/package.json ./website/package-lock.json ./ +RUN \ + if [ -f package-lock.json ]; then npm ci; \ + else echo "Lockfile not found." && exit 1; \ + fi + +# Rebuild the source code only when needed +FROM node:16.19 AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY ./website/ . + +# Next.js collects completely anonymous telemetry data about general usage. +# Learn more here: https://nextjs.org/telemetry +# Uncomment the following line in case you want to disable telemetry during the build. +# ENV NEXT_TELEMETRY_DISABLED 1 + +# RUN yarn build +RUN npx prisma generate +RUN npm run build + +# Production image, copy all the files and run next +FROM node:16.19 AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +# ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Copy over the prisma schema so we can to `npx prisma db push` and ensure the +# database exists on startup. +COPY --chown=nextjs:nodejs ./website/prisma/schema.prisma ./ +# Copy over a startup script that'll run `npx prisma db push` before starting +# the webserver. This ensures the webserver can actually check user accounts. +# This is a prisma variant of the postgres solution suggested in +# https://docs.docker.com/compose/startup-order/ +COPY --chown=nextjs:nodejs ./website/wait-for-postgres.sh ./ + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 + +CMD ["node", "server.js"] diff --git a/scripts/endtoend-demo/README.md b/scripts/endtoend-demo/README.md new file mode 100644 index 00000000..2d738453 --- /dev/null +++ b/scripts/endtoend-demo/README.md @@ -0,0 +1,14 @@ +# End to End Demo + +This sets up an entire stack needed to run Open Assistant, including the +website, backend, and associated dependent services. + +To start the service, do the following: + +```sh +docker compose up --build +``` + +Then, navigate to `http://localhost:3000` and interact with the website. When +logging in, navigate to `http://localhost:1080` to get the magic email login +link. diff --git a/scripts/endtoend-demo/docker-compose.yaml b/scripts/endtoend-demo/docker-compose.yaml new file mode 100644 index 00000000..8aa2b0fa --- /dev/null +++ b/scripts/endtoend-demo/docker-compose.yaml @@ -0,0 +1,90 @@ +version: "3.7" + +services: + # This DB is for the FastAPI Backend. + db: + image: postgres + restart: always + ports: + - 5432:5432 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 2s + timeout: 2s + retries: 10 + + # This DB is for Web Authentication and data caching. + webdb: + image: postgres + restart: always + ports: + - 5433:5432 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 2s + timeout: 2s + retries: 10 + + # This lets you manually inspect the web and backend databases. + adminer: + image: adminer + restart: always + ports: + - 8089:8080 + + # This fakes an SMTP email server used by website authentication. + # User registration emails can be found by going to localhost:1080 and + # opening the emails listed. + maildev: + image: maildev/maildev + restart: always + environment: + - MAILDEV_WEB_PORT=1080 + - MAILDEV_SMTP_PORT=1025 + ports: + - "1080:1080" + - "1025:1025" + + # The oassist backend service. + backend: + build: + dockerfile: docker/Dockerfile.backend + context: ../../ + image: oasst-backend + environment: + - POSTGRES_HOST=db + - ALLOW_ANY_API_KEY=True + - MAX_WORKERS=1 + depends_on: + db: + condition: service_healthy + ports: + - "8080:8080" + + # The oassist web service. + web: + build: + dockerfile: docker/Dockerfile.website + context: ../../ + image: oasst-web + environment: + - DATABASE_URL=postgres://postgres:postgres@webdb/ocgpt_website + - FASTAPI_URL=http://backend:8080 + - FASTAPI_KEY=1234 + - NEXTAUTH_SECRET=O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98= + - EMAIL_SERVER_HOST=maildev + - EMAIL_SERVER_PORT=1025 + - EMAIL_FROM=info@example.com + - NEXTAUTH_URL=http://localhost:3000 + depends_on: + webdb: + condition: service_healthy + ports: + - "3000:3000" + command: bash wait-for-postgres.sh node server.js diff --git a/scripts/frontend-development/docker-compose.yaml b/scripts/frontend-development/docker-compose.yaml index 6f9c01f4..99f30f62 100644 --- a/scripts/frontend-development/docker-compose.yaml +++ b/scripts/frontend-development/docker-compose.yaml @@ -47,8 +47,9 @@ services: ports: - "8080:8080" - # This fakes and SMTP email server. User registration emails can be found by going to - # localhost:1080 and opening the emails listed. + # This fakes an SMTP email server used by website authentication. + # User registration emails can be found by going to localhost:1080 and + # opening the emails listed. maildev: image: maildev/maildev restart: always diff --git a/website/README.md b/website/README.md index d88f6bed..7d57ceaa 100644 --- a/website/README.md +++ b/website/README.md @@ -1,31 +1,109 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Open-Assistant NextJS Website -## Required Environment Variables +## Purpose -This requires: +This provides a comprehensive webapp interface for LAION's Open Assistant +project. Initially it will support: -- `NEXT_PUBLIC_SUPABASE_URL`: A supabase Authorization URL -- `NEXT_PUBLIC_SUPABASE_ANON_KEY`: A public supabase key for Authorization +1. User registration using either Discord or Email. +1. Adding responses to incomplete Open Assistant tasks. +1. Rating or Ranking responses to Open Assistant tasks. +1. Viewing an activity leaderboard. +1. Tracking community wide updates. -These can both be optained from the Supabase project settings page. +This interface compliments the Discord bot and will give access to the same +underlying tasks. -## Getting Started +## Contributing -First, run the development server: +### Major Dependencies -```bash -npm run dev -# or -yarn dev -``` +This website is built using: -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +1. [npm](https://www.npmjs.com/): The node package manager for building. +1. [React](https://reactjs.org/): The core frontend framework. +1. [Next.js](https://nextjs.org/): A React scaffolding framework to streamline + development. +1. [Prisma](https://www.prisma.io/): An ORM to interact with a web specific + [Postgres](https://www.postgresql.org/) database. +1. [NextAuth.js](https://next-auth.js.org/): A user authentication framework + to ensure we handle accounts with best practices. +1. [TailwindCSS](https://tailwindcss.com/): A general purpose framework for + styling any component. +1. [Chakra-UI](https://chakra-ui.com/): A wide collection of pre-built UI + components that generally look pretty good. -You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. +### Set up your environment -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. +To contribute to the website, make sure you have the following setup and +installed: -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. +1. [NVM](https://github.com/nvm-sh/nvm): The Node Version Manager makes it + easy to ensure you have the right NodeJS version installed. Once installed, + run `nvm use 16` to use Node 16.x. The website is known to be stable with + NodeJS version 16.x. This will install both Node and NPM. +1. [Docker](https://www.docker.com/): We use docker to simplify running + dependent services. + +### Getting everything up and running + +If you're doing active development we suggest the following workflow: + +1. In one tab, navigate to + `${OPEN_ASSISTANT_ROOT}/scripts/frontend-development`. +1. Run `docker compose up --build`. You can optionally include `-d` to detach and + later track the logs if desired. +1. In another tab navigate to `${OPEN_ASSISTANT_ROOT/website`. +1. Run `npm install` +1. Run `npx prisma db push` (This is also needed when you restart the docker + stack from scratch). +1. Run `npm run dev`. Now the website is up and running locally at + `http://localhost:3000`. +1. To create an account, login via the user using email authentication and + navigate to `http://localhost:1080`. Check the email listed and click the + log in link. You're now logged in and authenticated. + +## Code Layout + +### React Code + +All react code is under `src/` with a few sub directories: + +1. `pages/`: All pages a user could navigate too and API URLs which are under `pages/api/`. +1. `components/`: All re-usable React components. If something gets used + twice we should create a component and put it here. +1. `lib/`: A generic place to store library files that are used anywhere. + This doesn't have much structure yet. + +NOTE: `styles/` can be ignored for now. + +### Database + +All database configurations are stored in `prisma/schema.prisma`. + +### Static Content + +All static images, fonts, svgs, etc are stored in `public/`. + +### Styles + +We're not really using CSS styles. `styles/` can be ignored. + +## Best Practices + +When writing code for the website, we have a few best practices: + +1. When importing packages import external dependencies first then local + dependencies. Order them alphabetically according to the package name. +1. When trying to implement something new, check if + [Chakra-UI](https://chakra-ui.com/) has components that are close enough to + your need. For example Sliders, Radio Buttons, Progress indicators, etc. They + have a lot and we can save time by re-using what they have and tweaking the + style as needed. +1. Format everything with [Prettier](https://prettier.io/). This is done by + default with pre-submits. We currently don't have any custom settings. +1. Define functional React components (with types for all properties when + feasible). ## Learn More @@ -33,11 +111,3 @@ To learn more about Next.js, take a look at the following resources: - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/website/next.config.js b/website/next.config.js index c1c1519d..1cbdb0e0 100644 --- a/website/next.config.js +++ b/website/next.config.js @@ -1,5 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + output: "standalone", reactStrictMode: true, experimental: { scrollRestoration: true, diff --git a/website/package.json b/website/package.json index b7680c79..10708782 100644 --- a/website/package.json +++ b/website/package.json @@ -17,10 +17,6 @@ "@heroicons/react": "^2.0.13", "@next-auth/prisma-adapter": "^1.0.5", "@prisma/client": "^4.7.1", - "@supabase/auth-helpers-nextjs": "^0.5.2", - "@supabase/auth-helpers-react": "^0.3.1", - "@supabase/auth-ui-react": "^0.2.6", - "@supabase/supabase-js": "^2.1.4", "@tailwindcss/forms": "^0.5.3", "autoprefixer": "^10.4.13", "axios": "^1.2.1", diff --git a/website/src/components/CallToAction.tsx b/website/src/components/CallToAction.tsx index 61f11540..35949645 100644 --- a/website/src/components/CallToAction.tsx +++ b/website/src/components/CallToAction.tsx @@ -16,7 +16,7 @@ export function CallToAction() { here:

- + + ); } return ( - + ); } diff --git a/website/src/pages/api/auth/[...nextauth].ts b/website/src/pages/api/auth/[...nextauth].ts index 6b2c0ae7..86d8bdc4 100644 --- a/website/src/pages/api/auth/[...nextauth].ts +++ b/website/src/pages/api/auth/[...nextauth].ts @@ -1,3 +1,4 @@ +import type { AuthOptions } from "next-auth"; import NextAuth from "next-auth"; import DiscordProvider from "next-auth/providers/discord"; import EmailProvider from "next-auth/providers/email"; @@ -31,7 +32,7 @@ if (process.env.DISCORD_CLIENT_ID) { ); } -export const authOptions = { +export const authOptions: AuthOptions = { // Ensure we can store user data in a database. adapter: PrismaAdapter(prisma), providers, diff --git a/website/src/pages/api/new_task/[task_type].ts b/website/src/pages/api/new_task/[task_type].ts index 0d45c5f3..3943536c 100644 --- a/website/src/pages/api/new_task/[task_type].ts +++ b/website/src/pages/api/new_task/[task_type].ts @@ -1,5 +1,6 @@ import { getToken } from "next-auth/jwt"; +import prisma from "src/lib/prismadb"; import { authOptions } from "src/pages/api/auth/[...nextauth]"; /** @@ -10,7 +11,7 @@ import { authOptions } from "src/pages/api/auth/[...nextauth]"; * 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 handler = async (req, res) => { const { task_type } = req.query; const token = await getToken({ req }); @@ -69,3 +70,5 @@ export default async (req, res) => { // Send the results to the client. res.status(200).json(registeredTask); }; + +export default handler; diff --git a/website/src/pages/api/update_task.ts b/website/src/pages/api/update_task.ts index b7919c5a..6760623c 100644 --- a/website/src/pages/api/update_task.ts +++ b/website/src/pages/api/update_task.ts @@ -1,5 +1,6 @@ import { getToken } from "next-auth/jwt"; +import prisma from "src/lib/prismadb"; import { authOptions } from "src/pages/api/auth/[...nextauth]"; /** @@ -11,7 +12,7 @@ import { authOptions } from "src/pages/api/auth/[...nextauth]"; * 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 handler = async (req, res) => { const token = await getToken({ req }); // Return nothing if the user isn't registered. @@ -76,3 +77,5 @@ export default async (req, res) => { // Send the next task in the sequence to the client. res.status(200).json(newRegisteredTask); }; + +export default handler; diff --git a/website/src/pages/grading/grade-output.tsx b/website/src/pages/grading/grade-output.tsx index 5fabe7ba..c04a99c3 100644 --- a/website/src/pages/grading/grade-output.tsx +++ b/website/src/pages/grading/grade-output.tsx @@ -134,7 +134,7 @@ const GradeOutput = () => { diff --git a/website/tsconfig.json b/website/tsconfig.json index 1563f3e8..8a5ec4fe 100644 --- a/website/tsconfig.json +++ b/website/tsconfig.json @@ -13,7 +13,11 @@ "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve" + "jsx": "preserve", + "baseUrl": ".", + "paths": { + "@/*": ["src/*"] + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/website/wait-for-postgres.sh b/website/wait-for-postgres.sh new file mode 100755 index 00000000..6ff1a024 --- /dev/null +++ b/website/wait-for-postgres.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# wait-for-postgres.sh +# +# This script tries to connect to a postgres database and then runs +# `npx prisma db push` +# It repeats until that setup completes (running multiple times will be a +# noop). Then, it'll run the next command provided. +# +# You don't need to provide the connection URL. This reads it from the +# DATABASE_URL environment variable which should be set. +# +# To run we suggest: +# ./wait-for-postgres.sh npm run dev + +set -e + +until npx prisma db push --skip-generate; do + >&2 echo "Postgres is unavailable - sleeping" + sleep 1 +done + +>&2 echo "Postgres is up - executing command" +# Print and execute all other arguments starting with `$1` +# So `exec "$1" "$2" "$3" ...` +exec "$@"