Merge branch 'main' of https://github.com/LAION-AI/Open-Assistant into LAION-AI-main

This commit is contained in:
Alex Ott
2022-12-29 14:57:51 -08:00
59 changed files with 29849 additions and 2877 deletions
+4
View File
@@ -4,7 +4,11 @@ on:
push:
branches:
- main
paths:
- website/**
pull_request:
paths:
- website/**
workflow_call:
jobs:
+2 -2
View File
@@ -14,7 +14,7 @@ repos:
# 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/*
exclude: "^copilot/web/addons/.*$"
- id: check-json
- id: check-case-conflict
- id: detect-private-key
@@ -60,4 +60,4 @@ repos:
types_or: [javascript, jsx, ts, tsx]
language: system
pass_filenames: false
entry: bash -c 'cd website && npm install && npm run lint'
entry: bash -c 'cd website && npm ci && npm run lint'
+16 -17
View File
@@ -4,6 +4,21 @@ Open Assistant is a project meant to give everyone access to a great chat based
We believe that by doing this we will create a revolution in innovation in language. In the same way that stable-diffusion helped the world make art and images in new ways we hope Open Assistant can help improve the world by improving language itself.
## Do you want to try it out?
If you are interested in taking a look at the current state of 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 the root directory of the repository:
```sh
docker compose up --build
```
Then, navigate to `http://localhost:3000` (It may take some time to boot up) and interact with the website.
**Note:** When logging in via email, navigate to `http://localhost:1080` to get the magic email login link.
## The Plan
We want to get to an initial MVP as fast as possible, by following the 3-steps outlined in the InstructGPT paper.
@@ -82,7 +97,7 @@ addressed now, or filing an issue to handle it later.
Work is organized in the [project board](https://github.com/orgs/LAION-AI/projects/3).
**Anything that is in the `Todo` column and not assigned, is up for grabs. Meaning we'd be happy if anyone did those tasks.**
**Anything that is in the `Todo` column and not assigned, is up for grabs. Meaning we'd be happy for anyone to do these tasks.**
If you want to work on something, assign yourself to it or write a comment that you want to work on it and what you plan to do.
@@ -95,22 +110,6 @@ 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.
+4
View File
@@ -45,12 +45,14 @@
name: oasst-backend
image: ghcr.io/laion-ai/open-assistant/oasst-backend
state: started
recreate: true
pull: true
restart_policy: always
network_mode: oasst
env:
POSTGRES_HOST: oasst-postgres
DEBUG_ALLOW_ANY_API_KEY: "true"
DEBUG_USE_SEED_DATA: "true"
MAX_WORKERS: "1"
ports:
- 8080:8080
@@ -60,6 +62,7 @@
name: oasst-web
image: ghcr.io/laion-ai/open-assistant/oasst-web
state: started
recreate: true
pull: true
restart_policy: always
network_mode: oasst
@@ -72,6 +75,7 @@
EMAIL_SERVER_PORT: "25"
EMAIL_FROM: info@example.com
NEXTAUTH_URL: http://web.dev.open-assistant.io/
DEBUG_LOGIN: "true"
ports:
- 3000:3000
command: bash wait-for-postgres.sh node server.js
@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""Added lang column for ISO-639-1 codes
Revision ID: ef0b52902560
Revises: 3358eb6834e6
Create Date: 2022-12-28 18:24:21.393973
"""
import sqlalchemy as sa
import sqlmodel
from alembic import op
# revision identifiers, used by Alembic.
revision = "ef0b52902560"
down_revision = "3358eb6834e6"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"post", sa.Column("lang", sqlmodel.sql.sqltypes.AutoString(length=200), nullable=False, default="en-US")
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("post", "lang")
# ### end Alembic commands ###
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
"""add collective flag to task
Revision ID: 464ec4667aae
Revises: d24b37426857
Create Date: 2022-12-29 21:03:06.841962
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "464ec4667aae"
down_revision = "d24b37426857"
branch_labels = None
depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"work_package", sa.Column("collective", sa.Boolean(), server_default=sa.text("false"), nullable=False)
)
# ### end Alembic commands ###
def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("work_package", "collective")
# ### end Alembic commands ###
+121
View File
@@ -1,14 +1,21 @@
# -*- coding: utf-8 -*-
from http import HTTPStatus
from pathlib import Path
from typing import Optional
import alembic.command
import alembic.config
import fastapi
import pydantic
from loguru import logger
from oasst_backend.api.deps import get_dummy_api_client
from oasst_backend.api.v1.api import api_router
from oasst_backend.config import settings
from oasst_backend.database import engine
from oasst_backend.exceptions import OasstError, OasstErrorCode
from oasst_backend.prompt_repository import PromptRepository
from oasst_shared.schemas import protocol as protocol_schema
from sqlmodel import Session
from starlette.middleware.cors import CORSMiddleware
app = fastapi.FastAPI(title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json")
@@ -56,4 +63,118 @@ if settings.UPDATE_ALEMBIC:
logger.exception("Alembic upgrade failed on startup")
if settings.DEBUG_USE_SEED_DATA:
@app.on_event("startup")
def seed_data():
class DummyPost(pydantic.BaseModel):
task_post_id: str
user_post_id: str
parent_post_id: Optional[str]
text: str
role: str
try:
logger.info("Seed data check began")
with Session(engine) as db:
api_client = get_dummy_api_client(db)
dummy_user = protocol_schema.User(id="__dummy_user__", display_name="Dummy User", auth_method="local")
pr = PromptRepository(db=db, api_client=api_client, user=dummy_user)
dummy_posts = [
DummyPost(
task_post_id="de111fa8",
user_post_id="6f1d0711",
parent_post_id=None,
text="Hi!",
role="user",
),
DummyPost(
task_post_id="74c381d4",
user_post_id="4a24530b",
parent_post_id="6f1d0711",
text="Hello! How can I help you?",
role="assistant",
),
DummyPost(
task_post_id="3d5dc440",
user_post_id="a8c01c04",
parent_post_id="4a24530b",
text="Do you have a recipe for potato soup?",
role="user",
),
DummyPost(
task_post_id="643716c1",
user_post_id="f43a93b7",
parent_post_id="4a24530b",
text="Who were the 8 presidents before George Washington?",
role="user",
),
DummyPost(
task_post_id="2e4e1e6",
user_post_id="c886920",
parent_post_id="6f1d0711",
text="Hey buddy! How can I serve you?",
role="assistant",
),
DummyPost(
task_post_id="970c437d",
user_post_id="cec432cf",
parent_post_id=None,
text="euirdteunvglfe23908230892309832098 AAAAAAAA",
role="user",
),
DummyPost(
task_post_id="6066118e",
user_post_id="4f85f637",
parent_post_id="cec432cf",
text="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?",
role="assistant",
),
DummyPost(
task_post_id="ba87780d",
user_post_id="0e276b98",
parent_post_id="cec432cf",
text="I'm unsure how to interpret this. Is it a riddle?",
role="assistant",
),
]
for p in dummy_posts:
wp = pr.fetch_workpackage_by_postid(p.task_post_id)
if wp and not wp.ack:
logger.warning("Deleting unacknowledged seed data work package")
db.delete(wp)
wp = None
if not wp:
if p.parent_post_id is None:
wp = pr.store_task(
protocol_schema.InitialPromptTask(hint=""), thread_id=None, parent_post_id=None
)
else:
print("p.parent_post_id", p.parent_post_id)
parent_post = pr.fetch_post_by_frontend_post_id(p.parent_post_id, fail_if_missing=True)
wp = pr.store_task(
protocol_schema.AssistantReplyTask(
conversation=protocol_schema.Conversation(
messages=[protocol_schema.ConversationMessage(text="dummy", is_assistant=False)]
)
),
thread_id=parent_post.thread_id,
parent_post_id=parent_post.id,
)
pr.bind_frontend_post_id(wp.id, p.task_post_id)
post = pr.store_text_reply(p.text, p.task_post_id, p.user_post_id)
logger.info(
f"Inserted: post_id: {post.id}, payload: {post.payload.payload}, parent_post_id: {post.parent_id}"
)
else:
logger.debug(f"seed data work_package found: {wp.id}")
logger.info("Seed data check completed")
except Exception:
logger.exception("Seed data insertion failed")
app.include_router(api_router, prefix=settings.API_V1_STR)
+14 -10
View File
@@ -33,6 +33,19 @@ async def get_api_key(
return api_key_header
def get_dummy_api_client(db: Session) -> ApiClient:
# make sure that a dummy api key exits in db (foreign key references)
ANY_API_KEY_ID = UUID("00000000-1111-2222-3333-444444444444")
api_client: ApiClient = db.query(ApiClient).filter(ApiClient.id == ANY_API_KEY_ID).first()
if api_client is None:
token = token_hex(32)
logger.info(f"ANY_API_KEY missing, inserting api_key: {token}")
api_client = ApiClient(id=ANY_API_KEY_ID, api_key=token, description="ANY_API_KEY, random token")
db.add(api_client)
db.commit()
return api_client
def api_auth(
api_key: APIKey,
db: Session,
@@ -40,16 +53,7 @@ def api_auth(
if api_key or settings.DEBUG_SKIP_API_KEY_CHECK:
if settings.DEBUG_SKIP_API_KEY_CHECK or settings.DEBUG_ALLOW_ANY_API_KEY:
# make sure that a dummy api key exits in db (foreign key references)
ANY_API_KEY_ID = UUID("00000000-1111-2222-3333-444444444444")
api_client: ApiClient = db.query(ApiClient).filter(ApiClient.id == ANY_API_KEY_ID).first()
if api_client is None:
token = token_hex(32)
logger.info(f"ANY_API_KEY missing, inserting api_key: {token}")
api_client = ApiClient(id=ANY_API_KEY_ID, api_key=token, description="ANY_API_KEY, random token")
db.add(api_client)
db.commit()
return api_client
return get_dummy_api_client(db)
api_client = db.query(ApiClient).filter(ApiClient.api_key == api_key).first()
if api_client is not None and api_client.enabled:
+13 -1
View File
@@ -139,7 +139,7 @@ def request_task(
try:
pr = PromptRepository(db, api_client, request.user)
task, thread_id, parent_post_id = generate_task(request, pr)
pr.store_task(task, thread_id, parent_post_id)
pr.store_task(task, thread_id, parent_post_id, request.collective)
except OasstError:
raise
@@ -252,3 +252,15 @@ def post_interaction(
except Exception:
logger.exception("Interaction request failed.")
raise OasstError("Interaction request failed.", OasstErrorCode.TASK_INTERACTION_REQUEST_FAILED)
@router.post("/close")
def close_collective_task(
close_task_request: protocol_schema.TaskClose,
db: Session = Depends(deps.get_db),
api_key: APIKey = Depends(deps.get_api_key),
):
api_client = deps.api_auth(api_key, db)
pr = PromptRepository(db, api_client, user=None)
pr.close_task(close_task_request.post_id)
return protocol_schema.TaskDone()
+1
View File
@@ -17,6 +17,7 @@ class Settings(BaseSettings):
DEBUG_ALLOW_ANY_API_KEY: bool = False
DEBUG_SKIP_API_KEY_CHECK: bool = False
DEBUG_USE_SEED_DATA: bool = False
@validator("DATABASE_URI", pre=True)
def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any:
+2
View File
@@ -34,12 +34,14 @@ class OasstErrorCode(IntEnum):
INVALID_TASK_TYPE = 2004
USER_NOT_SPECIFIED = 2005
NO_THREADS_FOUND = 2006
NO_REPLIES_FOUND = 2007
WORK_PACKAGE_NOT_FOUND = 2100
WORK_PACKAGE_EXPIRED = 2101
WORK_PACKAGE_PAYLOAD_TYPE_MISMATCH = 2102
WORK_PACKAGE_ALREADY_UPDATED = 2103
WORK_PACKAGE_NOT_ACK = 2104
WORK_PACKAGE_ALREADY_DONE = 2105
WORK_PACKAGE_NOT_COLLECTIVE = 2106
class OasstError(Exception):
+1
View File
@@ -31,5 +31,6 @@ class Post(SQLModel, table=True):
)
payload_type: str = Field(nullable=False, max_length=200)
payload: PayloadContainer = Field(sa_column=sa.Column(payload_column_type(PayloadContainer), nullable=True))
lang: str = Field(nullable=False, max_length=200, default="en-US")
depth: int = Field(sa_column=sa.Column(sa.Integer, default=0, server_default=sa.text("0"), nullable=False))
children_count: int = Field(sa_column=sa.Column(sa.Integer, default=0, server_default=sa.text("0"), nullable=False))
@@ -32,6 +32,7 @@ class WorkPackage(SQLModel, table=True):
frontend_ref_post_id: Optional[str] = None
thread_id: Optional[UUID] = None
parent_post_id: Optional[UUID] = None
collective: bool = Field(sa_column=sa.Column(sa.Boolean, nullable=False, server_default=false()))
@property
def expired(self) -> bool:
+35 -10
View File
@@ -160,8 +160,9 @@ class PromptRepository:
payload=db_payload.PostPayload(text=text),
depth=depth,
)
wp.done = True
self.db.add(wp)
if not wp.collective:
wp.done = True
self.db.add(wp)
self.db.commit()
self.journal.log_text_reply(work_package=wp, post_id=new_post_id, role=role, length=len(text))
return user_post
@@ -186,6 +187,10 @@ class PromptRepository:
# store reaction to post
reaction_payload = db_payload.RatingReactionPayload(rating=rating.rating)
reaction = self.insert_reaction(post.id, reaction_payload)
if not work_package.collective:
work_package.done = True
self.db.add(work_package)
self.journal.log_rating(work_package, post_id=post.id, rating=rating.rating)
logger.info(f"Ranking {rating.rating} stored for work_package {work_package.id}.")
return reaction
@@ -193,8 +198,9 @@ class PromptRepository:
def store_ranking(self, ranking: protocol_schema.PostRanking) -> PostReaction:
# fetch work_package
work_package = self.fetch_workpackage_by_postid(ranking.post_id)
work_package.done = True
self.db.add(work_package)
if not work_package.collective:
work_package.done = True
self.db.add(work_package)
work_payload: db_payload.RankConversationRepliesPayload | db_payload.RankInitialPromptsPayload = (
work_package.payload.payload
@@ -250,6 +256,7 @@ class PromptRepository:
task: protocol_schema.Task,
thread_id: UUID = None,
parent_post_id: UUID = None,
collective: bool = False,
) -> WorkPackage:
payload: db_payload.TaskPayload
match type(task):
@@ -287,10 +294,7 @@ class PromptRepository:
raise OasstError(f"Invalid task type: {type(task)=}", OasstErrorCode.INVALID_TASK_TYPE)
wp = self.insert_work_package(
payload=payload,
id=task.id,
thread_id=thread_id,
parent_post_id=parent_post_id,
payload=payload, id=task.id, thread_id=thread_id, parent_post_id=parent_post_id, collective=collective
)
assert wp.id == task.id
return wp
@@ -301,6 +305,7 @@ class PromptRepository:
id: UUID = None,
thread_id: UUID = None,
parent_post_id: UUID = None,
collective: bool = False,
) -> WorkPackage:
c = PayloadContainer(payload=payload)
wp = WorkPackage(
@@ -311,6 +316,7 @@ class PromptRepository:
api_client_id=self.api_client.id,
thread_id=thread_id,
parent_post_id=parent_post_id,
collective=collective,
)
self.db.add(wp)
self.db.commit()
@@ -397,7 +403,7 @@ class PromptRepository:
distinct_threads = distinct_threads.filter(Post.role == require_role)
distinct_threads = distinct_threads.subquery()
random_thread = self.db.query(distinct_threads).order_by(func.random()).limit(1).subquery()
random_thread = self.db.query(distinct_threads).order_by(func.random()).limit(1)
thread_posts = self.db.query(Post).filter(Post.thread_id.in_(random_thread)).all()
return thread_posts
@@ -443,8 +449,10 @@ class PromptRepository:
if post_role:
parent = parent.filter(Post.role == post_role)
parent = parent.order_by(func.random()).limit(1).subquery()
parent = parent.order_by(func.random()).limit(1)
replies = self.db.query(Post).filter(Post.parent_id.in_(parent)).order_by(func.random()).limit(max_size).all()
if not replies:
raise OasstError("No replies found", OasstErrorCode.NO_REPLIES_FOUND)
thread = self.fetch_thread(replies[0].thread_id)
thread = {p.id: p for p in thread}
@@ -463,3 +471,20 @@ class PromptRepository:
def fetch_post(self, post_id: UUID) -> Optional[Post]:
return self.db.query(Post).filter(Post.id == post_id).one()
def close_task(self, post_id: str, allow_personal_tasks: bool = False):
self.validate_post_id(post_id)
wp = self.fetch_workpackage_by_postid(post_id)
if not wp:
raise OasstError("Work package not found", OasstErrorCode.WORK_PACKAGE_NOT_FOUND)
if wp.expired:
raise OasstError("Work package expired", OasstErrorCode.WORK_PACKAGE_EXPIRED)
if not allow_personal_tasks and not wp.collective:
raise OasstError("This is not a collective task", OasstErrorCode.WORK_PACKAGE_NOT_COLLECTIVE)
if wp.done:
raise OasstError("Allready closed", OasstErrorCode.WORK_PACKAGE_ALREADY_DONE)
wp.done = True
self.db.add(wp)
self.db.commit()
+79
View File
@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
import enum
from typing import Optional, Type
import requests
from oasst_shared.schemas import protocol as protocol_schema
class TaskType(str, enum.Enum):
summarize_story = "summarize_story"
rate_summary = "rate_summary"
initial_prompt = "initial_prompt"
user_reply = "user_reply"
assistant_reply = "assistant_reply"
rank_initial_prompts = "rank_initial_prompts"
rank_user_replies = "rank_user_replies"
rank_assistant_replies = "rank_assistant_replies"
done = "task_done"
class ApiClient:
def __init__(self, backend_url: str, api_key: str):
self.backend_url = backend_url
self.api_key = api_key
task_models_map: dict[str, Type[protocol_schema.Task]] = {
TaskType.summarize_story: protocol_schema.SummarizeStoryTask,
TaskType.rate_summary: protocol_schema.RateSummaryTask,
TaskType.initial_prompt: protocol_schema.InitialPromptTask,
TaskType.user_reply: protocol_schema.UserReplyTask,
TaskType.assistant_reply: protocol_schema.AssistantReplyTask,
TaskType.rank_initial_prompts: protocol_schema.RankInitialPromptsTask,
TaskType.rank_user_replies: protocol_schema.RankUserRepliesTask,
TaskType.rank_assistant_replies: protocol_schema.RankAssistantRepliesTask,
TaskType.done: protocol_schema.TaskDone,
}
self.task_models_map = task_models_map
def post(self, path: str, json: dict) -> dict:
response = requests.post(f"{self.backend_url}{path}", json=json, headers={"X-API-Key": self.api_key})
response.raise_for_status()
return response.json()
def _parse_task(self, data: dict) -> protocol_schema.Task:
if not isinstance(data, dict):
raise ValueError("dict expected")
task_type = data.get("type")
if task_type not in self.task_models_map:
raise RuntimeError(f"Unsupported task type: {task_type}")
return self.task_models_map[task_type].parse_obj(data)
def fetch_task(
self,
task_type: protocol_schema.TaskRequestType,
user: Optional[protocol_schema.User] = None,
collective: bool = False,
) -> protocol_schema.Task:
req = protocol_schema.TaskRequest(type=task_type, user=user, collective=collective)
data = self.post("/api/v1/tasks/", req.dict())
return self._parse_task(data)
def fetch_random_task(
self, user: Optional[protocol_schema.User] = None, collective: bool = False
) -> protocol_schema.Task:
return self.fetch_task(protocol_schema.TaskRequestType.random, user, collective=collective)
def ack_task(self, task_id: str, post_id: str) -> None:
req = protocol_schema.TaskAck(post_id=post_id)
return self.post(f"/api/v1/tasks/{task_id}/ack", req.dict())
def nack_task(self, task_id: str, reason: str) -> None:
req = protocol_schema.TaskNAck(reason=reason)
return self.post(f"/api/v1/tasks/{task_id}/nack", req.dict())
def post_interaction(self, interaction: protocol_schema.Interaction) -> protocol_schema.Task:
data = self.post("/api/v1/tasks/interaction", interaction.dict())
return self._parse_task(data)
+2
View File
@@ -71,6 +71,7 @@ services:
environment:
- POSTGRES_HOST=db
- DEBUG_SKIP_API_KEY_CHECK=True
- DEBUG_USE_SEED_DATA=True
- MAX_WORKERS=1
depends_on:
db:
@@ -92,6 +93,7 @@ services:
- EMAIL_SERVER_PORT=1025
- EMAIL_FROM=info@example.com
- NEXTAUTH_URL=http://localhost:3000
- DEBUG_LOGIN=true
depends_on:
webdb:
condition: service_healthy
@@ -43,6 +43,7 @@ class TaskRequest(BaseModel):
type: TaskRequestType = TaskRequestType.random
user: Optional[User] = None
collective: bool = False
class TaskAck(BaseModel):
@@ -57,6 +58,12 @@ class TaskNAck(BaseModel):
reason: str
class TaskClose(BaseModel):
"""The frontend asks to mark task as done"""
post_id: str
class Task(BaseModel):
"""A task is a unit of work that the backend gives to the frontend."""
+1
View File
@@ -5,6 +5,7 @@ parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )
pushd "$parent_path/../../backend"
export DEBUG_SKIP_API_KEY_CHECK=True
export DEBUG_USE_SEED_DATA=True
uvicorn main:app --reload --port 8080 --host 0.0.0.0
+9 -1
View File
@@ -1,3 +1,11 @@
{
"extends": "next/core-web-vitals"
"root": true,
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"next/core-web-vitals"
],
"rules": {
"sort-imports": "warn"
}
}
+32
View File
@@ -0,0 +1,32 @@
const path = require("path");
module.exports = {
stories: [
"../src/components/**/*.stories.mdx",
"../src/components/**/*.stories.@(js|jsx|ts|tsx)",
],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"@chakra-ui/storybook-addon",
],
framework: "@storybook/react",
core: {
builder: "@storybook/builder-webpack5",
},
staticDirs: ["../public"],
// https://github.com/storybookjs/storybook/issues/15336#issuecomment-888528747
typescript: { reactDocgen: false },
// fix to make absolute imports working in storybook
webpackFinal: async (config, { configType }) => {
config.resolve.alias = {
...config.resolve.alias,
src: path.resolve(__dirname, "../src"),
};
return config;
},
features: {
emotionAlias: false,
},
};
+22
View File
@@ -0,0 +1,22 @@
import "!style-loader!css-loader!postcss-loader!tailwindcss/tailwind.css";
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
// Hacky solution to get Images in next to work
// https://dev.to/jonasmerlin/how-to-use-the-next-js-image-component-in-storybook-1415
import * as NextImage from "next/image";
const OriginalNextImage = NextImage.default;
Object.defineProperty(NextImage, "default", {
configurable: true,
value: (props) => <OriginalNextImage {...props} unoptimized />,
});
+8 -2
View File
@@ -64,12 +64,18 @@ If you're doing active development we suggest the following workflow:
### Using debug user credentials
Whenever the website runs in development mode, you can use the debug credentials provider to log in without fancy emails or OAuth.
You can use the debug credentials provider to log in without fancy emails or OAuth.
1. Development mode is automatically active when you start the website with `npm run dev`.
1. This feature is automatically on in development mode, i.e. when you run `npm run dev`. In case you want to do the same with a production build (for example, the docker image), then run the website with environment variable `DEBUG_LOGIN=true`.
1. Use the `Login` button in the top right to go to the login page.
1. You should see a section for debug credentials. Enter any username you wish, you will be logged in as that user.
### Using Storybook
To develop components using [Storybook](https://storybook.js.org/) run `npm run storybook`. Then navigate to in your browser to `http://localhost:6006`.
To create a new story create a file named `[componentName].stories.js`. An example how such a story could look like, see `Header.stories.jsx`.
## Code Layout
### React Code
-8
View File
@@ -1,8 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
}
+28847 -2589
View File
File diff suppressed because it is too large Load Diff
+20 -2
View File
@@ -7,7 +7,9 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"dependencies": {
"@chakra-ui/react": "^2.4.4",
@@ -21,6 +23,7 @@
"@tailwindcss/forms": "^0.5.3",
"autoprefixer": "^10.4.13",
"axios": "^1.2.1",
"boolean": "^3.2.0",
"clsx": "^1.2.1",
"eslint": "8.29.0",
"eslint-config-next": "13.0.6",
@@ -39,9 +42,24 @@
"use-debounce": "^9.0.2"
},
"devDependencies": {
"@babel/core": "^7.20.7",
"@chakra-ui/storybook-addon": "^4.0.16",
"@storybook/addon-actions": "^6.5.15",
"@storybook/addon-essentials": "^6.5.15",
"@storybook/addon-interactions": "^6.5.15",
"@storybook/addon-links": "^6.5.15",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/builder-webpack5": "^6.5.15",
"@storybook/manager-webpack5": "^6.5.15",
"@storybook/react": "^6.5.15",
"@storybook/testing-library": "^0.0.13",
"@types/node": "18.11.17",
"@types/react": "18.0.26",
"babel-loader": "^8.3.0",
"eslint-plugin-storybook": "^0.6.8",
"@typescript-eslint/eslint-plugin": "^5.47.1",
"prettier": "2.8.1",
"prisma": "^4.7.1"
"prisma": "^4.7.1",
"typescript": "4.9.4"
}
}
-19
View File
@@ -1,19 +0,0 @@
import clsx from "clsx";
export const Button = (
props: React.DetailedHTMLProps<React.ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
) => {
const { className, children, ...rest } = props;
return (
<button
type="button"
className={clsx(
"inline-flex items-center rounded-md border border-transparent px-4 py-2 text-sm font-medium focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2",
className
)}
{...rest}
>
{children}
</button>
);
};
+9
View File
@@ -0,0 +1,9 @@
import { Button, ButtonProps } from "@chakra-ui/react";
export const SkipButton = ({ children, ...props }: ButtonProps) => {
return (
<Button size="lg" variant="outline" {...props}>
{children}
</Button>
);
};
@@ -0,0 +1,9 @@
import { Button, ButtonProps } from "@chakra-ui/react";
export const SubmitButton = ({ children, ...props }: ButtonProps) => {
return (
<Button size="lg" variant="solid" {...props}>
{children}
</Button>
);
};
+1 -1
View File
@@ -1,7 +1,7 @@
import { useId } from "react";
export function CircleBackground({ color, width = 558, height = 558, ...props }) {
let id = useId();
const id = useId();
return (
<svg viewBox="0 0 558 558" width={width} height={height} fill="none" aria-hidden="true" {...props}>
+1 -3
View File
@@ -1,7 +1,5 @@
import Image from "next/image";
import Link from "next/link";
import { FaGithub, FaDiscord } from "react-icons/fa";
import { Container } from "./Container";
export function Footer() {
@@ -27,7 +25,7 @@ export function Footer() {
<Link href="#" aria-label="Our Team" className="hover:underline underline-offset-2">
Our Team
</Link>
<Link href="#join-us" aria-label="Join Us" className="hover:underline underline-offset-2">
<Link href="/#join-us" aria-label="Join Us" className="hover:underline underline-offset-2">
Join Us
</Link>
</div>
@@ -0,0 +1,24 @@
import { SessionContext } from "next-auth/react";
import React from "react";
import { Header } from "./Header";
export default {
title: "Header/Header",
component: Header,
parameters: {
layout: "fullscreen",
},
};
const Template = (args) => {
var { session } = args;
return (
<SessionContext.Provider value={session}>
<Header {...args} />
</SessionContext.Provider>
);
};
export const Default = Template.bind({});
Default.args = { session: { data: { user: { name: "StoryBook user" } }, status: "authenticated" }, transparent: false };
@@ -3,12 +3,14 @@ import { Popover } from "@headlessui/react";
import { AnimatePresence, motion } from "framer-motion";
import Image from "next/image";
import Link from "next/link";
import { signOut, useSession } from "next-auth/react";
import { FaUser, FaSignOutAlt } from "react-icons/fa";
import clsx from "clsx";
import { UserMenu } from "./UserMenu";
import { Container } from "./Container";
import { Container } from "src/components/Container";
import { NavLinks } from "./NavLinks";
import { UserMenu } from "./UserMenu";
function MenuIcon(props) {
return (
@@ -53,9 +55,10 @@ function AccountButton() {
);
}
export function Header() {
export function Header(props) {
const transparent = props.transparent ?? false;
return (
<header className="bg-white">
<header className={clsx(!transparent && "bg-white")}>
<nav>
<Container className="relative z-10 flex justify-between py-8">
<div className="relative z-10 flex items-center gap-16">
@@ -101,8 +104,8 @@ export function Header() {
className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-white px-6 pb-6 pt-32 shadow-2xl shadow-gray-900/20"
>
<div className="space-y-4">
<MobileNavLink href="#join-us">Join Us</MobileNavLink>
<MobileNavLink href="#faqs">FAQs</MobileNavLink>
<MobileNavLink href="/#join-us">Join Us</MobileNavLink>
<MobileNavLink href="/#faqs">FAQs</MobileNavLink>
</div>
<div className="mt-8 flex flex-col gap-4"></div>
</Popover.Panel>
@@ -0,0 +1,14 @@
import { NavLinks } from "./NavLinks";
export default {
title: "Header/NavLinks",
component: NavLinks,
};
const Template = (args) => (
<div className="hidden lg:flex lg:gap-10">
<NavLinks {...args} />
</div>
);
export const Default = Template.bind({});
@@ -3,13 +3,13 @@ import Link from "next/link";
import { AnimatePresence, motion } from "framer-motion";
export function NavLinks(): JSX.Element {
let [hoveredIndex, setHoveredIndex] = useState(null);
const [hoveredIndex, setHoveredIndex] = useState(null);
return (
<>
{[
["Join Us", "#join-us"],
["FAQ", "#faq"],
["Join Us", "/#join-us"],
["FAQ", "/#faq"],
].map(([label, href], index) => (
<Link
key={label}
@@ -0,0 +1,25 @@
import { SessionContext } from "next-auth/react";
import React from "react";
import UserMenu from "./UserMenu";
export default {
title: "Header/UserMenu",
component: UserMenu,
};
const Template = (args) => {
var { session } = args;
return (
<SessionContext.Provider value={session}>
<div className="flex flex-col">
<div className="self-end">
<UserMenu {...args} />
</div>
</div>
</SessionContext.Provider>
);
};
export const Default = Template.bind({});
Default.args = { session: { data: { user: { name: "StoryBook user" } }, status: "authenticated" } };
@@ -3,7 +3,7 @@ import { signOut, useSession } from "next-auth/react";
import Image from "next/image";
import { Popover } from "@headlessui/react";
import { AnimatePresence, motion } from "framer-motion";
import { FaCog, FaSignOutAlt, FaGithub } from "react-icons/fa";
import { FaCog, FaSignOutAlt } from "react-icons/fa";
export function UserMenu() {
const { data: session } = useSession();
+3
View File
@@ -0,0 +1,3 @@
export { Header } from "./Header";
export { UserMenu } from "./UserMenu";
export { NavLinks } from "./NavLinks";
+2 -2
View File
@@ -3,9 +3,9 @@
import type { NextPage } from "next";
import { Footer } from "./Footer";
import { Header } from "./Header";
import { Header } from "src/components/Header";
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
export type NextPageWithLayout<P = unknown, IP = P> = NextPage<P, IP> & {
getLayout?: (page: React.ReactElement) => React.ReactNode;
};
@@ -0,0 +1,16 @@
import { LoadingScreen } from "./LoadingScreen";
export default {
title: "Example/LoadingScreen",
component: LoadingScreen,
parameters: {
layout: "fullscreen",
},
};
const Template = (args) => <LoadingScreen {...args} />;
export const Default = Template.bind({});
export const WithText = Template.bind({});
WithText.args = { text: "Loading Text ..." };
@@ -0,0 +1,12 @@
import { Progress } from "@chakra-ui/react";
export const LoadingScreen = ({ text }) => (
<div className="bg-slate-100">
<Progress size="xs" isIndeterminate />
{text && (
<div className="flex h-full">
<div className="text-xl font-bold text-gray-800 mx-auto my-auto">{text}</div>
</div>
)}
</div>
);
@@ -0,0 +1,48 @@
import { ReactNode, useEffect, useState } from "react";
import { SortableItem } from "./SortableItem";
export interface SortableProps {
items: ReactNode[];
onChange: (newSortedIndices: number[]) => void;
}
export const Sortable = ({ items, onChange }) => {
const [sortOrder, setSortOrder] = useState<number[]>([]);
const update = (newRanking: number[]) => {
setSortOrder(newRanking);
onChange(newRanking);
};
useEffect(() => {
const indices = Array.from({ length: items.length }).map((_, i) => i);
setSortOrder(indices);
onChange(indices);
}, [items, onChange]);
return (
<ul className="flex flex-col gap-4">
{sortOrder.map((rank, i) => (
<SortableItem
key={`${rank}`}
canIncrement={i > 0}
onIncrement={() => {
const newRanking = sortOrder.slice();
const newIdx = i - 1;
[newRanking[i], newRanking[newIdx]] = [newRanking[newIdx], newRanking[i]];
update(newRanking);
}}
canDecrement={i < sortOrder.length - 1}
onDecrement={() => {
const newRanking = sortOrder.slice();
const newIdx = i + 1;
[newRanking[i], newRanking[newIdx]] = [newRanking[newIdx], newRanking[i]];
update(newRanking);
}}
>
{items[rank]}
</SortableItem>
))}
</ul>
);
};
@@ -0,0 +1,40 @@
import { ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/20/solid";
import { Button } from "@chakra-ui/react";
import clsx from "clsx";
export interface SortableItemProps {
canIncrement: boolean;
canDecrement: boolean;
onIncrement: () => void;
onDecrement: () => void;
children: React.ReactNode;
}
export const SortableItem = ({ canIncrement, canDecrement, onIncrement, onDecrement, children }: SortableItemProps) => {
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>
);
};
interface ArrowButtonProps {
active: boolean;
onClick: () => void;
children: React.ReactNode;
}
const ArrowButton = ({ children, active, onClick }: ArrowButtonProps) => {
return (
<Button justifyContent="center" variant="ghost" onClick={onClick} disabled={!active}>
{children}
</Button>
);
};
@@ -0,0 +1,10 @@
export const TaskInfo = ({ id, output }: { id: string; output: any }) => {
return (
<div className="grid grid-cols-[min-content_auto] gap-x-2 text-gray-700">
<b>Prompt</b>
<span>{id}</span>
<b>Output</b>
<span>{output}</span>
</div>
);
};
@@ -7,12 +7,12 @@ export const TaskSelection = () => {
return (
<Flex gap={10} wrap="wrap" justifyContent="space-evenly" width="full" height="full" alignItems={"center"}>
<TaskOptions key="create" title="Create">
<TaskOption
{/* <TaskOption
alt="Summarize Stories"
img="/images/logos/logo.svg"
title="Summarize stories"
link="/create/summarize_story"
/>
/> */}
<TaskOption alt="Reply as User" img="/images/logos/logo.svg" title="Reply as User" link="/create/user_reply" />
<TaskOption
alt="Reply as Assistant"
@@ -22,18 +22,30 @@ export const TaskSelection = () => {
/>
</TaskOptions>
<TaskOptions key="evaluate" title="Evaluate">
<TaskOption
{/* <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"
/>
<TaskOption
alt="Rank User Replies"
img="/images/logos/logo.svg"
title="Rank User Replies"
link="/evaluate/rank_user_replies"
/>
<TaskOption
alt="Rank Assistant Replies"
img="/images/logos/logo.svg"
title="Rank Assistant Replies"
link="/evaluate/rank_assistant_replies"
/>
</TaskOptions>
</Flex>
);
+3 -3
View File
@@ -1,10 +1,10 @@
import { PrismaClient } from "@prisma/client";
declare global {
var prisma;
// eslint-disable-next-line no-var
var prisma: PrismaClient | undefined;
}
const client = globalThis.prisma || new PrismaClient();
const client = new PrismaClient();
if (process.env.NODE_ENV !== "production") {
globalThis.prisma = client;
}
+1 -1
View File
@@ -1,6 +1,6 @@
import { useSession } from "next-auth/react";
import { Footer } from "../components/Footer";
import { Header } from "../components/Header";
import { Header } from "src/components/Header";
import Head from "next/head";
import Link from "next/link";
+1 -1
View File
@@ -4,7 +4,7 @@ import { Inter } from "@next/font/google";
import { extendTheme } from "@chakra-ui/react";
import type { AppProps } from "next/app";
import { getDefaultLayout, NextPageWithLayout } from "src/components/Layout";
import { NextPageWithLayout, getDefaultLayout } from "src/components/Layout";
import "../styles/globals.css";
import "focus-visible";
+2 -1
View File
@@ -5,6 +5,7 @@ import DiscordProvider from "next-auth/providers/discord";
import EmailProvider from "next-auth/providers/email";
import CredentialsProvider from "next-auth/providers/credentials";
import { PrismaAdapter } from "@next-auth/prisma-adapter";
import { boolean } from "boolean";
import prisma from "src/lib/prismadb";
@@ -34,7 +35,7 @@ if (process.env.DISCORD_CLIENT_ID) {
);
}
if (process.env.NODE_ENV === "development") {
if (boolean(process.env.DEBUG_LOGIN) || process.env.NODE_ENV === "development") {
providers.push(
CredentialsProvider({
name: "Debug Credentials",
+30 -16
View File
@@ -1,19 +1,26 @@
import { Button, Input, Stack } from "@chakra-ui/react";
import Head from "next/head";
import { FaDiscord, FaEnvelope, FaGithub } from "react-icons/fa";
import { FaDiscord, FaEnvelope, FaGithub, FaBug } from "react-icons/fa";
import { getCsrfToken, getProviders, signIn } from "next-auth/react";
import { useRef } from "react";
import React, { useRef } from "react";
import Link from "next/link";
import { AuthLayout } from "src/components/AuthLayout";
export default function Signin({ csrfToken, providers }) {
const { discord, email, github } = providers;
const { discord, email, github, credentials } = providers;
const emailEl = useRef(null);
const signinWithEmail = () => {
const signinWithEmail = (ev: React.FormEvent) => {
ev.preventDefault();
signIn(email.id, { callbackUrl: "/", email: emailEl.current.value });
};
const debugUsernameEl = useRef(null);
function signinWithDebugCredentials(ev: React.FormEvent) {
ev.preventDefault();
signIn(credentials.id, { callbackUrl: "/", username: debugUsernameEl.current.value });
}
return (
<>
<Head>
@@ -22,19 +29,26 @@ export default function Signin({ csrfToken, providers }) {
</Head>
<AuthLayout>
<Stack spacing="2">
{credentials && (
<form onSubmit={signinWithDebugCredentials} className="border-2 border-orange-200 rounded-md p-4 relative">
<span className="text-orange-600 absolute -top-3 left-5 bg-white px-1">For Debugging Only</span>
<Stack>
<Input variant="outline" size="lg" placeholder="Username" ref={debugUsernameEl} />
<Button size={"lg"} leftIcon={<FaBug />} colorScheme="gray" type="submit">
Continue with Debug User
</Button>
</Stack>
</form>
)}
{email && (
<Stack>
<Input variant="outline" size="lg" placeholder="Email Address" ref={emailEl} />
<Button
size={"lg"}
leftIcon={<FaEnvelope />}
colorScheme="gray"
onClick={signinWithEmail}
// isDisabled="false"
>
Continue with Email
</Button>
</Stack>
<form onSubmit={signinWithEmail}>
<Stack>
<Input variant="outline" size="lg" placeholder="Email Address" ref={emailEl} />
<Button size={"lg"} leftIcon={<FaEnvelope />} colorScheme="gray" type="submit">
Continue with Email
</Button>
</Stack>
</form>
)}
{discord && (
<Button
+16 -21
View File
@@ -1,13 +1,17 @@
import { Textarea } from "@chakra-ui/react";
import { Flex, 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";
import { Messages } from "src/components/Messages";
import { TwoColumns } from "src/components/TwoColumns";
import { Button } from "src/components/Button";
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
const AssistantReply = () => {
const [tasks, setTasks] = useState([]);
@@ -39,11 +43,12 @@ const AssistantReply = () => {
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (isLoading) {
return <LoadingScreen text="Loading..." />;
}
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
return <div className="p-6 bg-slate-100 text-gray-800">No tasks found...</div>;
}
const task = tasks[0].task;
@@ -59,22 +64,12 @@ const AssistantReply = () => {
</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>
<TaskInfo id={tasks[0].id} output="Submit your answer" />
<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>
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
<SubmitButton onClick={() => submitResponse(tasks[0])}>Submit</SubmitButton>
</Flex>
</section>
</div>
);
+15 -21
View File
@@ -1,4 +1,4 @@
import { Textarea } from "@chakra-ui/react";
import { Flex, Textarea } from "@chakra-ui/react";
import Head from "next/head";
import { useRef, useState } from "react";
import useSWRImmutable from "swr/immutable";
@@ -7,8 +7,11 @@ import useSWRMutation from "swr/mutation";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { TwoColumns } from "src/components/TwoColumns";
import { Button } from "src/components/Button";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
const SummarizeStory = () => {
// Use an array of tasks that record the sequence of steps until a task is
@@ -49,11 +52,12 @@ const SummarizeStory = () => {
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (isLoading) {
return <LoadingScreen text="Loading..." />;
}
if (tasks.length == 0) {
return <div className=" p-6 bg-slate-100 text-gray-800">Loading...</div>;
return <div className="p-6 bg-slate-100 text-gray-800">No tasks found...</div>;
}
return (
@@ -73,22 +77,12 @@ const SummarizeStory = () => {
</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>
<TaskInfo id={tasks[0].id} output="Submit your answer" />
<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>
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
<SubmitButton onClick={() => submitResponse(tasks[0])}>Submit</SubmitButton>
</Flex>
</section>
</main>
</>
+16 -21
View File
@@ -1,13 +1,17 @@
import { Textarea } from "@chakra-ui/react";
import { Flex, 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";
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { Messages } from "src/components/Messages";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
import { TwoColumns } from "src/components/TwoColumns";
import { Button } from "src/components/Button";
import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
const UserReply = () => {
const [tasks, setTasks] = useState([]);
@@ -39,11 +43,12 @@ const UserReply = () => {
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (isLoading) {
return <LoadingScreen text="Loading..." />;
}
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
return <div className="p-6 bg-slate-100 text-gray-800">No tasks found...</div>;
}
const task = tasks[0].task;
@@ -60,22 +65,12 @@ const UserReply = () => {
</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>
<TaskInfo id={tasks[0].id} output="Submit your answer" />
<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>
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
<SubmitButton onClick={() => submitResponse(tasks[0])}>Submit</SubmitButton>
</Flex>
</section>
</div>
);
@@ -0,0 +1,86 @@
import { Button, Flex } from "@chakra-ui/react";
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 { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { Sortable } from "src/components/Sortable/Sortable";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
import { SubmitButton } from "src/components/Buttons/Submit";
import { SkipButton } from "src/components/Buttons/Skip";
const RankAssistantReplies = () => {
const [tasks, setTasks] = useState([]);
/**
* This array will contain the ranked indices of the replies
* The best reply will have index 0, and the worst is the last.
*/
const [ranking, setRanking] = useState<number[]>([]);
const { isLoading } = useSWRImmutable("/api/new_task/rank_assistant_replies", fetcher, {
onSuccess: (data) => {
setTasks([data]);
},
});
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,
},
});
};
if (isLoading) {
return <LoadingScreen text="Loading..." />;
}
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
}
const replies = tasks[0].task.replies as string[];
return (
<>
<Head>
<title>Rank Assistant Replies</title>
<meta name="description" content="Rank Assistant Replies." />
</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 replies, sort them from best to worst, best being first, worst being last.
</p>
<Sortable items={replies} onChange={setRanking} />
</div>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch">
<TaskInfo id={tasks[0].id} output="Submit your answer" />
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
<SubmitButton onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
Submit
</SubmitButton>
</Flex>
</section>
</main>
</>
);
};
export default RankAssistantReplies;
@@ -1,5 +1,4 @@
import { ArrowUpIcon, ArrowDownIcon } from "@heroicons/react/20/solid";
import clsx from "clsx";
import { Flex } from "@chakra-ui/react";
import Head from "next/head";
import { useState } from "react";
import useSWRImmutable from "swr/immutable";
@@ -8,7 +7,11 @@ import useSWRMutation from "swr/mutation";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import { Button } from "src/components/Button";
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { Sortable } from "src/components/Sortable/Sortable";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
const RankInitialPrompts = () => {
const [tasks, setTasks] = useState([]);
@@ -21,9 +24,6 @@ const RankInitialPrompts = () => {
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);
},
});
@@ -44,17 +44,13 @@ const RankInitialPrompts = () => {
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
if (isLoading) {
return <LoadingScreen text="Loading..." />;
}
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">No tasks found...</div>;
}
const prompts = tasks[0].task.prompts as string[];
const items = ranking.map((i) => ({
text: prompts[i],
originalIndex: i,
}));
return (
<>
@@ -68,49 +64,18 @@ const RankInitialPrompts = () => {
<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>
<Sortable items={tasks[0].task.prompts} onChange={setRanking} />
</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>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch">
<TaskInfo id={tasks[0].id} output="Submit your answer" />
<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"
>
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
<SubmitButton onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
Submit
</Button>
</div>
</SubmitButton>
</Flex>
</section>
</main>
</>
@@ -118,30 +83,3 @@ const RankInitialPrompts = () => {
};
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>
);
};
@@ -0,0 +1,86 @@
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 { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { Sortable } from "src/components/Sortable/Sortable";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
import { Flex } from "@chakra-ui/react";
import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
const RankUserReplies = () => {
const [tasks, setTasks] = useState([]);
/**
* This array will contain the ranked indices of the replies
* The best reply will have index 0, and the worst is the last.
*/
const [ranking, setRanking] = useState<number[]>([]);
const { isLoading } = useSWRImmutable("/api/new_task/rank_user_replies", fetcher, {
onSuccess: (data) => {
setTasks([data]);
},
});
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,
},
});
};
if (isLoading) {
return <LoadingScreen text="Loading..." />;
}
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
}
const replies = tasks[0].task.replies as string[];
return (
<>
<Head>
<title>Rank User Replies</title>
<meta name="description" content="Rank User Replies." />
</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 replies, sort them from best to worst, best being first, worst being last.
</p>
<Sortable items={replies} onChange={setRanking} />
</div>
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
<TaskInfo id={tasks[0].id} output="Submit your answer" />
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
<SubmitButton onClick={() => submitResponse(tasks[0])} disabled={ranking.length === 0}>
Submit
</SubmitButton>
</Flex>
</section>
</main>
</>
);
};
export default RankUserReplies;
+15 -21
View File
@@ -1,4 +1,4 @@
import { Textarea } from "@chakra-ui/react";
import { Flex, Textarea } from "@chakra-ui/react";
import { QuestionMarkCircleIcon } from "@heroicons/react/20/solid";
import Head from "next/head";
import { useState } from "react";
@@ -9,8 +9,11 @@ import RatingRadioGroup from "src/components/RatingRadioGroup";
import fetcher from "src/lib/fetcher";
import poster from "src/lib/poster";
import { LoadingScreen } from "src/components/Loading/LoadingScreen";
import { TwoColumns } from "src/components/TwoColumns";
import { Button } from "src/components/Button";
import { TaskInfo } from "src/components/TaskInfo/TaskInfo";
import { SkipButton } from "src/components/Buttons/Skip";
import { SubmitButton } from "src/components/Buttons/Submit";
const RateSummary = () => {
// Use an array of tasks that record the sequence of steps until a task is
@@ -49,11 +52,12 @@ const RateSummary = () => {
});
};
/**
* TODO: Make this a nicer loading screen.
*/
if (isLoading) {
return <LoadingScreen text="Loading..." />;
}
if (tasks.length == 0) {
return <div className="p-6 bg-slate-100 text-gray-800">Loading...</div>;
return <div className="p-6 bg-slate-100 text-gray-800">No tasks found...</div>;
}
return (
@@ -89,22 +93,12 @@ const RateSummary = () => {
</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>
<TaskInfo id={tasks[0].id} output="Submit your answer" />
<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>
<Flex justify="center" ml="auto" gap={2}>
<SkipButton>Skip</SkipButton>
<SubmitButton onClick={() => submitResponse(tasks[0])}>Submit</SubmitButton>
</Flex>
</section>
</main>
</>
+10
View File
@@ -5,6 +5,8 @@ import { CallToAction } from "src/components/CallToAction";
import { Faq } from "src/components/Faq";
import { Hero } from "src/components/Hero";
import { TaskSelection } from "src/components/TaskSelection";
import { Header } from "src/components/Header";
import { Footer } from "src/components/Footer";
const Home = () => {
const { data: session } = useSession();
@@ -34,4 +36,12 @@ const Home = () => {
);
};
Home.getLayout = (page) => (
<div className="grid grid-rows-[min-content_1fr_min-content] h-full justify-items-stretch">
<Header transparent={true} />
{page}
<Footer />
</div>
);
export default Home;
@@ -1,7 +1,5 @@
import RankItem from "@/components/RankItem";
import { BarsArrowUpIcon, BarsArrowDownIcon } from "@heroicons/react/24/solid";
import Image from "next/image";
import { HiBarsArrowUp, HiBarsArrowDown } from "react-icons/hi2";
import RankItem from "src/components/RankItem";
import { HiBarsArrowDown } from "react-icons/hi2";
const LeaderBoard = () => {
const PlaceHolderProps = { username: "test_user", score: 10 };
+1 -4
View File
@@ -14,10 +14,7 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
"baseUrl": "."
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]