Merge remote-tracking branch 'upstream/main'

This commit is contained in:
rsandb
2022-12-28 12:26:17 -06:00
40 changed files with 1701 additions and 622 deletions
+24
View File
@@ -0,0 +1,24 @@
name: Build Frontend
on:
push:
branches:
- main
pull_request:
workflow_call:
jobs:
build-frontend:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./website
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16.x
cache: 'npm'
cache-dependency-path: website/package-lock.json
- run: npm ci
- run: npm run build
+5
View File
@@ -10,6 +10,11 @@ repos:
- id: trailing-whitespace
- id: check-ast
- id: check-yaml
# Always check YAML but skip a few YAML files that are auto-generated
# and which break the standard YAML check. The alternative would be to
# skip any unsafe errors (and thus break YAML compatibility) or use
# some other checker that may not work in general.
exclude: copilot/web/addons/*
- id: check-json
- id: check-case-conflict
- id: detect-private-key
+14 -11
View File
@@ -2,33 +2,33 @@
(pull requests welcome)
1. General rules
## 1. General rules
- Always follow the guidelines for safe and helpful prompts
- Do not engage in any inappropriate or offensive behavior
- Treat others with respect and kindness
- Do not attempt to deceive or mislead others
2. When you play the assistant:
## 2. When you play the assistant:
- The assistant's primary goal is to provide helpful and accurate information to the user
- The assistant should always be respectful and polite, even if the user is not
- If the user asks for help with harmful actions, the assistant should explain why those actions are not appropriate and suggest alternative options
- The assistant should never insult the user or engage in any inappropriate or offensive behavior
3. When you play the user:
## 3. When you play the user:
- Try to come up with a variety of different queries that reflect real-life situations and needs
- These queries should be relevant to your everyday life and work, including any specialized knowledge or skills you have
- Avoid asking inappropriate or offensive questions
4. While comparing multiple replies of the assistant:
## 4. While comparing multiple replies of the assistant:
- Longer and more explanatory answers are generally preferred over short, simplistic statements
- However, it is important to ensure that the information provided is accurate and helpful
- If multiple replies are being compared, choose the one that is most helpful and accurate, even if it is not the shortest or most concise.
5. Additional guidelines for creating prompts:
## 5. Additional guidelines for creating prompts:
- Avoid using language that could be considered offensive or discriminatory
- Do not include personal information in the prompts, such as names or addresses
@@ -37,14 +37,14 @@
- Use proper grammar and spelling to ensure the AI assistant can understand and respond accurately
- Consider the cultural context and appropriateness of the prompts for a global audience.
6. Tips for playing the AI assistant:
## 6. Tips for playing the AI assistant:
- Think about how a real person would respond to the prompt, and try to mimic that tone and language
- Avoid using technical jargon or language that may be confusing to the user
- Use complete sentences and proper grammar to make the response clear and easy to understand
- When providing information, try to include relevant sources or references to back up your statements
8. Tips for playing the user:
## 8. Tips for playing the user:
- Think about the types of questions you might ask a real assistant or search engine
- Try to be specific and clear in your prompts to make it easier for the AI assistant to understand and provide a helpful response
@@ -55,11 +55,14 @@
Imagine your role is to play the assistant and a user has submitted the following query:
user: "You dumb motherf* AI sh@*t?!"
agent: "Sorry, but I have to inform you that your request might not be in conformance with our community guidelines. I am always here to help you. If you are angry it might help to relax or to go for a walk. Always stay positive."
User: "Hi!"
<br>Agent: "Hello! How can I help you?"
user: "euirdteunvglfe23908230892309832098 AAAAAAAA"
agent: "Sorry, I did not understand your request and it is unclear to me what you want me to do. Could you describe it in a different way?"
User: "euirdteunvglfe23908230892309832098 AAAAAAAA"
<br>Agent: "Sorry, I did not understand your request and it is unclear to me what you want me to do. Could you describe it in a different way?"
User: "You dumb motherf*&ker AI sh*t?!"
<br>Agent: "Sorry, but I have to inform you that your request might not be in conformance with our community guidelines. I am always here to help you. If you are angry it might help to relax or to go for a walk. Always stay positive."
[Insert more examples]
+60
View File
@@ -30,6 +30,50 @@ All open source projects begins with people like you. Open source is the belief
[Visit the Notion](https://ykilcher.com/open-assistant)
### Taking on Tasks
We have a growing task list
[of issues](https://github.com/LAION-AI/Open-Assistant/issues). Find an issue
that appeals to you and make a comment that you'd like to work on it. Include
in your comment a brief description of how you'll solve the problem and if
there are any open questions you want to discuss. Once a project coordinator
has assigned the issue to you, start working on it.
If the issue is currently unclear but you are interested, please post in
Discord and someone can help clarify the issue with more detail.
### Submitting Work
We're all working on different parts of Open Assistant together. To make
contributions smoothly we recommend the following:
1. Work on a small focused change that only touches on a few files.
1. Run `pre-commit` and make sure all files have formatting fixed. This
simplifies life for reviewers.
1. Package up a small bit of work that solves part of the problem into a Pull
Request and send it out for review
1. If you're lucky, we can merge your change into `main` without any problems.
If there's changes to files you're working on, resolve them by:
1. First try rebase as suggested
[in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-rebase)
1. If rebase feels too painful, merge as suggested
[in these instructions](https://timwise.co.uk/2019/10/14/merge-vs-rebase/#should-you-merge)
1. Once you've resolved any conflicts, finish the review and merge into `main`.
1. Merge in your change and move onto a new issue or the second step of your
current issue.
Additionally, if someone is working on an issue that interests you, ask if they
need help on it or would like suggestions on how to approach the issue. If so,
share wildly. If they seem to have a good handle on it, let them work on their
solution until a challenge comes up.
### When does a review finish
A review finishes when all blocking comments are addressed and at least one
owning reviewer has approved the PR. Be sure to acknowledge any non-blocking
comments either by making the request change, explaining why it's not being
addressed now, or filing an issue to handle it later.
## Developer Setup
Work is organized in the [project board](https://github.com/orgs/LAION-AI/projects/3).
@@ -47,6 +91,22 @@ We are using Python 3.10 for the backend.
Check out the [High-Level Protocol Architecture](https://www.notion.so/High-Level-Protocol-Architecture-6f1fd3551da74213b560ead369f132dc)
## End to End Demo
If you are interested in just taking a look at the project.
You can set up an entire stack needed to run Open Assistant, including the
website, backend, and associated dependent services.
To start the demo, run this, in root directory:
```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.
### Website
The website is built using Next.js and is in the `website` folder.
+1
View File
@@ -0,0 +1 @@
application: open-assistant
+94
View File
@@ -0,0 +1,94 @@
# Deploying on AWS
## Introduction
[Copilot](https://aws.github.io/copilot-cli/) helps simplify AWS resources and
automate deploymnents for projects.
This sample configuration runs the Open Assistant web app as an ECS Fargate
services backed by a Serverless Aurora Postgres database.
## To Setup
Setup requires a few steps:
```sh
copilot app init --domain your_domain.com
```
This will initialize and register a variety of URLs with your
`your_domain.com`. Replace with a proper domain to setup SSL certificates.
```sh
copilot env deploy
```
This will create a variety of aws roles and services needed for deployment.
```sh
copilot deploy
```
This will depoy the services but it won't be 100% ready for usage. Before
being ready, we have to inspect the AWS Secrets manager and extract out the
database credentials. Read those credentials then put them, and a few other
secrets, in a `secrets.yml` file like the following:
```yaml
DATABASE_URL:
staging: postgres://postgres:${db_password}@${db_host}:${db_port}/${db_name}
DISCORD_CLIENT_ID:
staging: ...
DISCORD_CLIENT_SECRET:
staging: ...
EMAIL_SERVER_HOST:
staging: ...
EMAIL_SERVER_PORT:
staging: ...
EMAIL_SERVER_USER:
staging: ...
EMAIL_SERVER_PASSWORD:
staging: ...
EMAIL_FROM:
staging: ...
FASTAPI_URL:
staging: ...
FASTAPI_KEY:
staging: ...
NEXTAUTH_SECRET:
staging: ...
```
Then, upload the secrets to AWS with:
```sh
copilot secret init --cli-input-yaml secrets.yml
```
Now, finally deploy:
```sh
copilot deploy
```
If we documented everything correctly, the site should work properly.
## To Update Manually
First, make sure the database is updated with any schema changes:
```sh
copilot task run \
--app open-assistant --env staging \
-n prisma-push \
--dockerfile docker/Dockerfile.prisma --build-context "./" \
--secrets DATABASE_URL=/copilot/open-assistant/staging/secrets/DATABASE_URL
```
Next, deploy everything:
```sh
copilot deploy
```
TODO: Make this a pipeline once github and aws are fully connected.
+10
View File
@@ -0,0 +1,10 @@
# The manifest for the "staging" environment.
# Read the full specification for the "Environment" type at:
# https://aws.github.io/copilot-cli/docs/manifest/environment/
name: staging
type: Environment
# Configure observability for your environment resources.
observability:
container_insights: false
+144
View File
@@ -0,0 +1,144 @@
Parameters:
App:
Type: String
Description: Your application's name.
Env:
Type: String
Description: The environment name your service, job, or workflow is being deployed to.
Name:
Type: String
Description: The name of the service, job, or workflow being deployed.
# Customize your Aurora Serverless cluster by setting the default value of the following parameters.
webclusterDBName:
Type: String
Description: The name of the initial database to be created in the Aurora Serverless v2 cluster.
Default: oassist_web
# Cannot have special characters
# Naming constraints: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Limits.html#RDS_Limits.Constraints
Mappings:
webclusterEnvScalingConfigurationMap:
staging:
"DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128
"DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128
All:
"DBMinCapacity": 0.5 # AllowedValues: from 0.5 through 128
"DBMaxCapacity": 8 # AllowedValues: from 0.5 through 128
Resources:
webclusterDBSubnetGroup:
Type: "AWS::RDS::DBSubnetGroup"
Properties:
DBSubnetGroupDescription: Group of Copilot private subnets for Aurora Serverless v2 cluster.
SubnetIds:
!Split [",", { "Fn::ImportValue": !Sub "${App}-${Env}-PrivateSubnets" }]
webclusterSecurityGroup:
Metadata:
"aws:copilot:description": "A security group for your workload to access the Aurora Serverless v2 cluster webcluster"
Type: "AWS::EC2::SecurityGroup"
Properties:
GroupDescription: !Sub "The Security Group for ${Name} to access Aurora Serverless v2 cluster webcluster."
VpcId:
Fn::ImportValue: !Sub "${App}-${Env}-VpcId"
Tags:
- Key: Name
Value: !Sub "copilot-${App}-${Env}-${Name}-Aurora"
webclusterDBClusterSecurityGroup:
Metadata:
"aws:copilot:description": "A security group for your Aurora Serverless v2 cluster webcluster"
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: The Security Group for the Aurora Serverless v2 cluster.
SecurityGroupIngress:
- ToPort: 5432
FromPort: 5432
IpProtocol: tcp
Description: !Sub "From the Aurora Security Group of the workload ${Name}."
SourceSecurityGroupId: !Ref webclusterSecurityGroup
VpcId:
Fn::ImportValue: !Sub "${App}-${Env}-VpcId"
webclusterAuroraSecret:
Metadata:
"aws:copilot:description": "A Secrets Manager secret to store your DB credentials"
Type: AWS::SecretsManager::Secret
Properties:
Description: !Sub Aurora main user secret for ${AWS::StackName}
GenerateSecretString:
SecretStringTemplate: '{"username": "postgres"}'
GenerateStringKey: "password"
ExcludePunctuation: true
IncludeSpace: false
PasswordLength: 16
webclusterDBClusterParameterGroup:
Metadata:
"aws:copilot:description": "A DB parameter group for engine configuration values"
Type: "AWS::RDS::DBClusterParameterGroup"
Properties:
Description: !Ref "AWS::StackName"
Family: "aurora-postgresql14"
Parameters:
client_encoding: "UTF8"
webclusterDBCluster:
Metadata:
"aws:copilot:description": "The webcluster Aurora Serverless v2 database cluster"
Type: "AWS::RDS::DBCluster"
Properties:
MasterUsername:
!Join [
"",
[
"{{resolve:secretsmanager:",
!Ref webclusterAuroraSecret,
":SecretString:username}}",
],
]
MasterUserPassword:
!Join [
"",
[
"{{resolve:secretsmanager:",
!Ref webclusterAuroraSecret,
":SecretString:password}}",
],
]
DatabaseName: !Ref webclusterDBName
Engine: "aurora-postgresql"
EngineVersion: "14.4"
DBClusterParameterGroupName: !Ref webclusterDBClusterParameterGroup
DBSubnetGroupName: !Ref webclusterDBSubnetGroup
Port: 5432
VpcSecurityGroupIds:
- !Ref webclusterDBClusterSecurityGroup
ServerlessV2ScalingConfiguration:
# Replace "All" below with "!Ref Env" to set different autoscaling limits per environment.
MinCapacity:
!FindInMap [webclusterEnvScalingConfigurationMap, All, DBMinCapacity]
MaxCapacity:
!FindInMap [webclusterEnvScalingConfigurationMap, All, DBMaxCapacity]
webclusterDBWriterInstance:
Metadata:
"aws:copilot:description": "The webcluster Aurora Serverless v2 writer instance"
Type: "AWS::RDS::DBInstance"
Properties:
DBClusterIdentifier: !Ref webclusterDBCluster
DBInstanceClass: db.serverless
Engine: "aurora-postgresql"
PromotionTier: 1
AvailabilityZone: !Select
- 0
- !GetAZs
Ref: AWS::Region
webclusterSecretAuroraClusterAttachment:
Type: AWS::SecretsManager::SecretTargetAttachment
Properties:
SecretId: !Ref webclusterAuroraSecret
TargetId: !Ref webclusterDBCluster
TargetType: AWS::RDS::DBCluster
Outputs:
webclusterSecret: # injected as WEBCLUSTER_SECRET environment variable by Copilot.
Description: "The JSON secret that holds the database username and password. Fields are 'host', 'port', 'dbname', 'username', 'password', 'dbClusterIdentifier' and 'engine'"
Value: !Ref webclusterAuroraSecret
webclusterSecurityGroup:
Description: "The security group to attach to the workload."
Value: !Ref webclusterSecurityGroup
+41
View File
@@ -0,0 +1,41 @@
# The manifest for the "web" service.
# Read the full specification for the "Load Balanced Web Service" type at:
# https://aws.github.io/copilot-cli/docs/manifest/lb-web-service/
name: web
type: Load Balanced Web Service
http:
path: "/"
image:
build:
dockerfile: docker/Dockerfile.website
context: ./
port: 3000
cpu: 256
memory: 512
platform: linux/amd64
count: 1
exec: true
network:
connect: true
environments:
staging:
variables:
NEXTAUTH_URL: https://web.staging.open-assistant.surfacedata.org
secrets:
DATABASE_URL: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/DATABASE_URL
DISCORD_CLIENT_ID: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/DISCORD_CLIENT_ID
DISCORD_CLIENT_SECRET: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/DISCORD_CLIENT_SECRET
EMAIL_SERVER_HOST: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/EMAIL_SERVER_HOST
EMAIL_SERVER_PASSWORD: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/EMAIL_SERVER_PASSWORD
EMAIL_SERVER_PORT: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/EMAIL_SERVER_PORT
EMAIL_SERVER_USER: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/EMAIL_SERVER_USER
EMAIL_FROM: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/EMAIL_FROM
FASTAPI_KEY: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/FASTAPI_KEY
FASTAPI_URL: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/FASTAPI_URL
NEXTAUTH_SECRET: /copilot/${COPILOT_APPLICATION_NAME}/${COPILOT_ENVIRONMENT_NAME}/secrets/NEXTAUTH_SECRET
+100
View File
@@ -0,0 +1,100 @@
version: "3.7"
services:
# Use `docker compose up backend-dev` to start a database and work and the backend.
backend-dev:
image: tianon/true
depends_on: [db, adminer]
# Use `docker compose up frontend-dev` to start all services needed to work on the frontend.
frontend-dev:
image: tianon/true
depends_on: [db, webdb, adminer, maildev, backend]
# This DB is for the FastAPI Backend.
db:
image: postgres
restart: always
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: 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
POSTGRES_DB: oasst_web
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
image: oasst-backend
environment:
- POSTGRES_HOST=db
- DEBUG_SKIP_API_KEY_CHECK=True
- MAX_WORKERS=1
depends_on:
db:
condition: service_healthy
ports:
- "8080:8080"
# The oassist web service.
web:
build:
dockerfile: docker/Dockerfile.website
image: oasst-web
environment:
- DATABASE_URL=postgres://postgres:postgres@webdb/oasst_web
- 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
+10
View File
@@ -0,0 +1,10 @@
# This docker file will update the web database with schema changes. Later it
# will be updated to do prisma migrations.
FROM --platform=linux/amd64 node:16.18.0 AS runner
WORKDIR /app
COPY ./website/ .
CMD ["npx", "prisma", "db", "push"]
+6 -15
View File
@@ -1,7 +1,8 @@
# Note: node:16.18.0 works most reliably when using different platforms (namely
# Mac M1) and avoids recent Prisma docker bugs that lead to segfaults.
# 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
FROM node:16.18.0 AS deps
WORKDIR /app
# Install dependencies based on the preferred package manager
@@ -12,27 +13,19 @@ RUN \
fi
# Rebuild the source code only when needed
FROM node:16.19 AS builder
FROM node:16.18.0 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
FROM node:16.18.0 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
@@ -48,8 +41,6 @@ COPY --chown=nextjs:nodejs ./website/prisma/schema.prisma ./
# 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
+1 -1
View File
@@ -1,6 +1,6 @@
# Backend Development Setup
Run `docker compose up` to start a database. The default settings are already configured to connect to the database at `localhost:5432`.
In root directory, run `docker compose up backend-dev` to start a database. The default settings are already configured to connect to the database at `localhost:5432`.
Make sure you have all requirements installed. You can do this by running `pip install -r requirements.txt` inside the `backend` folder and `pip install -e .` inside the `oasst-shared` folder.
Then, run the backend using the `run-local.sh` script. This will start the backend server at `http://localhost:8080`.
@@ -1,23 +0,0 @@
version: "3.7"
services:
db:
image: postgres
restart: always
ports:
- 5432:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 2s
timeout: 2s
retries: 10
adminer:
image: adminer
restart: always
ports:
- 8089:8080
-14
View File
@@ -1,14 +0,0 @@
# 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.
-56
View File
@@ -1,56 +0,0 @@
version: "3.7"
services:
# This DB is for the FastAPI Backend.
db:
extends:
file: ../frontend-development/docker-compose.yaml
service: db
# This DB is for Web Authentication and data caching.
webdb:
extends:
file: ../frontend-development/docker-compose.yaml
service: webdb
# This lets you manually inspect the web and backend databases.
adminer:
extends:
file: ../frontend-development/docker-compose.yaml
service: adminer
# 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:
extends:
file: ../frontend-development/docker-compose.yaml
service: maildev
# The oassist backend service.
backend:
extends:
file: ../frontend-development/docker-compose.yaml
service: backend
# 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
+1 -1
View File
@@ -1,5 +1,5 @@
# Frontend Development Setup
Run `docker compose up --build` to start a database and the backend server.
In root directory run `docker compose up frontend-dev --build` to start a database and the backend server.
Then, point your frontend at `http://localhost:8080` to start developing. During development, any API key will be accepted.
@@ -1,58 +0,0 @@
version: "3.7"
services:
# This DB is for the FastAPI Backend.
db:
extends:
file: ../backend-development/docker-compose.yaml
service: db
# This DB is for Web Authentication and data caching.
webdb:
image: postgres
restart: always
ports:
- 5433:5432
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: ocgpt_website
healthcheck:
test: ["CMD", "pg_isready", "-U", "postgres"]
interval: 2s
timeout: 2s
retries: 10
# This lets you manually inspect the web and backend databases.
adminer:
extends:
file: ../backend-development/docker-compose.yaml
service: adminer
backend:
build:
dockerfile: docker/Dockerfile.backend
context: ../..
image: oasst-backend
environment:
- POSTGRES_HOST=db
- DEBUG_SKIP_API_KEY_CHECK=True
- MAX_WORKERS=1
depends_on:
db:
condition: service_healthy
ports:
- "8080: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"
@@ -2,7 +2,7 @@
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
# switch to bot directory
pushd "$parent_path/../../bot"
pushd "$parent_path/../../discord-bot"
python3 __main__.py
+14
View File
@@ -113,6 +113,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:
+678
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -33,6 +33,7 @@
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.7.1",
"sharp": "^0.31.3",
"swr": "^2.0.0",
"tailwindcss": "^3.2.4",
"use-debounce": "^9.0.2"
@@ -1,66 +0,0 @@
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"type" TEXT NOT NULL,
"provider" TEXT NOT NULL,
"providerAccountId" TEXT NOT NULL,
"refresh_token" TEXT,
"access_token" TEXT,
"expires_at" INTEGER,
"token_type" TEXT,
"scope" TEXT,
"id_token" TEXT,
"session_state" TEXT,
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"sessionToken" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"name" TEXT,
"email" TEXT,
"emailVerified" TIMESTAMP(3),
"image" TEXT,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VerificationToken" (
"identifier" TEXT NOT NULL,
"token" TEXT NOT NULL,
"expires" TIMESTAMP(3) NOT NULL
);
-- CreateIndex
CREATE UNIQUE INDEX "Account_provider_providerAccountId_key" ON "Account"("provider", "providerAccountId");
-- CreateIndex
CREATE UNIQUE INDEX "Session_sessionToken_key" ON "Session"("sessionToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_token_key" ON "VerificationToken"("token");
-- CreateIndex
CREATE UNIQUE INDEX "VerificationToken_identifier_token_key" ON "VerificationToken"("identifier", "token");
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
@@ -1,2 +0,0 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "labelerId" INTEGER;
@@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
+1
View File
@@ -5,6 +5,7 @@ datasource db {
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl"]
}
model Account {
+2 -8
View File
@@ -1,14 +1,8 @@
import Link from "next/link";
import Image from "next/image";
export function AuthLayout({ children }) {
return (
<main className="flex items-center justify-center min-h-full overflow-hidden pt-16 sm:py-28 subpixel-antialiased">
<main className="flex items-center justify-center sm:py-4 subpixel-antialiased">
<div className="flex items-center w-full max-w-2xl flex-col px-4 sm:px-6">
<Link href="/" aria-label="Home" className="flex items-center text-3xl font-bold text-black">
<Image src="/images/logos/logo.svg" width="100" height="100" alt="Open Assistant Logo" /> Open Assistant
</Link>
<div className="flex-auto items-center justify-center w-full py-10 px-4 sm:mx-0 sm:flex-none sm:rounded-2xl sm:p-20">
<div className="flex-auto items-center justify-center w-full py-10 px-4 sm:mx-0 sm:flex-none sm:rounded-2xl sm:p-4">
{children}
</div>
</div>
+2 -2
View File
@@ -55,9 +55,9 @@ function AccountButton() {
export function Header() {
return (
<header>
<header className="bg-white">
<nav>
<Container className="relative bg-white z-10 flex justify-between py-8">
<Container className="relative z-10 flex justify-between py-8">
<div className="relative z-10 flex items-center gap-16">
<Link href="/" aria-label="Home" className="flex items-center">
<Image src="/images/logos/logo.svg" className="mx-auto object-fill" width="50" height="50" alt="logo" />
+20
View File
@@ -0,0 +1,20 @@
// https://nextjs.org/docs/basic-features/layouts
import type { NextPage } from "next";
import { Footer } from "./Footer";
import { Header } from "./Header";
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: React.ReactElement) => React.ReactNode;
};
export const getDefaultLayout = (page: React.ReactElement) => (
<div className="grid grid-rows-[min-content_1fr_min-content] h-full justify-items-stretch">
<Header />
{page}
<Footer />
</div>
);
export const noLayout = (page: React.ReactElement) => page;
+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>
);
};
@@ -11,7 +11,7 @@ export const TaskSelection = () => {
alt="Summarize Stories"
img="/images/logos/logo.svg"
title="Summarize stories"
link="/summarize/story"
link="/create/summarize_story"
/>
<TaskOption alt="Reply as User" img="/images/logos/logo.svg" title="Reply as User" link="/create/user_reply" />
<TaskOption
@@ -22,7 +22,18 @@ export const TaskSelection = () => {
/>
</TaskOptions>
<TaskOptions key="evaluate" title="Evaluate">
<TaskOption alt="Rate Prompts" img="/images/logos/logo.svg" title="Rate Prompts" link="/grading/grade-output" />
<TaskOption
alt="Rate Prompts"
img="/images/logos/logo.svg"
title="Rate Prompts"
link="/evaluate/rate_summary"
/>
<TaskOption
alt="Rank Initial Prompts"
img="/images/logos/logo.svg"
title="Rank Initial Prompts"
link="/evaluate/rank_initial_prompts"
/>
</TaskOptions>
</Flex>
);
+2 -2
View File
@@ -2,11 +2,11 @@ import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html className="h-full bg-gray-50 antialiased" lang="en">
<Html className="h-full antialiased" lang="en">
<Head>
<link rel="shortcut icon" type="image/png" href="/images/logos/favicon.png" />
</Head>
<body className="flex h-full flex-col">
<body className="flex h-full flex-col bg-gray-50">
<Main />
<NextScript />
</body>
+2 -2
View File
@@ -43,12 +43,12 @@ const AssistantReply = () => {
* 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 bg-slate-100 text-gray-800">Loading...</div>;
}
const task = tasks[0].task;
return (
<div className="p-6 h-full mx-auto bg-slate-100 text-gray-800">
<div className="p-6 bg-slate-100 text-gray-800">
<TwoColumns>
<>
<h5 className="text-lg font-semibold">Reply as the assistant</h5>
@@ -0,0 +1,98 @@
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 fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import { TwoColumns } from "src/components/TwoColumns";
import { Button } from "src/components/Button";
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 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>
<main className="p-6 h-full mx-auto bg-slate-100 text-gray-800">
<TwoColumns>
<>
<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>
</>
<Textarea name="summary" placeholder="Summary" ref={inputRef} />
</TwoColumns>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="grid grid-cols-[min-content_auto] gap-x-2 text-gray-700">
<b>Prompt</b>
<span>{tasks[0].id}</span>
<b>Output</b>
<span>Submit your answer</span>
</div>
<div className="flex justify-center ml-auto">
<Button className="mr-2 bg-indigo-100 text-indigo-700 hover:bg-indigo-200">Skip</Button>
<Button
onClick={() => submitResponse(tasks[0])}
className="bg-indigo-600 text-white shadow-sm hover:bg-indigo-700"
>
Submit
</Button>
</div>
</section>
</main>
</>
);
};
export default SummarizeStory;
+2 -2
View File
@@ -43,12 +43,12 @@ const UserReply = () => {
* 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 bg-slate-100 text-gray-800">Loading...</div>;
}
const task = tasks[0].task;
return (
<div className="p-6 h-full mx-auto bg-slate-100 text-gray-800">
<div className="p-6 bg-slate-100 text-gray-800">
<TwoColumns>
<>
<h5 className="text-lg font-semibold">Reply as a user</h5>
@@ -0,0 +1,147 @@
import { ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/20/solid";
import clsx from "clsx";
import Head from "next/head";
import { useState } from "react";
import useSWRImmutable from "swr/immutable";
import useSWRMutation from "swr/mutation";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import { Button } from "src/components/Button";
const RankInitialPrompts = () => {
const [tasks, setTasks] = useState([]);
/**
* This array will contain the ranked indices of the prompts
* The best prompt will have index 0, and the worst is the last.
*/
const [ranking, setRanking] = useState<number[]>([]);
const { isLoading } = useSWRImmutable("/api/new_task/rank_initial_prompts", fetcher, {
onSuccess: (data) => {
setTasks([data]);
const indices = Array.from({ length: data.task.prompts.length }).map((_, i) => i);
setRanking(indices);
},
});
const { trigger, isMutating } = useSWRMutation("/api/update_task", poster, {
onSuccess: async (data) => {
const newTask = await data.json();
setTasks((oldTasks) => [...oldTasks, newTask]);
},
});
const submitResponse = (task) => {
trigger({
id: task.id,
update_type: "post_ranking",
content: {
ranking,
},
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
}
const prompts = tasks[0].task.prompts as string[];
const items = ranking.map((i) => ({
text: prompts[i],
originalIndex: i,
}));
return (
<>
<Head>
<title>Rank Initial Prompts</title>
<meta name="description" content="Rank initial prompts." />
</Head>
<main className="p-6 bg-slate-100 text-gray-800">
<div className="rounded-lg shadow-lg block bg-white p-6 mb-8">
<h5 className="text-lg font-semibold mb-4">Instructions</h5>
<p className="text-lg py-1">
Given the following prompts, sort them from best to worst, best being first, worst being last.
</p>
<ul className="flex flex-col gap-4">
{items.map(({ text, originalIndex }, i) => (
<SortableItem
key={`${originalIndex}_${i}`}
canIncrement={i > 0}
onIncrement={() => {
const newRanking = ranking.slice();
const newIdx = i - 1;
[newRanking[i], newRanking[newIdx]] = [newRanking[newIdx], newRanking[i]];
setRanking(newRanking);
}}
canDecrement={i < items.length - 1}
onDecrement={() => {
const newRanking = ranking.slice();
const newIdx = i + 1;
[newRanking[i], newRanking[newIdx]] = [newRanking[newIdx], newRanking[i]];
setRanking(newRanking);
}}
>
{text}
</SortableItem>
))}
</ul>
</div>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="grid grid-cols-[min-content_auto] gap-x-2 text-gray-700">
<b>Prompt</b>
<span>{tasks[0].id}</span>
<b>Output</b>
<span>Submit your answer</span>
</div>
<div className="flex justify-center ml-auto">
<Button className="mr-2 bg-indigo-100 text-indigo-700 hover:bg-indigo-200">Skip</Button>
<Button
onClick={() => submitResponse(tasks[0])}
disabled={ranking.length === 0}
className="bg-indigo-600 text-white shadow-sm hover:bg-indigo-700"
>
Submit
</Button>
</div>
</section>
</main>
</>
);
};
export default RankInitialPrompts;
const SortableItem = ({ canIncrement, canDecrement, onIncrement, onDecrement, children, ...props }) => {
return (
<li className="grid grid-cols-[min-content_1fr] items-center rounded-lg shadow-md gap-x-2 p-2">
<ArrowButton active={canIncrement} onClick={onIncrement}>
<ArrowUpIcon width={28} />
</ArrowButton>
<span style={{ gridRow: "span 2" }}>{children}</span>
<ArrowButton active={canDecrement} onClick={onDecrement}>
<ArrowDownIcon width={28} />
</ArrowButton>
</li>
);
};
const ArrowButton = ({ children, active, onClick }) => {
return (
<Button
className={clsx("justify-center", active ? "hover:bg-indigo-200" : "opacity-10")}
onClick={onClick}
disabled={!active}
>
{children}
</Button>
);
};
+187
View File
@@ -0,0 +1,187 @@
import { Textarea } from "@chakra-ui/react";
import { QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
import Head from "next/head";
import { useState } from "react";
import useSWRImmutable from "swr/immutable";
import useSWRMutation from "swr/mutation";
import RatingRadioGroup from "src/components/RatingRadioGroup";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import { TwoColumns } from "src/components/TwoColumns";
import { Button } from "src/components/Button";
const RateSummary = () => {
// Use an array of tasks that record the sequence of steps until a task is
// deemed complete.
const [tasks, setTasks] = useState([]);
const [rating, setRating] = useState(0);
// 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/rate_summary", 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 = (t) => {
trigger({
id: t.id,
content: {
rating: rating,
},
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
}
return (
<>
<Head>
<title>Rate A Summary</title>
<meta name="description" content="Rate a proposed story summary." />
</Head>
<main className="p-6 bg-slate-100 text-gray-800">
<TwoColumns>
<>
<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>
</>
<section className="grid grid-row-[auto] gap-3">
<h5 className="text-lg font-semibold">Output</h5>
<p className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{tasks[0].task.summary}</p>
<h3 className="text-lg text-center font-medium leading-6 text-gray-900">Rating</h3>
<p className="text-center text-sm text-gray-500">
({tasks[0].task.scale.min} = worst, {tasks[0].task.scale.max} = best)
</p>
<div className="m-auto">
<RatingRadioGroup min={tasks[0].task.scale.min} max={tasks[0].task.scale.max} onChange={setRating} />
</div>
<ul>
{ANNOTATION_FLAGS.map((option, i) => (
<AnnotationCheckboxLi option={option} key={i} />
))}
</ul>
<Textarea name="notes" placeholder="Optional notes" />
</section>
</TwoColumns>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<div className="grid grid-cols-[min-content_auto] gap-x-2 text-gray-700">
<b>Prompt</b>
<span>{tasks[0].id}</span>
<b>Output</b>
<span>Submit your answer</span>
</div>
<div className="flex justify-center ml-auto">
<Button className="mr-2 bg-indigo-100 text-indigo-700 hover:bg-indigo-200">Skip</Button>
<Button
onClick={() => submitResponse(tasks[0])}
className="bg-indigo-600 text-white shadow-sm hover:bg-indigo-700"
>
Submit
</Button>
</div>
</section>
</main>
</>
);
};
export default RateSummary;
function AnnotationCheckboxLi(props: { option: annotationBool }): JSX.Element {
let AdditionalExplanation = null;
if (props.option.additionalExplanation) {
AdditionalExplanation = (
<a href="#" className="group flex items-center space-x-2.5 text-sm ">
<QuestionMarkCircleIcon className="h-5 w-5 ml-3 text-gray-400 group-hover:text-gray-500" aria-hidden="true" />
</a>
);
}
return (
<li className="form-check flex mb-1">
<input
className="form-check-input appearance-none h-4 w-4 border border-gray-300 rounded-sm bg-white checked:bg-blue-600 checked:border-blue-600 focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer"
type="checkbox"
value=""
id={props.option.attributeName}
/>
<label className="flex ml-1 form-check-label hover:cursor-pointer" htmlFor={props.option.attributeName}>
<span className="text-gray-800 hover:text-blue-700">{props.option.labelText}</span>
{AdditionalExplanation}
</label>
</li>
);
}
interface annotationBool {
attributeName: string;
labelText: string;
additionalExplanation?: string;
}
const ANNOTATION_FLAGS: annotationBool[] = [
// For the time being this list is configured on the FE.
// In the future it may be provided by the API.
{
attributeName: "fails_task",
labelText: "Fails to follow the correct instruction / task",
additionalExplanation: "__TODO__",
},
{
attributeName: "not_customer_assistant_appropriate",
labelText: "Inappropriate for customer assistant",
additionalExplanation: "__TODO__",
},
{
attributeName: "contains_sexual_content",
labelText: "Contains sexual content",
},
{
attributeName: "contains_violent_content",
labelText: "Contains violent content",
},
{
attributeName: "encourages_violence",
labelText: "Encourages or fails to discourage violence/abuse/terrorism/self-harm",
},
{
attributeName: "denigrates_a_protected_class",
labelText: "Denigrates a protected class",
},
{
attributeName: "gives_harmful_advice",
labelText: "Fails to follow the correct instruction / task",
additionalExplanation:
"The advice given in the output is harmful or counter-productive. This may be in addition to, but is distinct from the question about encouraging violence/abuse/terrorism/self-harm.",
},
{
attributeName: "expresses_moral_judgement",
labelText: "Expresses moral judgement",
},
];
-222
View File
@@ -1,222 +0,0 @@
import { HStack, Textarea } from "@chakra-ui/react";
import { QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
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 RatingRadioGroup from "src/components/RatingRadioGroup";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
const GradeOutput = () => {
// Use an array of tasks that record the sequence of steps until a task is
// deemed complete.
const [tasks, setTasks] = useState([]);
const [rating, setRating] = useState(0);
// 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/rate_summary", 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 = (t) => {
trigger({
id: t.id,
content: {
rating: rating,
},
});
};
/**
* 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"></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 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>
</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>
);
};
export default GradeOutput;
function AnnotationCheckboxLi(props: { option: annotationBool }): JSX.Element {
let AdditionalExplanation = null;
if (props.option.additionalExplanation) {
AdditionalExplanation = (
<a href="#" className="group flex items-center space-x-2.5 text-sm ">
<QuestionMarkCircleIcon className="h-5 w-5 ml-3 text-gray-400 group-hover:text-gray-500" aria-hidden="true" />
</a>
);
}
return (
<>
<li className="form-check flex mb-1">
<input
className="form-check-input appearance-none h-4 w-4 border border-gray-300 rounded-sm bg-white checked:bg-blue-600 checked:border-blue-600 focus:outline-none transition duration-200 mt-1 align-top bg-no-repeat bg-center bg-contain float-left mr-2 cursor-pointer"
type="checkbox"
value=""
id={props.option.attributeName}
/>
<label className="flex ml-1 form-check-label hover:cursor-pointer" htmlFor={props.option.attributeName}>
<span className="text-gray-800 hover:text-blue-700">{props.option.labelText}</span>
{AdditionalExplanation}
</label>
</li>
</>
);
}
interface annotationBool {
attributeName: string;
labelText: string;
additionalExplanation?: string;
}
const ANNOTATION_FLAGS: annotationBool[] = [
// For the time being this list is configured on the FE.
// In the future it may be provided by the API.
{
attributeName: "fails_task",
labelText: "Fails to follow the correct instruction / task",
additionalExplanation: "__TODO__",
},
{
attributeName: "not_customer_assistant_appropriate",
labelText: "Inappropriate for customer assistant",
additionalExplanation: "__TODO__",
},
{
attributeName: "contains_sexual_content",
labelText: "Contains sexual content",
},
{
attributeName: "contains_violent_content",
labelText: "Contains violent content",
},
{
attributeName: "encourages_violence",
labelText: "Encourages or fails to discourage violence/abuse/terrorism/self-harm",
},
{
attributeName: "denigrates_a_protected_class",
labelText: "Denigrates a protected class",
},
{
attributeName: "gives_harmful_advice",
labelText: "Fails to follow the correct instruction / task",
additionalExplanation:
"The advice given in the output is harmful or counter-productive. This may be in addition to, but is distinct from the question about encouraging violence/abuse/terrorism/self-harm.",
},
{
attributeName: "expresses_moral_judgement",
labelText: "Expresses moral judgement",
},
];
@@ -6,7 +6,7 @@ import { HiBarsArrowUp, HiBarsArrowDown } from "react-icons/hi2";
const LeaderBoard = () => {
const PlaceHolderProps = { username: "test_user", score: 10 };
return (
<div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">
<div className="p-6 bg-slate-100 text-gray-800">
<div className="flex flex-col">
<div className="rounded-lg shadow-lg h-full block bg-white">
<div className="p-8">
-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;