mirror of
https://github.com/wassname/Open-Assistant.git
synced 2026-07-08 00:10:24 +08:00
Merge branch 'main' of https://github.com/LAION-AI/Open-Assistant into LAION-AI-main
This commit is contained in:
@@ -4,7 +4,11 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- website/**
|
||||
pull_request:
|
||||
paths:
|
||||
- website/**
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+31
@@ -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
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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."""
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
};
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+28847
-2589
File diff suppressed because it is too large
Load Diff
+20
-2
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,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,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();
|
||||
@@ -0,0 +1,3 @@
|
||||
export { Header } from "./Header";
|
||||
export { UserMenu } from "./UserMenu";
|
||||
export { NavLinks } from "./NavLinks";
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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,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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -14,10 +14,7 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
}
|
||||
"baseUrl": "."
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"exclude": ["node_modules"]
|
||||
|
||||
Reference in New Issue
Block a user