diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..6313b56c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/.github/workflows/docker-build.yaml b/.github/workflows/docker-build.yaml index 5697d5b0..2a8c2ff9 100644 --- a/.github/workflows/docker-build.yaml +++ b/.github/workflows/docker-build.yaml @@ -3,7 +3,10 @@ name: Build on: workflow_call: inputs: - folder: + dockerfile: + required: true + type: string + context: required: true type: string image-name: @@ -48,7 +51,8 @@ jobs: - name: Build and push Docker image uses: docker/build-push-action@v3.2.0 with: - context: ${{ inputs.folder }} + file: ${{ inputs.dockerfile }} + context: ${{ inputs.context }} build-args: ${{ inputs.build-args }} push: true tags: ${{ steps.meta.outputs.tags }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index a7dd89c8..bb844a34 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,5 +9,39 @@ jobs: uses: ./.github/workflows/docker-build.yaml with: image-name: oasst-backend - folder: backend + context: . + dockerfile: docker/Dockerfile.backend build-args: "" + build-web: + uses: ./.github/workflows/docker-build.yaml + with: + image-name: oasst-web + context: . + dockerfile: docker/Dockerfile.website + build-args: "" + build-bot: + uses: ./.github/workflows/docker-build.yaml + with: + image-name: oasst-discord-bot + context: . + dockerfile: docker/Dockerfile.discord-bot + build-args: "" + deploy-dev: + needs: [build-backend, build-web, build-bot] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + - name: Run playbook + uses: dawidd6/action-ansible-playbook@v2 + with: + # Required, playbook filepath + playbook: dev.yaml + # Optional, directory where playbooks live + directory: ansible + # Optional, SSH private key + key: ${{secrets.DEV_NODE_PRIVATE_KEY}} + # Optional, literal inventory file contents + inventory: | + [dev] + dev01 ansible_host=${{secrets.DEV_NODE_IP}} ansible_connection=ssh ansible_user=web-team diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cccb2167..928810f5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,3 +46,13 @@ repos: hooks: - id: prettier args: ["--write"] + + - repo: local + hooks: + - id: next-lint-website + name: Lint website + files: ^website/ + types_or: [javascript, jsx, ts, tsx] + language: system + pass_filenames: false + entry: bash -c 'cd website && npm install && npm run lint' diff --git a/README.md b/README.md index 43c5a885..a2b92789 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ 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. +## The Plan + +We want to get to an initial MVP as fast as possible, by following the 3-steps outlined in the InstructGPT paper. + +1. Collect high-quality human generated Instruction-Fulfillment samples (prompt + response), goal >50k. We design a crowdsourced process to collect and reviewed prompts. We do not want to train on flooding/toxic/spam/junk/personal information data. We will have a leaderboard to motivate the community that shows progress and the most active users. Swag will be given to the top-contributors. +2. For each of the collected prompts we will sample multiple completions. Completions of one prompt will then be shown randomly to users to rank them from best to worst. Again this should happen crowd-sourced, e.g. we need to deal with unreliable potentially malicious users. At least multiple votes by independent users have to be collected to measure the overall agreement. The gathered ranking-data will be used to train a reward model. +3. Now follows the RLHF training phase based on the prompts and the reward model. + +We can then take the resulting model and continue with completion sampling step 2 for a next iteration. + +## The Vision + +We are not going to stop at replicating ChatGPT. We want to build the assistant of the future, able to not only write email and cover letters, but do meaningful work, use APIs, dynamically research information, and much more, with the ability to be personalized and extended by anyone. And we want to do this in a way that is open and accessible, which means we must not only build a great assistant, but also make it small and efficient enough to run on consumer hardware. + ## How can you help? All open source projects begins with people like you. Open source is the belief that if we collaborate we can together gift our knowledge and technology to the world for the benefit of humanity. @@ -12,7 +26,7 @@ All open source projects begins with people like you. Open source is the belief [Fill out the contributor signup form](https://docs.google.com/forms/d/e/1FAIpQLSeuggO7UdYkBvGLEJldDvxp6DwaRbW5p7dl96UzFkZgziRTrQ/viewform) -[Join the LAION Discord Server!](https://discord.gg/RQFtmAmk) +[Join the LAION Discord Server!](https://discord.com/invite/mVcgxMPD7e) [Visit the Notion](https://ykilcher.com/open-assistant) @@ -43,30 +57,6 @@ Install `pre-commit` and run `pre-commit install` to install the pre-commit hook In case you haven't done this, have already committed, and CI is failing, you can run `pre-commit run --all-files` to run the pre-commit hooks on all files. -# (Older version of the readme below) +### Deployment -## How do I start helping out? - -Check out these pages to learn more about the project. - -Ping Birger on discord if you want help to get started. - -http://**discordapp.com/users/birger#6875** - -## More information in the notion - -https://roan-iguanadon-a58.notion.site/Open-Chat-Gpt-83dd217eeeb84907a155b8a9d716fa46 - -## Code structure - -### Bot - -We have a folder named bot where code related to the bot lives. - -### Backend - -We have a backend folder for backend development of the api that the discord bot sends it information to. - -### Website - -We have a folder for the website, live at https://projects.laion.ai/Open-Chat-GPT/ .The website is built using Next.js +Upon making a release on GitHub, all docker images are automatically built and pushed to ghcr.io. The docker images are tagged with the release version, and the `latest` tag. Further, the ansible playbook in `ansible/dev.yaml` is run to automatically deploy the built release to the dev machine. diff --git a/ansible/.gitignore b/ansible/.gitignore new file mode 100644 index 00000000..a20b2ddb --- /dev/null +++ b/ansible/.gitignore @@ -0,0 +1 @@ +*.local.yaml diff --git a/ansible/dev.yaml b/ansible/dev.yaml new file mode 100644 index 00000000..a44506ac --- /dev/null +++ b/ansible/dev.yaml @@ -0,0 +1,77 @@ +# ansible playbook to set up some docker containers + +- name: Set up a dev node + hosts: dev + gather_facts: true + tasks: + - name: Create network + community.docker.docker_network: + name: oasst + state: present + driver: bridge + + - name: Create postgres containers + community.docker.docker_container: + name: "{{ item.name }}" + image: postgres:15 + state: started + restart_policy: always + network_mode: oasst + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + volumes: + - "{{ item.name }}:/var/lib/postgresql/data" + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 2s + timeout: 2s + retries: 10 + loop: + - name: oasst-postgres + - name: oasst-postgres-web + + - name: Set up maildev + community.docker.docker_container: + name: oasst-maildev + image: maildev/maildev + state: started + restart_policy: always + network_mode: oasst + + - name: Run the oasst oasst-backend + community.docker.docker_container: + name: oasst-backend + image: ghcr.io/laion-ai/open-assistant/oasst-backend + state: started + pull: true + restart_policy: always + network_mode: oasst + env: + POSTGRES_HOST: oasst-postgres + DEBUG_ALLOW_ANY_API_KEY: "true" + MAX_WORKERS: "1" + ports: + - 8080:8080 + + - name: Run the oasst oasst-web frontend + community.docker.docker_container: + name: oasst-web + image: ghcr.io/laion-ai/open-assistant/oasst-web + state: started + pull: true + restart_policy: always + network_mode: oasst + env: + FASTAPI_URL: http://oasst-backend:8080 + FASTAPI_KEY: "123" + DATABASE_URL: postgres://postgres:postgres@oasst-postgres-web/postgres + NEXTAUTH_SECRET: O/M2uIbGj+lDD2oyNa8ax4jEOJqCPJzO53UbWShmq98= + EMAIL_SERVER_HOST: oasst-maildev + EMAIL_SERVER_PORT: "25" + EMAIL_FROM: info@example.com + NEXTAUTH_URL: http://web.dev.open-assistant.io/ + ports: + - 3000:3000 + command: bash wait-for-postgres.sh node server.js diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako index 55df2863..3124b62c 100644 --- a/backend/alembic/script.py.mako +++ b/backend/alembic/script.py.mako @@ -7,6 +7,7 @@ Create Date: ${create_date} """ from alembic import op import sqlalchemy as sa +import sqlmodel ${imports if imports else ""} # revision identifiers, used by Alembic. diff --git a/backend/alembic/versions/2022_12_25_1705-067c4002f2d9_add_text_labels.py b/backend/alembic/versions/2022_12_25_1705-067c4002f2d9_add_text_labels.py new file mode 100644 index 00000000..94e1c514 --- /dev/null +++ b/backend/alembic/versions/2022_12_25_1705-067c4002f2d9_add_text_labels.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +"""Adds text labels table. + +Revision ID: 067c4002f2d9 +Revises: 0daec5f8135f +Create Date: 2022-12-25 17:05:21.208843 + +""" +import sqlalchemy as sa +import sqlmodel +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = "067c4002f2d9" +down_revision = "0daec5f8135f" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "text_labels", + sa.Column("id", postgresql.UUID(as_uuid=True), server_default=sa.text("gen_random_uuid()"), nullable=False), + sa.Column("created_date", sa.DateTime(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False), + sa.Column("post_id", postgresql.UUID(as_uuid=True), nullable=True), + sa.Column("labels", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("api_client_id", sqlmodel.sql.sqltypes.GUID(), nullable=False), + sa.Column("text", sqlmodel.sql.sqltypes.AutoString(length=65536), nullable=False), + sa.ForeignKeyConstraint( + ["api_client_id"], + ["api_client.id"], + ), + sa.ForeignKeyConstraint( + ["post_id"], + ["post.id"], + ), + sa.PrimaryKeyConstraint("id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("text_labels") + # ### end Alembic commands ### diff --git a/backend/oasst_backend/api/deps.py b/backend/oasst_backend/api/deps.py index 96af5c5e..505fa2c6 100644 --- a/backend/oasst_backend/api/deps.py +++ b/backend/oasst_backend/api/deps.py @@ -37,21 +37,21 @@ def api_auth( db: Session, ) -> ApiClient: - if api_key is not None: - if settings.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 + if api_key is None and not settings.DEBUG_SKIP_API_KEY_CHECK: + raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials") - api_client = db.query(ApiClient).filter(ApiClient.api_key == api_key).first() - if api_client is not None and api_client.enabled: - return api_client + 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 - raise HTTPException(status_code=HTTP_403_FORBIDDEN, detail="Could not validate credentials") + api_client = db.query(ApiClient).filter(ApiClient.api_key == api_key).first() + if api_client is not None and api_client.enabled: + return api_client diff --git a/backend/oasst_backend/api/v1/api.py b/backend/oasst_backend/api/v1/api.py index cd1119d6..b54f3dd0 100644 --- a/backend/oasst_backend/api/v1/api.py +++ b/backend/oasst_backend/api/v1/api.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from fastapi import APIRouter -from oasst_backend.api.v1 import tasks +from oasst_backend.api.v1 import tasks, text_labels api_router = APIRouter() api_router.include_router(tasks.router, prefix="/tasks", tags=["tasks"]) +api_router.include_router(text_labels.router, prefix="/text_labels", tags=["text_labels"]) diff --git a/backend/oasst_backend/api/v1/text_labels.py b/backend/oasst_backend/api/v1/text_labels.py new file mode 100644 index 00000000..09933304 --- /dev/null +++ b/backend/oasst_backend/api/v1/text_labels.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- +import pydantic +from fastapi import APIRouter, Depends, HTTPException +from fastapi.security.api_key import APIKey +from loguru import logger +from oasst_backend.api import deps +from oasst_backend.prompt_repository import PromptRepository +from oasst_shared.schemas import protocol as protocol_schema +from sqlmodel import Session +from starlette.status import HTTP_400_BAD_REQUEST + +router = APIRouter() + + +class LabelTextRequest(pydantic.BaseModel): + text_labels: protocol_schema.TextLabels + user: protocol_schema.User + + +@router.post("/") +def label_text( + *, + db: Session = Depends(deps.get_db), + api_key: APIKey = Depends(deps.get_api_key), + request: LabelTextRequest, +) -> None: + """ + Label a piece of text. + """ + api_client = deps.api_auth(api_key, db) + + try: + logger.info(f"Labeling text {request=}.") + pr = PromptRepository(db, api_client, user=request.user) + pr.store_text_labels(request.text_labels) + + except Exception: + logger.exception("Failed to store label.") + raise HTTPException( + status_code=HTTP_400_BAD_REQUEST, + ) diff --git a/backend/oasst_backend/config.py b/backend/oasst_backend/config.py index 0a1049f7..96d6021e 100644 --- a/backend/oasst_backend/config.py +++ b/backend/oasst_backend/config.py @@ -15,7 +15,8 @@ class Settings(BaseSettings): POSTGRES_DB: str = "postgres" DATABASE_URI: Optional[PostgresDsn] = None - ALLOW_ANY_API_KEY: bool = False + DEBUG_ALLOW_ANY_API_KEY: bool = False + DEBUG_SKIP_API_KEY_CHECK: bool = False @validator("DATABASE_URI", pre=True) def assemble_db_connection(cls, v: Optional[str], values: Dict[str, Any]) -> Any: diff --git a/backend/oasst_backend/models/__init__.py b/backend/oasst_backend/models/__init__.py index 414ec385..2a9b0c1f 100644 --- a/backend/oasst_backend/models/__init__.py +++ b/backend/oasst_backend/models/__init__.py @@ -4,6 +4,7 @@ from .person import Person from .person_stats import PersonStats from .post import Post from .post_reaction import PostReaction +from .text_labels import TextLabels from .work_package import WorkPackage __all__ = [ @@ -13,4 +14,5 @@ __all__ = [ "Post", "PostReaction", "WorkPackage", + "TextLabels", ] diff --git a/backend/oasst_backend/models/text_labels.py b/backend/oasst_backend/models/text_labels.py new file mode 100644 index 00000000..2699302f --- /dev/null +++ b/backend/oasst_backend/models/text_labels.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +from datetime import datetime +from typing import Optional +from uuid import UUID, uuid4 + +import sqlalchemy as sa +import sqlalchemy.dialects.postgresql as pg +from sqlmodel import Field, SQLModel + + +class TextLabels(SQLModel, table=True): + __tablename__ = "text_labels" + + id: Optional[UUID] = Field( + sa_column=sa.Column( + pg.UUID(as_uuid=True), primary_key=True, default=uuid4, server_default=sa.text("gen_random_uuid()") + ), + ) + created_date: Optional[datetime] = Field( + sa_column=sa.Column(sa.DateTime(), nullable=False, server_default=sa.func.current_timestamp()), + ) + api_client_id: UUID = Field(nullable=False, foreign_key="api_client.id") + text: str = Field(nullable=False, max_length=2**16) + post_id: Optional[UUID] = Field(sa_column=sa.Column(pg.UUID(as_uuid=True), sa.ForeignKey("post.id"), nullable=True)) + labels: dict[str, float] = Field(default={}, sa_column=sa.Column(pg.JSONB), nullable=False) diff --git a/backend/oasst_backend/prompt_repository.py b/backend/oasst_backend/prompt_repository.py index b0063cdf..6db42de1 100644 --- a/backend/oasst_backend/prompt_repository.py +++ b/backend/oasst_backend/prompt_repository.py @@ -5,7 +5,7 @@ from uuid import UUID, uuid4 import oasst_backend.models.db_payload as db_payload from loguru import logger -from oasst_backend.models import ApiClient, Person, Post, PostReaction, WorkPackage +from oasst_backend.models import ApiClient, Person, Post, PostReaction, TextLabels, WorkPackage from oasst_backend.models.payload_column_type import PayloadContainer from oasst_shared.schemas import protocol as protocol_schema from sqlmodel import Session @@ -314,3 +314,17 @@ class PromptRepository: self.db.commit() self.db.refresh(reaction) return reaction + + def store_text_labels(self, text_labels: protocol_schema.TextLabels) -> TextLabels: + model = TextLabels( + api_client_id=self.api_client.id, + text=text_labels.text, + labels=text_labels.labels, + ) + if text_labels.has_post_id: + self.fetch_post_by_frontend_post_id(text_labels.post_id, fail_if_missing=True) + model.post_id = text_labels.post_id + self.db.add(model) + self.db.commit() + self.db.refresh(model) + return model diff --git a/backend/requirements.txt b/backend/requirements.txt index b882d594..dd11aa18 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -5,6 +5,7 @@ numpy==1.22.4 psycopg2-binary==2.9.5 pydantic==1.9.1 python-dotenv==0.21.0 +scipy==1.8.1 SQLAlchemy==1.4.41 sqlmodel==0.0.8 starlette==0.22.0 diff --git a/bot/templates/teaser_assistant_reply.msg b/bot/templates/teaser_assistant_reply.msg deleted file mode 100644 index 6975d417..00000000 --- a/bot/templates/teaser_assistant_reply.msg +++ /dev/null @@ -1,3 +0,0 @@ -:robot: **Challenge: Assistant Reply** - -:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). \ No newline at end of file diff --git a/bot/templates/teaser_initial_prompt.msg b/bot/templates/teaser_initial_prompt.msg deleted file mode 100644 index e9ae5c7a..00000000 --- a/bot/templates/teaser_initial_prompt.msg +++ /dev/null @@ -1,3 +0,0 @@ -:microphone2: **Challenge: Initial Prompt** - -:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). \ No newline at end of file diff --git a/bot/.gitignore b/discord-bot/.gitignore similarity index 100% rename from bot/.gitignore rename to discord-bot/.gitignore diff --git a/bot/README.md b/discord-bot/README.md similarity index 100% rename from bot/README.md rename to discord-bot/README.md diff --git a/bot/__main__.py b/discord-bot/__main__.py similarity index 99% rename from bot/__main__.py rename to discord-bot/__main__.py index 0047bce7..9e5e29c7 100644 --- a/bot/__main__.py +++ b/discord-bot/__main__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- -from bot_settings import settings - from bot import OpenAssistantBot +from bot_settings import settings # invite bot url: https://discord.com/api/oauth2/authorize?client_id=1054078345542910022&permissions=1634235579456&scope=bot diff --git a/bot/api_client.py b/discord-bot/api_client.py similarity index 100% rename from bot/api_client.py rename to discord-bot/api_client.py diff --git a/bot/bot.py b/discord-bot/bot.py similarity index 100% rename from bot/bot.py rename to discord-bot/bot.py diff --git a/bot/bot_base.py b/discord-bot/bot_base.py similarity index 100% rename from bot/bot_base.py rename to discord-bot/bot_base.py diff --git a/bot/bot_settings.py b/discord-bot/bot_settings.py similarity index 100% rename from bot/bot_settings.py rename to discord-bot/bot_settings.py diff --git a/bot/channel_handlers.py b/discord-bot/channel_handlers.py similarity index 100% rename from bot/channel_handlers.py rename to discord-bot/channel_handlers.py diff --git a/bot/message_templates.py b/discord-bot/message_templates.py similarity index 100% rename from bot/message_templates.py rename to discord-bot/message_templates.py diff --git a/bot/requirements.txt b/discord-bot/requirements.txt similarity index 100% rename from bot/requirements.txt rename to discord-bot/requirements.txt diff --git a/bot/task_handlers.py b/discord-bot/task_handlers.py similarity index 100% rename from bot/task_handlers.py rename to discord-bot/task_handlers.py diff --git a/bot/templates/boot.msg b/discord-bot/templates/boot.msg similarity index 100% rename from bot/templates/boot.msg rename to discord-bot/templates/boot.msg diff --git a/bot/templates/help.msg b/discord-bot/templates/help.msg similarity index 87% rename from bot/templates/help.msg rename to discord-bot/templates/help.msg index ca033c47..3fbfb50d 100644 --- a/bot/templates/help.msg +++ b/discord-bot/templates/help.msg @@ -10,6 +10,6 @@ Commands for bot owners: `!sync` `!sync.guild` -`!sync.copy_global` +`!sync.copy_global` `!sync.clear_guild` -{% endif %} \ No newline at end of file +{% endif %} diff --git a/bot/templates/task_assistant_reply.msg b/discord-bot/templates/task_assistant_reply.msg similarity index 85% rename from bot/templates/task_assistant_reply.msg rename to discord-bot/templates/task_assistant_reply.msg index 3dfe84a3..b7b8abef 100644 --- a/bot/templates/task_assistant_reply.msg +++ b/discord-bot/templates/task_assistant_reply.msg @@ -9,4 +9,4 @@ Here is the conversation so far: **{{ message.text }}**" {% endif %} {% endfor %} -:robot: Assistant: { human, pls help me! ... } \ No newline at end of file +:robot: Assistant: { human, pls help me! ... } diff --git a/bot/templates/task_initial_prompt.msg b/discord-bot/templates/task_initial_prompt.msg similarity index 89% rename from bot/templates/task_initial_prompt.msg rename to discord-bot/templates/task_initial_prompt.msg index 47cf0f45..fae963c2 100644 --- a/bot/templates/task_initial_prompt.msg +++ b/discord-bot/templates/task_initial_prompt.msg @@ -1,4 +1,4 @@ Please provide an initial prompt to the assistant. {% if task.hint is not none %} Hint: {{task.hint}} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/bot/templates/task_rank_conversation_replies.msg b/discord-bot/templates/task_rank_conversation_replies.msg similarity index 91% rename from bot/templates/task_rank_conversation_replies.msg rename to discord-bot/templates/task_rank_conversation_replies.msg index c0c8bc80..c2864e9f 100644 --- a/bot/templates/task_rank_conversation_replies.msg +++ b/discord-bot/templates/task_rank_conversation_replies.msg @@ -10,4 +10,4 @@ Rank the following replies: {% for reply in task.replies %} {{loop.index}}: {{reply}}{% endfor %} -:scroll: Reply with the numbers of best to worst prompts separated by commas (example: "4,1,3,2"). \ No newline at end of file +:scroll: Reply with the numbers of best to worst prompts separated by commas (example: "4,1,3,2"). diff --git a/bot/templates/task_rank_initial_prompts.msg b/discord-bot/templates/task_rank_initial_prompts.msg similarity index 82% rename from bot/templates/task_rank_initial_prompts.msg rename to discord-bot/templates/task_rank_initial_prompts.msg index 5a75cbd1..3f84f24e 100644 --- a/bot/templates/task_rank_initial_prompts.msg +++ b/discord-bot/templates/task_rank_initial_prompts.msg @@ -2,4 +2,4 @@ Rank the following prompts: {% for prompt in task.prompts %} {{loop.index}}: {{prompt}}{% endfor %} -:scroll: Reply with the numbers of best to worst prompts separated by commas (example: "4,1,3,2"). \ No newline at end of file +:scroll: Reply with the numbers of best to worst prompts separated by commas (example: "4,1,3,2"). diff --git a/bot/templates/task_rate_summary.msg b/discord-bot/templates/task_rate_summary.msg similarity index 100% rename from bot/templates/task_rate_summary.msg rename to discord-bot/templates/task_rate_summary.msg diff --git a/bot/templates/task_summarize_story.msg b/discord-bot/templates/task_summarize_story.msg similarity index 100% rename from bot/templates/task_summarize_story.msg rename to discord-bot/templates/task_summarize_story.msg diff --git a/bot/templates/task_user_reply.msg b/discord-bot/templates/task_user_reply.msg similarity index 96% rename from bot/templates/task_user_reply.msg rename to discord-bot/templates/task_user_reply.msg index c247daa5..7c0a047b 100644 --- a/bot/templates/task_user_reply.msg +++ b/discord-bot/templates/task_user_reply.msg @@ -9,4 +9,4 @@ Here is the conversation so far: {% endif %}{% endfor %} {% if task.hint %} Hint: {{ task.hint }} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/discord-bot/templates/teaser_assistant_reply.msg b/discord-bot/templates/teaser_assistant_reply.msg new file mode 100644 index 00000000..f86baa3c --- /dev/null +++ b/discord-bot/templates/teaser_assistant_reply.msg @@ -0,0 +1,3 @@ +:robot: **Challenge: Assistant Reply** + +:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). diff --git a/discord-bot/templates/teaser_initial_prompt.msg b/discord-bot/templates/teaser_initial_prompt.msg new file mode 100644 index 00000000..7aad81b4 --- /dev/null +++ b/discord-bot/templates/teaser_initial_prompt.msg @@ -0,0 +1,3 @@ +:microphone2: **Challenge: Initial Prompt** + +:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). diff --git a/bot/templates/teaser_rank_conversation_replies.msg b/discord-bot/templates/teaser_rank_conversation_replies.msg similarity index 70% rename from bot/templates/teaser_rank_conversation_replies.msg rename to discord-bot/templates/teaser_rank_conversation_replies.msg index 744f7a76..366d86e5 100644 --- a/bot/templates/teaser_rank_conversation_replies.msg +++ b/discord-bot/templates/teaser_rank_conversation_replies.msg @@ -1,3 +1,3 @@ :bar_chart: **Challenge: Rank Replies** -:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). \ No newline at end of file +:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). diff --git a/bot/templates/teaser_rank_initial_prompts.msg b/discord-bot/templates/teaser_rank_initial_prompts.msg similarity index 71% rename from bot/templates/teaser_rank_initial_prompts.msg rename to discord-bot/templates/teaser_rank_initial_prompts.msg index 07399f56..09f04ffa 100644 --- a/bot/templates/teaser_rank_initial_prompts.msg +++ b/discord-bot/templates/teaser_rank_initial_prompts.msg @@ -1,3 +1,3 @@ :bar_chart: **Challenge: Rank Initial Prompts** -:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). \ No newline at end of file +:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). diff --git a/bot/templates/teaser_rate_summary.msg b/discord-bot/templates/teaser_rate_summary.msg similarity index 70% rename from bot/templates/teaser_rate_summary.msg rename to discord-bot/templates/teaser_rate_summary.msg index 41357b06..284aae26 100644 --- a/bot/templates/teaser_rate_summary.msg +++ b/discord-bot/templates/teaser_rate_summary.msg @@ -1,3 +1,3 @@ :ballot_box: **Challenge: Rate Summary** -:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). \ No newline at end of file +:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). diff --git a/bot/templates/teaser_summarize_story.msg b/discord-bot/templates/teaser_summarize_story.msg similarity index 70% rename from bot/templates/teaser_summarize_story.msg rename to discord-bot/templates/teaser_summarize_story.msg index 6e5ee5e5..52d46462 100644 --- a/bot/templates/teaser_summarize_story.msg +++ b/discord-bot/templates/teaser_summarize_story.msg @@ -1,3 +1,3 @@ :books: **Challenge: Summarize Story** -:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). \ No newline at end of file +:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). diff --git a/bot/templates/teaser_user_reply.msg b/discord-bot/templates/teaser_user_reply.msg similarity index 70% rename from bot/templates/teaser_user_reply.msg rename to discord-bot/templates/teaser_user_reply.msg index 47ec8a2d..31827252 100644 --- a/bot/templates/teaser_user_reply.msg +++ b/discord-bot/templates/teaser_user_reply.msg @@ -1,3 +1,3 @@ :person_red_hair: **Challenge: User Reply** -:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). \ No newline at end of file +:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}). diff --git a/bot/templates/welcome.msg b/discord-bot/templates/welcome.msg similarity index 100% rename from bot/templates/welcome.msg rename to discord-bot/templates/welcome.msg diff --git a/bot/utils.py b/discord-bot/utils.py similarity index 100% rename from bot/utils.py rename to discord-bot/utils.py diff --git a/oasst-shared/oasst_shared/schemas/protocol.py b/oasst-shared/oasst_shared/schemas/protocol.py index d5f508b6..17ee23f0 100644 --- a/oasst-shared/oasst_shared/schemas/protocol.py +++ b/oasst-shared/oasst_shared/schemas/protocol.py @@ -204,3 +204,51 @@ AnyInteraction = Union[ PostRating, PostRanking, ] + + +class TextLabel(str, enum.Enum): + """A label for a piece of text.""" + + spam = "spam" + violence = "violence" + sexual_content = "sexual_content" + toxicity = "toxicity" + political_content = "political_content" + humor = "humor" + sarcasm = "sarcasm" + hate_speech = "hate_speech" + profanity = "profanity" + ad_hominem = "ad_hominem" + insult = "insult" + threat = "threat" + aggressive = "aggressive" + misleading = "misleading" + helpful = "helpful" + formal = "formal" + cringe = "cringe" + creative = "creative" + beautiful = "beautiful" + informative = "informative" + based = "based" + slang = "slang" + + +class TextLabels(BaseModel): + """A set of labels for a piece of text.""" + + text: str + labels: dict[TextLabel, float] + post_id: str | None = None + + @property + def has_post_id(self) -> bool: + """Whether this TextLabels has a post_id.""" + return bool(self.post_id) + + # check that each label value is between 0 and 1 + @pydantic.validator("labels") + def check_label_values(cls, v): + for key, value in v.items(): + if not (0 <= value <= 1): + raise ValueError(f"Label values must be between 0 and 1, got {value} for {key}.") + return v diff --git a/scripts/backend-development/docker-compose.yaml b/scripts/backend-development/docker-compose.yaml index 2b92d6b0..65a65e73 100644 --- a/scripts/backend-development/docker-compose.yaml +++ b/scripts/backend-development/docker-compose.yaml @@ -9,6 +9,11 @@ services: environment: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 2s + timeout: 2s + retries: 10 adminer: image: adminer diff --git a/scripts/backend-development/run-local.sh b/scripts/backend-development/run-local.sh index e9df6ca2..90bd195d 100755 --- a/scripts/backend-development/run-local.sh +++ b/scripts/backend-development/run-local.sh @@ -4,7 +4,7 @@ parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P ) # switch to backend directory pushd "$parent_path/../../backend" -export ALLOW_ANY_API_KEY=True +export DEBUG_SKIP_API_KEY_CHECK=True uvicorn main:app --reload --port 8080 --host 0.0.0.0 diff --git a/scripts/endtoend-demo/docker-compose.yaml b/scripts/endtoend-demo/docker-compose.yaml index 8aa2b0fa..5e9ad9ac 100644 --- a/scripts/endtoend-demo/docker-compose.yaml +++ b/scripts/endtoend-demo/docker-compose.yaml @@ -3,69 +3,35 @@ version: "3.7" services: # This DB is for the FastAPI Backend. db: - image: postgres - restart: always - ports: - - 5432:5432 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 2s - timeout: 2s - retries: 10 + extends: + file: ../frontend-development/docker-compose.yaml + service: db # This DB is for Web Authentication and data caching. webdb: - image: postgres - restart: always - ports: - - 5433:5432 - environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 2s - timeout: 2s - retries: 10 + extends: + file: ../frontend-development/docker-compose.yaml + service: webdb # This lets you manually inspect the web and backend databases. adminer: - image: adminer - restart: always - ports: - - 8089:8080 + extends: + file: ../frontend-development/docker-compose.yaml + service: adminer # This fakes an SMTP email server used by website authentication. # User registration emails can be found by going to localhost:1080 and # opening the emails listed. maildev: - image: maildev/maildev - restart: always - environment: - - MAILDEV_WEB_PORT=1080 - - MAILDEV_SMTP_PORT=1025 - ports: - - "1080:1080" - - "1025:1025" + extends: + file: ../frontend-development/docker-compose.yaml + service: maildev # The oassist backend service. backend: - build: - dockerfile: docker/Dockerfile.backend - context: ../../ - image: oasst-backend - environment: - - POSTGRES_HOST=db - - ALLOW_ANY_API_KEY=True - - MAX_WORKERS=1 - depends_on: - db: - condition: service_healthy - ports: - - "8080:8080" + extends: + file: ../frontend-development/docker-compose.yaml + service: backend # The oassist web service. web: diff --git a/scripts/frontend-development/docker-compose.yaml b/scripts/frontend-development/docker-compose.yaml index 99f30f62..ef0f3489 100644 --- a/scripts/frontend-development/docker-compose.yaml +++ b/scripts/frontend-development/docker-compose.yaml @@ -6,11 +6,6 @@ services: extends: file: ../backend-development/docker-compose.yaml service: db - healthcheck: - test: ["CMD", "pg_isready", "-U", "postgres"] - interval: 2s - timeout: 2s - retries: 10 # This DB is for Web Authentication and data caching. webdb: @@ -32,6 +27,7 @@ services: extends: file: ../backend-development/docker-compose.yaml service: adminer + backend: build: dockerfile: docker/Dockerfile.backend @@ -39,7 +35,7 @@ services: image: oasst-backend environment: - POSTGRES_HOST=db - - ALLOW_ANY_API_KEY=True + - DEBUG_SKIP_API_KEY_CHECK=True - MAX_WORKERS=1 depends_on: db: diff --git a/scripts/postprocessing/infogain_selector.py b/scripts/postprocessing/infogain_selector.py new file mode 100644 index 00000000..51f60fa7 --- /dev/null +++ b/scripts/postprocessing/infogain_selector.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- +import numpy as np +from scipy import log2 +from scipy.integrate import nquad +from scipy.special import gammaln, psi +from scipy.stats import dirichlet + + +def make_range(*x): + """ + constructs leftover values for the simplex given the first k entries + (0,x_k) = 1-(x_1+...+x_(k-1)) + """ + return (0, max(0, 1 - sum(x))) + + +def relative_entropy(p, q): + """ + relative entropy of the two given dirichlet distributions + """ + + def tmp(*x): + """ + First adds the last always forced entry to the input (the last x_last = 1-(x_1+...+x_(N)) ) + Then computes the relative entropy of posterior and prior for that datapoint + """ + x_new = np.append(x, 1 - sum(x)) + return p(x_new) * log2(p(x_new) / q(x_new)) + + return tmp + + +def naive_monte_carlo_integral(fun, dim, samples=10_000_000): + s = np.random.rand(dim - 1, samples) + s = np.sort(np.concatenate((np.zeros((1, samples)), s, np.ones((1, samples)))), 0) + # print(s) + pos = np.diff(s, axis=0) + # print(pos) + res = fun(pos) + return np.mean(res) + + +def analytic_solution(a_post, a_prior): + """ + Analytic solution to the KL-divergence between two dirichlet distributions. + Proof is in the Notion design doc. + """ + post_sum = np.sum(a_post) + prior_sum = np.sum(a_prior) + info = ( + gammaln(post_sum) + - gammaln(prior_sum) + - np.sum(gammaln(a_post)) + + np.sum(gammaln(a_prior)) + - np.sum((a_post - a_prior) * (psi(a_post) - psi(post_sum))) + ) + + return info + + +def infogain(a_post, a_prior): + raise ( + """For the love of good don't use this: + it's insanely poorly conditioned, the worst numerical code I have ever written + and it's slow as molasses. Use the analytic solution instead. + + Maybe remove + """ + ) + args = len(a_prior) + p = dirichlet(a_post).pdf + q = dirichlet(a_prior).pdf + (info, _) = nquad(relative_entropy(p, q), [make_range for _ in range(args - 1)], opts={"epsabs": 1e-8}) + # info = naive_monte_carlo_integral(relative_entropy(p,q), len(a_post)) + return info + + +def uniform_expected_infogain(a_prior): + mean_weight = dirichlet.mean(a_prior) + print("weight", mean_weight) + results = [] + for i, w in enumerate(mean_weight): + a_post = a_prior.copy() + a_post[i] = a_post[i] + 1 + results.append(w * analytic_solution(a_post, a_prior)) + return np.sum(results) + + +if __name__ == "__main__": + a_prior = np.array([1, 1, 9, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + a_post = np.array([1, 1, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + + print("algebraic", analytic_solution(a_post, a_prior)) + # print("raw",infogain(a_post, a_prior)) + print("large infogain", uniform_expected_infogain(a_prior)) + print("post infogain", uniform_expected_infogain(a_post)) + # a_prior = np.array([1,1,1000]) + # print("small infogain",uniform_expected_infogain(a_prior)) diff --git a/scripts/postprocessing/scoring.py b/scripts/postprocessing/scoring.py new file mode 100644 index 00000000..3c145b28 --- /dev/null +++ b/scripts/postprocessing/scoring.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +from dataclasses import dataclass, replace +from typing import Any + +import numpy as np +import numpy.typing as npt +from scipy.stats import kendalltau + + +@dataclass +class Voter: + """ + Represents a single voter. + This tabulates the number of good votes, total votes, + and points. + We only put well-behaved people on the scoreboard and filter out the badly behaved ones + """ + + uid: Any + num_votes: int + num_good_votes: int + num_prompts: int + num_good_prompts: int + num_rankings: int + num_good_rankings: int + + ##################### + voting_points: int + prompt_points: int + ranking_points: int + + def voter_quality(self): + return self.num_good_votes / self.num_votes + + def rank_quality(self): + return self.num_good_rankings / self.num_rankings + + def prompt_quality(self): + return self.num_good_prompts / self.num_prompts + + def is_well_behaved(self, threshhold_vote, threshhold_prompt, threshhold_rank): + return ( + self.voter_quality() > threshhold_vote + and self.prompt_quality() > threshhold_prompt + and self.rank_quality() > threshhold_rank + ) + + def total_points(self, voting_weight, prompt_weight, ranking_weight): + return ( + voting_weight * self.voting_points + + prompt_weight * self.prompt_points + + ranking_weight * self.ranking_points + ) + + +def score_update_votes(new_vote: int, consensus: npt.ArrayLike, voter_data: Voter) -> Voter: + """ + This function returns the new "quality score" and points for a voter, + after that voter cast a vote on a question. + + This function is only to be run when archiving a question + i.e. the question has had sufficiently many votes, or we cann't get more than "K" bits of information + + The consensus is the array of all votes cast by all voters for that question + We then update the voter data using the new information + + Parameters: + new_vote (int): the index of the vote cast by the voter + consensus (ArrayLike): all votes cast for this question + voter_data (Voter): a "Voter" object that represents the person casting the "new_vote" + + Returns: + updated_voter (Voter): the new "quality score" and points for the voter + """ + # produces the ranking of votes, e.g. for [100,300,200] it returns [0, 2, 1], + # since 100 is the lowest, 300 the highest and 200 the middle value + consensus_ranking = np.argsort(np.argsort(consensus)) + new_points = consensus_ranking[new_vote] + voter_data.voting_points + + # we need to correct for 0 indexing, if you are closer to "right" than "wrong" of the conensus, + # it's a good vote + new_good_votes = int(consensus_ranking[new_vote] > (len(consensus) - 1) / 2) + voter_data.num_good_votes + new_num_votes = voter_data.num_votes + 1 + return replace(voter_data, num_votes=new_num_votes, num_good_votes=new_good_votes, voting_points=new_points) + + +def score_update_prompts(consensus: npt.ArrayLike, voter_data: Voter) -> Voter: + """ + This function returns the gain of points for a given prompt's votes + + This function is only to be run when archiving a question + i.e. the question has had sufficiently many votes, or we cann't get more than "K" bits of information + + Parameters: + consensus (ArrayLike): all votes cast for this question + voter_data (Voter): a "Voter" object that represents the person that wrote the prompt + + Returns: + updated_voter (Voter): the new "quality score" and points for the voter + """ + # produces the ranking of votes, e.g. for [100,300,200] it returns [0, 2, 1], + # since 100 is the lowest, 300 the highest and 200 the middle value + consensus_ranking = np.arange(len(consensus)) - len(consensus) // 2 + 1 + delta_votes = np.sum(consensus_ranking * consensus) + new_points = delta_votes + voter_data.prompt_points + + # we need to correct for 0 indexing, if you are closer to "right" than "wrong" of the conensus, + # it's a good vote + new_good_prompts = int(delta_votes > 0) + voter_data.num_good_prompts + new_num_prompts = voter_data.num_prompts + 1 + return replace( + voter_data, + num_prompts=new_num_prompts, + num_good_prompts=new_good_prompts, + prompt_points=new_points, + ) + + +def score_update_ranking(user_ranking: npt.ArrayLike, consensus_ranking: npt.ArrayLike, voter_data: Voter) -> Voter: + """ + This function returns the gain of points for a given ranking's votes + + This function is only to be run when archiving a question + i.e. the question has had sufficiently many votes, or we cann't get more than "K" bits of information + + we use the bubble-sort distance (or "kendall-tau" distance) to compare the two rankings + we use this over spearman correlation since: + "[Kendall's τ] approaches a normal distribution more rapidly than ρ, as N, the sample size, increases; + and τ is also more tractable mathematically, particularly when ties are present" + Gilpin, A. R. (1993). Table for conversion of Kendall's Tau to Spearman's + Rho within the context measures of magnitude of effect for meta-analysis + + Further in + "research design and statistical analyses, second edition, 2003" + the authors note that at least from an significance test POV they will yield the same p-values + + Parameters: + user_ranking (ArrayLike): ranking produced by the user + consensus (ArrayLike): ranking produced after running the voting algorithm to merge into the consensus ranking + voter_data (Voter): a "Voter" object that represents the person that wrote the prompt + + Returns: + updated_voter (Voter): the new "quality score" and points for the voter + """ + bubble_sort_distance, p_value = kendalltau(user_ranking, consensus_ranking) + # normalize kendall-tau from [-1,1] into [0,1] range + bubble_sort_distance = (1 + bubble_sort_distance) / 2 + new_points = bubble_sort_distance + voter_data.ranking_points + new_good_rankings = int(bubble_sort_distance > 0.5) + voter_data.num_good_rankings + new_num_rankings = voter_data.num_rankings + 1 + return replace( + voter_data, + num_rankings=new_num_rankings, + num_good_rankings=new_good_rankings, + ranking_points=new_points, + ) + + +if __name__ == "__main__": + demo_voter = Voter( + "abc", + num_votes=10, + num_good_votes=2, + num_prompts=10, + num_good_prompts=2, + num_rankings=10, + num_good_rankings=2, + voting_points=6, + prompt_points=0, + ranking_points=0, + ) + new_vote = 3 + consensus = np.array([200, 300, 100, 500]) + print(demo_voter) + print("best vote ", score_update_votes(new_vote, consensus, demo_voter)) + new_vote = 2 + print("worst vote ", score_update_votes(new_vote, consensus, demo_voter)) + new_vote = 1 + print("medium vote ", score_update_votes(new_vote, consensus, demo_voter)) + print("prompt writer", score_update_prompts(consensus, demo_voter)) + print("best rank ", score_update_ranking(np.array([0, 2, 1]), np.array([0, 2, 1]), demo_voter)) + print("medium rank ", score_update_ranking(np.array([2, 0, 1]), np.array([0, 2, 1]), demo_voter)) + print("worst rank ", score_update_ranking(np.array([1, 0, 2]), np.array([0, 2, 1]), demo_voter)) diff --git a/website/package-lock.json b/website/package-lock.json index f4f6d839..45063104 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -14,6 +14,7 @@ "@headlessui/react": "^1.7.7", "@heroicons/react": "^2.0.13", "@next-auth/prisma-adapter": "^1.0.5", + "@next/font": "^13.1.0", "@prisma/client": "^4.7.1", "@tailwindcss/forms": "^0.5.3", "autoprefixer": "^10.4.13", @@ -1961,6 +1962,11 @@ "glob": "7.1.7" } }, + "node_modules/@next/font": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@next/font/-/font-13.1.0.tgz", + "integrity": "sha512-9+c2eWoeLftcGAul1fiXD8lL4o4/0beQrz2/0h0B0VV5AWrqCCfj/204quUxdp541ab+NCWVX/m49qjbqFMaFA==" + }, "node_modules/@next/swc-android-arm-eabi": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.6.tgz", @@ -7769,6 +7775,11 @@ "glob": "7.1.7" } }, + "@next/font": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@next/font/-/font-13.1.0.tgz", + "integrity": "sha512-9+c2eWoeLftcGAul1fiXD8lL4o4/0beQrz2/0h0B0VV5AWrqCCfj/204quUxdp541ab+NCWVX/m49qjbqFMaFA==" + }, "@next/swc-android-arm-eabi": { "version": "13.0.6", "resolved": "https://registry.npmjs.org/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-13.0.6.tgz", diff --git a/website/package.json b/website/package.json index 10708782..bfec87c7 100644 --- a/website/package.json +++ b/website/package.json @@ -16,6 +16,7 @@ "@headlessui/react": "^1.7.7", "@heroicons/react": "^2.0.13", "@next-auth/prisma-adapter": "^1.0.5", + "@next/font": "^13.1.0", "@prisma/client": "^4.7.1", "@tailwindcss/forms": "^0.5.3", "autoprefixer": "^10.4.13", diff --git a/website/public/images/logos/CHAT-THOUGHT-CONVO.svg b/website/public/images/logos/CHAT-THOUGHT-CONVO.svg deleted file mode 100644 index 3c14ab57..00000000 --- a/website/public/images/logos/CHAT-THOUGHT-CONVO.svg +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/website/public/images/logos/CHAT-THOUGHT-LOGO.svg b/website/public/images/logos/CHAT-THOUGHT-LOGO.svg deleted file mode 100644 index 7657e0a4..00000000 --- a/website/public/images/logos/CHAT-THOUGHT-LOGO.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/website/public/images/logos/logo.png b/website/public/images/logos/logo.png new file mode 100644 index 00000000..34cdf878 Binary files /dev/null and b/website/public/images/logos/logo.png differ diff --git a/website/public/images/logos/logo.svg b/website/public/images/logos/logo.svg new file mode 100644 index 00000000..8cc9a708 --- /dev/null +++ b/website/public/images/logos/logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/public/images/logos/logo_mono.png b/website/public/images/logos/logo_mono.png new file mode 100644 index 00000000..2b7cd775 Binary files /dev/null and b/website/public/images/logos/logo_mono.png differ diff --git a/website/public/images/logos/logo_mono.svg b/website/public/images/logos/logo_mono.svg new file mode 100644 index 00000000..ef272256 --- /dev/null +++ b/website/public/images/logos/logo_mono.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/website/public/images/temp-avatars/av1.jpg b/website/public/images/temp-avatars/av1.jpg new file mode 100644 index 00000000..0c76c2b7 Binary files /dev/null and b/website/public/images/temp-avatars/av1.jpg differ diff --git a/website/public/images/temp-avatars/av2.jpg b/website/public/images/temp-avatars/av2.jpg new file mode 100644 index 00000000..9eb06f60 Binary files /dev/null and b/website/public/images/temp-avatars/av2.jpg differ diff --git a/website/public/images/temp-avatars/av3.jpg b/website/public/images/temp-avatars/av3.jpg new file mode 100644 index 00000000..402d485f Binary files /dev/null and b/website/public/images/temp-avatars/av3.jpg differ diff --git a/website/public/images/temp-avatars/av4.jpg b/website/public/images/temp-avatars/av4.jpg new file mode 100644 index 00000000..286997af Binary files /dev/null and b/website/public/images/temp-avatars/av4.jpg differ diff --git a/website/public/images/temp-avatars/av5.jpg b/website/public/images/temp-avatars/av5.jpg new file mode 100644 index 00000000..c017c767 Binary files /dev/null and b/website/public/images/temp-avatars/av5.jpg differ diff --git a/website/src/components/AuthLayout.tsx b/website/src/components/AuthLayout.tsx index 428ce6ea..04110253 100644 --- a/website/src/components/AuthLayout.tsx +++ b/website/src/components/AuthLayout.tsx @@ -1,33 +1,14 @@ import Link from "next/link"; +import Image from "next/image"; -function BackgroundIllustration(props) { +export function AuthLayout({ children }) { return ( - - ); -} - -export function AuthLayout({ title, subtitle, children }) { - return ( -
-
- - {/* logo */} +
+
+ + Open Assistant Logo Open Assistant -
- -

{title}

- {subtitle &&

{subtitle}

} -
-
+
{children}
diff --git a/website/src/components/Avatar.tsx b/website/src/components/Avatar.tsx new file mode 100644 index 00000000..26bdec0b --- /dev/null +++ b/website/src/components/Avatar.tsx @@ -0,0 +1,96 @@ +import React from "react"; +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"; + +export function Avatar() { + const { data: session } = useSession(); + + if (!session) { + return <>; + } + if (session && session.user) { + const email = session.user.email; + const accountOptions = [ + { + name: "Account Settings", + href: "#", + desc: "Account Settings", + icon: FaCog, + //For future use + }, + ]; + return ( + + {({ open }) => ( + <> + +
+ Profile Picture +

{email}

+ {/* Will be changed to username once it is implemented */} +
+
+ + {open && ( + <> + +
+ {accountOptions.map((item) => ( + +
+
+
+

{item.name}

+
+
+ ))} + signOut()} + > +
+ +
+
+

Sign Out

+
+
+
+
+ + )} +
+ + )} +
+ ); + } +} + +export default Avatar; diff --git a/website/src/components/CallToAction.tsx b/website/src/components/CallToAction.tsx index 35949645..2fd91c79 100644 --- a/website/src/components/CallToAction.tsx +++ b/website/src/components/CallToAction.tsx @@ -12,7 +12,7 @@ export function CallToAction() {

Join Us

All open source projects begin with people like you. Open source is the belief that if we collaborate we can - together gift our knoweledge and technology to the world for the benefit of humanity. Are you in? Find us + together gift our knowledge and technology to the world for the benefit of humanity. Are you in? Find us here:

@@ -27,7 +27,7 @@ export function CallToAction() { d="M524.531,69.836a1.5,1.5,0,0,0-.764-.7A485.065,485.065,0,0,0,404.081,32.03a1.816,1.816,0,0,0-1.923.91,337.461,337.461,0,0,0-14.9,30.6,447.848,447.848,0,0,0-134.426,0,309.541,309.541,0,0,0-15.135-30.6,1.89,1.89,0,0,0-1.924-.91A483.689,483.689,0,0,0,116.085,69.137a1.712,1.712,0,0,0-.788.676C39.068,183.651,18.186,294.69,28.43,404.354a2.016,2.016,0,0,0,.765,1.375A487.666,487.666,0,0,0,176.02,479.918a1.9,1.9,0,0,0,2.063-.676A348.2,348.2,0,0,0,208.12,430.4a1.86,1.86,0,0,0-1.019-2.588,321.173,321.173,0,0,1-45.868-21.853,1.885,1.885,0,0,1-.185-3.126c3.082-2.309,6.166-4.711,9.109-7.137a1.819,1.819,0,0,1,1.9-.256c96.229,43.917,200.41,43.917,295.5,0a1.812,1.812,0,0,1,1.924.233c2.944,2.426,6.027,4.851,9.132,7.16a1.884,1.884,0,0,1-.162,3.126,301.407,301.407,0,0,1-45.89,21.83,1.875,1.875,0,0,0-1,2.611,391.055,391.055,0,0,0,30.014,48.815,1.864,1.864,0,0,0,2.063.7A486.048,486.048,0,0,0,610.7,405.729a1.882,1.882,0,0,0,.765-1.352C623.729,277.594,590.933,167.465,524.531,69.836ZM222.491,337.58c-28.972,0-52.844-26.587-52.844-59.239S193.056,219.1,222.491,219.1c29.665,0,53.306,26.82,52.843,59.239C275.334,310.993,251.924,337.58,222.491,337.58Zm195.38,0c-28.971,0-52.843-26.587-52.843-59.239S388.437,219.1,417.871,219.1c29.667,0,53.307,26.82,52.844,59.239C470.715,310.993,447.538,337.58,417.871,337.58Z" /> - DISCORD + Discord diff --git a/website/src/components/Footer.tsx b/website/src/components/Footer.tsx index fa3d8c0d..5e6ac47d 100644 --- a/website/src/components/Footer.tsx +++ b/website/src/components/Footer.tsx @@ -12,13 +12,7 @@ export function Footer() {
- logo + logo
diff --git a/website/src/components/Header.tsx b/website/src/components/Header.tsx index e2cd7e17..c47982a1 100644 --- a/website/src/components/Header.tsx +++ b/website/src/components/Header.tsx @@ -4,7 +4,9 @@ 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 { Avatar } from "./Avatar"; import { Container } from "./Container"; import { NavLinks } from "./NavLinks"; @@ -40,15 +42,13 @@ function MobileNavLink({ children, ...props }) { function AccountButton() { const { data: session } = useSession(); if (session) { - return ( - - ); + return; } return ( - - + + ); } @@ -57,28 +57,22 @@ export function Header() { return (
diff --git a/website/src/components/Hero.tsx b/website/src/components/Hero.tsx index 0d74fb84..4f6bf4cb 100644 --- a/website/src/components/Hero.tsx +++ b/website/src/components/Hero.tsx @@ -71,7 +71,7 @@ export function Hero() {
+ + Open Assistant + + +
+
+ {"Sorry, the page you're looking for does not exist."} +
+