Merge branch 'main' of https://github.com/LAION-AI/Open-Assistant
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.local.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
|
||||
@@ -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.
|
||||
|
||||
@@ -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 ###
|
||||
@@ -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
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
@@ -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:
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
:robot: **Challenge: Assistant Reply**
|
||||
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -1,3 +0,0 @@
|
||||
:microphone2: **Challenge: Initial Prompt**
|
||||
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -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
|
||||
|
||||
@@ -10,6 +10,6 @@ Commands for bot owners:
|
||||
|
||||
`!sync`
|
||||
`!sync.guild`
|
||||
`!sync.copy_global`
|
||||
`!sync.copy_global`
|
||||
`!sync.clear_guild`
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -9,4 +9,4 @@ Here is the conversation so far:
|
||||
**{{ message.text }}**"
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
:robot: Assistant: { human, pls help me! ... }
|
||||
:robot: Assistant: { human, pls help me! ... }
|
||||
@@ -1,4 +1,4 @@
|
||||
Please provide an initial prompt to the assistant.
|
||||
{% if task.hint is not none %}
|
||||
Hint: {{task.hint}}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -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").
|
||||
:scroll: Reply with the numbers of best to worst prompts separated by commas (example: "4,1,3,2").
|
||||
@@ -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").
|
||||
:scroll: Reply with the numbers of best to worst prompts separated by commas (example: "4,1,3,2").
|
||||
@@ -9,4 +9,4 @@ Here is the conversation so far:
|
||||
{% endif %}{% endfor %}
|
||||
{% if task.hint %}
|
||||
Hint: {{ task.hint }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
@@ -0,0 +1,3 @@
|
||||
:robot: **Challenge: Assistant Reply**
|
||||
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -0,0 +1,3 @@
|
||||
:microphone2: **Challenge: Initial Prompt**
|
||||
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -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 }}).
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -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 }}).
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -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 }}).
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -1,3 +1,3 @@
|
||||
:books: **Challenge: Summarize Story**
|
||||
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -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 }}).
|
||||
:point_down: Work on it here (:fire: Thread will self-destruct at {{ expiry_time }}, {{ expiry_relative }}).
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
@@ -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))
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 715.62 494.67">
|
||||
<defs>
|
||||
<radialGradient id="radial-gradient" cx="375.98" cy="-12.21" fx="375.98" fy="-12.21" r="497.2" gradientTransform="translate(0 496) scale(1 -1)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#fc00ff"/>
|
||||
<stop offset="0" stop-color="#d327fe"/>
|
||||
<stop offset="0" stop-color="#af4afc"/>
|
||||
<stop offset="0" stop-color="#8f69fb"/>
|
||||
<stop offset="0" stop-color="#7384fa"/>
|
||||
<stop offset="0" stop-color="#5d99f9"/>
|
||||
<stop offset="0" stop-color="#4ca9f9"/>
|
||||
<stop offset="0" stop-color="#41b5f8"/>
|
||||
<stop offset="0" stop-color="#3abbf8"/>
|
||||
<stop offset="0" stop-color="#38bdf8"/>
|
||||
<stop offset=".07" stop-color="#31a6f9"/>
|
||||
<stop offset=".24" stop-color="#2274fb"/>
|
||||
<stop offset=".41" stop-color="#164afc"/>
|
||||
<stop offset=".57" stop-color="#0c2afd"/>
|
||||
<stop offset=".73" stop-color="#0613fe"/>
|
||||
<stop offset=".87" stop-color="#0105ff"/>
|
||||
<stop offset="1" stop-color="blue"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="radial-gradient-2" cx="-32.67" cy="158.04" fx="-32.67" fy="158.04" r="497.2" gradientTransform="translate(340.29 396.4) rotate(-39.31) scale(1 -1.17)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#fc00ff"/>
|
||||
<stop offset="0" stop-color="#fa00ff"/>
|
||||
<stop offset="0" stop-color="#f800ff"/>
|
||||
<stop offset="0" stop-color="#38bdf8"/>
|
||||
<stop offset=".11" stop-color="#2f9ef9"/>
|
||||
<stop offset=".34" stop-color="#1e66fb"/>
|
||||
<stop offset=".55" stop-color="#113afd"/>
|
||||
<stop offset=".74" stop-color="#081afe"/>
|
||||
<stop offset=".89" stop-color="#0207ff"/>
|
||||
<stop offset="1" stop-color="blue"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="linear-gradient" x1="159.14" y1="78.07" x2="347.74" y2="266.67" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#4ba2ea"/>
|
||||
<stop offset="1" stop-color="#235ea5"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g>
|
||||
<path d="m641.06,74.17h-89.45c-8.23,0-14.91,5.54-14.91,12.37s6.68,12.37,14.91,12.37h89.45c24.66,0,44.73,16.65,44.73,37.1v222.6c0,20.45-20.07,37.1-44.73,37.1h-59.63c-8.23,0-14.91,5.54-14.91,12.37v49.47l-95.41-59.36c-2.56-1.61-5.72-2.47-8.94-2.47h-178.9c-9.24,0-18.22-2.42-25.97-7.02-6.62-3.98-15.98-2.72-20.78,2.84-4.8,5.57-3.28,13.28,3.43,17.26,12.82,7.64,27.79,11.67,43.32,11.67h173.92l115.3,71.73c2.62,1.63,5.78,2.47,8.95,2.47,2.27,0,4.56-.45,6.68-1.34,5.04-2.1,8.23-6.38,8.23-11.06v-61.83h44.73c41.12,0,74.54-27.75,74.54-61.83v-222.6c-.02-34.08-33.44-61.84-74.56-61.84Z" style="fill: url(#radial-gradient);"/>
|
||||
<path d="m432.34,346.27c41.12,0,74.54-27.75,74.54-61.83V61.83c0-34.08-33.42-61.83-74.54-61.83H74.54C33.42,0,0,27.75,0,61.83v222.6c0,34.08,33.42,61.83,74.54,61.83h44.73v61.83c0,4.67,3.19,8.95,8.23,11.06,2.12.87,4.41,1.31,6.68,1.31,3.16,0,6.32-.84,8.95-2.47l115.3-71.73h173.91Zm-187.84-22.25l-95.41,59.36v-49.47c0-6.83-6.68-12.37-14.91-12.37h-59.64c-24.66,0-44.73-16.65-44.73-37.1V61.83c0-20.45,20.07-37.1,44.73-37.1h357.8c24.66,0,44.73,16.65,44.73,37.1v222.6c0,20.45-20.07,37.1-44.73,37.1h-178.9c-3.25,0-6.38.87-8.94,2.48Z" style="fill: url(#radial-gradient-2);"/>
|
||||
</g>
|
||||
<circle cx="253.44" cy="172.37" r="133.36" style="fill: url(#linear-gradient);"/>
|
||||
<circle cx="193.83" cy="172.37" r="19.89" style="fill: #fff;"/>
|
||||
<circle cx="253.44" cy="172.37" r="19.89" style="fill: #fff;"/>
|
||||
<circle cx="313.05" cy="172.37" r="19.89" style="fill: #fff;"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,13 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 266.72 266.72">
|
||||
<defs>
|
||||
<linearGradient id="linear-gradient" x1="39.06" y1="39.06" x2="227.66" y2="227.66" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#4ba2ea"/>
|
||||
<stop offset="1" stop-color="#235ea5"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="133.36" cy="133.36" r="133.36" style="fill: url(#linear-gradient);"/>
|
||||
<circle cx="73.75" cy="133.36" r="19.89" style="fill: #fff;"/>
|
||||
<circle cx="133.36" cy="133.36" r="19.89" style="fill: #fff;"/>
|
||||
<circle cx="192.96" cy="133.36" r="19.89" style="fill: #fff;"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 724 B |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="374.17" x2="170.64" y1="-112.67" y2="463" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#16bbf4" offset="0"/>
|
||||
<stop stop-color="#165ff2" offset=".99"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b" x1="488.28" x2="474.29" y1="112.58" y2="556.15" xlink:href="#a"/>
|
||||
<linearGradient id="linearGradient206" x1="374.17" x2="170.64" y1="-112.67" y2="463" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
</defs>
|
||||
<g transform="matrix(.5796 0 0 .5796 66.717 93.438)">
|
||||
<g>
|
||||
<path d="m205.08 399.31h292.41a30 30 0 0 0 30-30v-339.31a30 30 0 0 0-30-30h-467.49a30 30 0 0 0-30 30v339.31a30 30 0 0 0 30 30h42a10 10 0 0 1 10 10v84.85a10 10 0 0 0 10.07 10 9.83 9.83 0 0 0 7-2.95l99-99a10 10 0 0 1 7.01-2.9z" fill="url(#linearGradient206)" style="isolation:isolate"/>
|
||||
<g fill="#ffffff">
|
||||
<path d="m160.43 213c-32.24-20-38.9-71.83-10.42-97.83 18.42-7.6 32.4 12.85 36.62 28.25 10.32 17.45 12.59 41-3.16 56.08a42.81 42.81 0 0 1-23.04 13.5z" style="isolation:isolate"/>
|
||||
<path d="m348.22 213.86c-21.73-15.31-45.37-29.75-71.77-35.15-33.1-4.41-70.73 5.36-91.7 32.87-14.83 14.32-18.34 36.94-5.49 53.76 8.52 19.48 5.59 45.78 28.23 56.94 16 15.83 40 1.27 56.32 14.21a7.6 7.6 0 0 0 5.59-5.05c-4.25-31.33 29.21-16.95 45.66-14.61 19.77-11.71 25.43-36.14 34.75-55.58 12.55-13.83 15-35.25-1.59-47.39z" style="isolation:isolate"/>
|
||||
<path d="m367 118.1c-21.87 2.52-29.89 28.17-40.34 44.42-10.67 20.94 12.26 38.77 28.48 47.89a19.63 19.63 0 0 0 13-1.07c18.86-10.12 26.86-33.43 27.34-53.79 0.24-16.78-8.3-38.93-28.48-37.45z" style="isolation:isolate"/>
|
||||
<path d="m218.7 176c-24-14.47-25.38-45.76-27.32-70.65-0.38-24 35.23-45.5 49.43-20.14 9.8 20.9 21.47 45.47 12.47 68.66-5.68 13.77-20.93 19.73-34.58 22.13z" style="isolation:isolate"/>
|
||||
<path d="m306.18 175.87c-28.48 0.84-43.29-32.4-35.93-56.83 0.17-19.58 7.31-53.56 33.53-48.18 28.29 10.94 34.3 49.46 20.82 74.07-6.77 10-6.2 25.11-18.42 30.94z" style="isolation:isolate"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m633.15 225.66h-80.66a10 10 0 0 0-10 10v133.65a45 45 0 0 1-45 45h-185.19a10 10 0 0 0-10 10v47a20 20 0 0 0 19.95 20h194.47a6.65 6.65 0 0 1 4.7 1.95l65.83 65.74a6.65 6.65 0 0 0 11.35-4.7v-56.43a6.65 6.65 0 0 1 6.65-6.65h27.9a20 20 0 0 0 20-20v-225.61a20 20 0 0 0-20-19.95z" fill="url(#b)" style="isolation:isolate"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 6.6 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="512" height="512" version="1.1" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g>
|
||||
<g>
|
||||
<path d="m185.58 324.88h169.48a17.388 17.388 0 0 0 17.388-17.388v-196.66a17.388 17.388 0 0 0-17.388-17.388h-270.96a17.388 17.388 0 0 0-17.388 17.388v196.66a17.388 17.388 0 0 0 17.388 17.388h24.343a5.796 5.796 0 0 1 5.796 5.796v49.179a5.796 5.796 0 0 0 5.8366 5.796 5.6975 5.6975 0 0 0 4.0572-1.7098l57.38-57.38a5.796 5.796 0 0 1 4.063-1.6808z" fill="#000000" stroke-width=".5796" style="isolation:isolate"/>
|
||||
<g transform="matrix(.5796 0 0 .5796 66.717 93.438)" fill="#ffffff">
|
||||
<path d="m160.43 213c-32.24-20-38.9-71.83-10.42-97.83 18.42-7.6 32.4 12.85 36.62 28.25 10.32 17.45 12.59 41-3.16 56.08a42.81 42.81 0 0 1-23.04 13.5z" style="isolation:isolate"/>
|
||||
<path d="m348.22 213.86c-21.73-15.31-45.37-29.75-71.77-35.15-33.1-4.41-70.73 5.36-91.7 32.87-14.83 14.32-18.34 36.94-5.49 53.76 8.52 19.48 5.59 45.78 28.23 56.94 16 15.83 40 1.27 56.32 14.21a7.6 7.6 0 0 0 5.59-5.05c-4.25-31.33 29.21-16.95 45.66-14.61 19.77-11.71 25.43-36.14 34.75-55.58 12.55-13.83 15-35.25-1.59-47.39z" style="isolation:isolate"/>
|
||||
<path d="m367 118.1c-21.87 2.52-29.89 28.17-40.34 44.42-10.67 20.94 12.26 38.77 28.48 47.89a19.63 19.63 0 0 0 13-1.07c18.86-10.12 26.86-33.43 27.34-53.79 0.24-16.78-8.3-38.93-28.48-37.45z" style="isolation:isolate"/>
|
||||
<path d="m218.7 176c-24-14.47-25.38-45.76-27.32-70.65-0.38-24 35.23-45.5 49.43-20.14 9.8 20.9 21.47 45.47 12.47 68.66-5.68 13.77-20.93 19.73-34.58 22.13z" style="isolation:isolate"/>
|
||||
<path d="m306.18 175.87c-28.48 0.84-43.29-32.4-35.93-56.83 0.17-19.58 7.31-53.56 33.53-48.18 28.29 10.94 34.3 49.46 20.82 74.07-6.77 10-6.2 25.11-18.42 30.94z" style="isolation:isolate"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="m433.69 224.23h-46.751a5.796 5.796 0 0 0-5.796 5.796v77.464a26.082 26.082 0 0 1-26.082 26.082h-107.34a5.796 5.796 0 0 0-5.796 5.796v27.241a11.592 11.592 0 0 0 11.563 11.592h112.71a3.8543 3.8543 0 0 1 2.7241 1.1302l38.155 38.103a3.8543 3.8543 0 0 0 6.5785-2.7241v-32.707a3.8543 3.8543 0 0 1 3.8543-3.8543h16.171a11.592 11.592 0 0 0 11.592-11.592v-130.76a11.592 11.592 0 0 0-11.592-11.563z" fill="#000000" stroke-width=".5796" style="isolation:isolate"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 8.1 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
@@ -1,33 +1,14 @@
|
||||
import Link from "next/link";
|
||||
import Image from "next/image";
|
||||
|
||||
function BackgroundIllustration(props) {
|
||||
export function AuthLayout({ children }) {
|
||||
return (
|
||||
<svg viewBox="0 0 1090 1090" aria-hidden="true" fill="none" preserveAspectRatio="none" {...props}>
|
||||
<circle cx={545} cy={545} r="544.5" />
|
||||
<circle cx={545} cy={545} r="480.5" />
|
||||
<circle cx={545} cy={545} r="416.5" />
|
||||
<circle cx={545} cy={545} r="352.5" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function AuthLayout({ title, subtitle, children }) {
|
||||
return (
|
||||
<main className="flex min-h-full overflow-hidden pt-16 sm:py-28">
|
||||
<div className="mx-auto flex w-full max-w-2xl flex-col px-4 sm:px-6">
|
||||
<Link href="/" aria-label="Home">
|
||||
{/* logo */}
|
||||
<main className="flex items-center justify-center min-h-full overflow-hidden pt-16 sm:py-28 subpixel-antialiased">
|
||||
<div className="flex items-center w-full max-w-2xl flex-col px-4 sm:px-6">
|
||||
<Link href="/" aria-label="Home" className="flex items-center text-3xl font-bold text-black">
|
||||
<Image src="/images/logos/logo.svg" width="100" height="100" alt="Open Assistant Logo" /> Open Assistant
|
||||
</Link>
|
||||
<div className="relative mt-12 sm:mt-16">
|
||||
<BackgroundIllustration
|
||||
width="1090"
|
||||
height="1090"
|
||||
className="absolute -top-7 left-1/2 -z-10 h-[788px] -translate-x-1/2 stroke-gray-300/30 [mask-image:linear-gradient(to_bottom,white_20%,transparent_75%)] sm:-top-9 sm:h-auto"
|
||||
/>
|
||||
<h1 className="text-center text-2xl font-medium tracking-tight text-gray-900">{title}</h1>
|
||||
{subtitle && <p className="mt-3 text-center text-lg text-gray-600">{subtitle}</p>}
|
||||
</div>
|
||||
<div className="-mx-4 mt-10 flex-auto bg-white py-10 px-4 shadow-2xl shadow-gray-900/10 sm:mx-0 sm:flex-none sm:rounded-2xl sm:p-24">
|
||||
<div className="flex-auto items-center justify-center w-full py-10 px-4 sm:mx-0 sm:flex-none sm:rounded-2xl sm:p-20">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 (
|
||||
<Popover className="relative">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button aria-label="Toggle Account Options" className="flex">
|
||||
<div className="flex items-center gap-4 p-1 lg:pr-6 rounded-full bg-white border border-slate-300/70 hover:bg-gray-200/50 transition-colors duration-300">
|
||||
<Image
|
||||
src="/images/temp-avatars/av1.jpg"
|
||||
alt="Profile Picture"
|
||||
width="40"
|
||||
height="40"
|
||||
className="rounded-full"
|
||||
></Image>
|
||||
<p className="hidden lg:flex">{email}</p>
|
||||
{/* Will be changed to username once it is implemented */}
|
||||
</div>
|
||||
</Popover.Button>
|
||||
<AnimatePresence initial={false}>
|
||||
{open && (
|
||||
<>
|
||||
<Popover.Panel
|
||||
static
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: -10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
y: -10,
|
||||
transition: { duration: 0.2 },
|
||||
}}
|
||||
className="absolute right-0 mt-3 w-screen max-w-xs p-4 rounded-md bg-white border border-slate-300/70"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
{accountOptions.map((item) => (
|
||||
<a
|
||||
key={item.name}
|
||||
href={item.href}
|
||||
aria-label={item.desc}
|
||||
className="flex items-center rounded-md hover:bg-gray-200/50"
|
||||
>
|
||||
<div className="p-4">
|
||||
<item.icon aria-hidden="true" />
|
||||
</div>
|
||||
<div>
|
||||
<p>{item.name}</p>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
<a
|
||||
className="flex items-center rounded-md hover:bg-gray-100 cursor-pointer"
|
||||
onClick={() => signOut()}
|
||||
>
|
||||
<div className="p-4">
|
||||
<FaSignOutAlt />
|
||||
</div>
|
||||
<div>
|
||||
<p>Sign Out</p>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</Popover.Panel>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Avatar;
|
||||
@@ -12,7 +12,7 @@ export function CallToAction() {
|
||||
<h2 className="text-3xl font-medium tracking-tight text-white sm:text-4xl">Join Us</h2>
|
||||
<p className="mt-4 text-lg text-gray-300">
|
||||
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:
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center">
|
||||
@@ -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"
|
||||
/>
|
||||
</svg>
|
||||
<span className="text-lg ml-3">DISCORD</span>
|
||||
<span className="text-lg ml-3">Discord</span>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
|
||||
@@ -12,13 +12,7 @@ export function Footer() {
|
||||
<div>
|
||||
<div className="flex items-center text-gray-900">
|
||||
<Link href="/" aria-label="Home" className="flex items-center">
|
||||
<Image
|
||||
src="/images/logos/CHAT-THOUGHT-LOGO.svg"
|
||||
className="mx-auto object-fill"
|
||||
width="50"
|
||||
height="50"
|
||||
alt="logo"
|
||||
/>
|
||||
<Image src="/images/logos/logo.svg" className="mx-auto object-fill" width="50" height="50" alt="logo" />
|
||||
</Link>
|
||||
|
||||
<div className="ml-4">
|
||||
|
||||
@@ -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 (
|
||||
<Button variant="outline" onClick={() => signOut()}>
|
||||
Log out
|
||||
</Button>
|
||||
);
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<Link href="/auth/signin" aria-label="Home" className="flex items-center">
|
||||
<Button variant="outline">Log in</Button>
|
||||
<Link href="/auth/signup" aria-label="Home" className="flex items-center">
|
||||
<Button variant="outline" leftIcon={<FaUser />}>
|
||||
Log in
|
||||
</Button>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
@@ -57,28 +57,22 @@ export function Header() {
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
<Container className="relative z-50 flex justify-between py-8">
|
||||
<Container className="relative bg-white z-10 flex justify-between py-8">
|
||||
<div className="relative z-10 flex items-center gap-16">
|
||||
<Link href="/" aria-label="Home" className="flex items-center">
|
||||
<Image
|
||||
src="/images/logos/CHAT-THOUGHT-LOGO.svg"
|
||||
className="mx-auto object-fill"
|
||||
width="50"
|
||||
height="50"
|
||||
alt="logo"
|
||||
/>
|
||||
<Image src="/images/logos/logo.svg" className="mx-auto object-fill" width="50" height="50" alt="logo" />
|
||||
<span className="text-2xl font-bold ml-3">Open Assistant</span>
|
||||
</Link>
|
||||
<div className="hidden lg:flex lg:gap-10">
|
||||
<NavLinks />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<Popover className="lg:hidden">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Popover.Button
|
||||
className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 active:stroke-gray-900 [&:not(:focus-visible)]:focus:outline-none"
|
||||
className="relative z-10 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 active:stroke-gray-900 [&:not(:focus-visible)]:focus:outline-none"
|
||||
aria-label="Toggle site navigation"
|
||||
>
|
||||
{({ open }) => (open ? <ChevronUpIcon className="h-6 w-6" /> : <MenuIcon className="h-6 w-6" />)}
|
||||
@@ -92,7 +86,7 @@ export function Header() {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-0 bg-gray-300/60 backdrop-blur"
|
||||
className="fixed inset-0 z-1 bg-gray-300/60 backdrop-blur"
|
||||
/>
|
||||
<Popover.Panel
|
||||
static
|
||||
@@ -104,15 +98,13 @@ export function Header() {
|
||||
y: -32,
|
||||
transition: { duration: 0.2 },
|
||||
}}
|
||||
className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-gray-50 px-6 pb-6 pt-32 shadow-2xl shadow-gray-900/20"
|
||||
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>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
<AccountButton />
|
||||
</div>
|
||||
<div className="mt-8 flex flex-col gap-4"></div>
|
||||
</Popover.Panel>
|
||||
</>
|
||||
)}
|
||||
@@ -121,6 +113,7 @@ export function Header() {
|
||||
)}
|
||||
</Popover>
|
||||
<AccountButton />
|
||||
<Avatar />
|
||||
</div>
|
||||
</Container>
|
||||
</nav>
|
||||
|
||||
@@ -71,7 +71,7 @@ export function Hero() {
|
||||
<BackgroundIllustration className="absolute left-1/2 top-4 h-[1026px] w-[1026px] -translate-x-1/3 stroke-gray-300/70 [mask-image:linear-gradient(to_bottom,white_20%,transparent_75%)] sm:top-16 sm:-translate-x-1/2 lg:-top-16 lg:ml-12 xl:-top-14 xl:ml-0" />
|
||||
<div className="-mx-4 h-[448px] px-9 [mask-image:linear-gradient(to_bottom,white_60%,transparent)] sm:mx-0 lg:absolute lg:-inset-x-10 lg:-top-10 lg:-bottom-20 lg:h-auto lg:px-0 lg:pt-10 xl:-bottom-32">
|
||||
<Image
|
||||
src="/images/logos/CHAT-THOUGHT-CONVO.svg"
|
||||
src="/images/logos/logo.svg"
|
||||
className="mx-auto mr-6 object-fill"
|
||||
width="450"
|
||||
height="450"
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import { Footer } from "../components/Footer";
|
||||
import { Header } from "../components/Header";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function Error() {
|
||||
const { data: session } = useSession();
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Open Assistant</title>
|
||||
<meta name="404" content="Sorry, this page doesn't exist." />
|
||||
</Head>
|
||||
<Header />
|
||||
<main className="flex h-3/4 items-center justify-center overflow-hidden subpixel-antialiased text-xl">
|
||||
{"Sorry, the page you're looking for does not exist."}
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Open Assistant</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Conversational AI for everyone. An open source project to create a chat enabled GPT LLM run by LAION and contributors around the world."
|
||||
/>
|
||||
</Head>
|
||||
<Header />
|
||||
<main>
|
||||
<h2>Open Chat Gpt</h2>
|
||||
|
||||
<p>You are logged in</p>
|
||||
|
||||
<Link href="/grading/grade-output">~Rate a prompt and output now~</Link>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,12 +1,35 @@
|
||||
import { ChakraProvider } from "@chakra-ui/react";
|
||||
import { SessionProvider } from "next-auth/react";
|
||||
import { Inter } from "@next/font/google";
|
||||
import { extendTheme } from "@chakra-ui/react";
|
||||
|
||||
import "../styles/globals.css";
|
||||
import "focus-visible";
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ["latin"],
|
||||
variable: "--font-inter",
|
||||
});
|
||||
|
||||
const theme = extendTheme({
|
||||
styles: {
|
||||
global: {
|
||||
body: {
|
||||
bg: "white",
|
||||
},
|
||||
main: {
|
||||
fontFamily: "Inter",
|
||||
},
|
||||
header: {
|
||||
fontFamily: "Inter",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function MyApp({ Component, pageProps: { session, ...pageProps } }) {
|
||||
return (
|
||||
<ChakraProvider>
|
||||
<ChakraProvider theme={theme}>
|
||||
<SessionProvider session={session}>
|
||||
<Component {...pageProps} />
|
||||
</SessionProvider>
|
||||
|
||||
@@ -37,7 +37,9 @@ export const authOptions: AuthOptions = {
|
||||
adapter: PrismaAdapter(prisma),
|
||||
providers,
|
||||
pages: {
|
||||
signIn: "/auth/signin",
|
||||
signIn: "/auth/signup",
|
||||
verifyRequest: "/auth/verify",
|
||||
// error: "/auth/error", -Will be used later
|
||||
},
|
||||
session: {
|
||||
strategy: "jwt",
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import { Button, Input } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
import { FaDiscord, FaGithub, FaMagic } from "react-icons/fa";
|
||||
import { getCsrfToken, getProviders, signIn } from "next-auth/react";
|
||||
import { useRef } from "react";
|
||||
|
||||
import { AuthLayout } from "src/components/AuthLayout";
|
||||
|
||||
export default function Signin({ csrfToken, providers }) {
|
||||
const { discord, email } = providers;
|
||||
const emailEl = useRef(null);
|
||||
const signinWithEmail = () => {
|
||||
signIn(email.id, { callbackUrl: "/", email: emailEl.current.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Log in</title>
|
||||
</Head>
|
||||
<AuthLayout title="Log In" subtitle={<></>}>
|
||||
<div className="space-y-6 w-100 flex flex-col justify-center items-center ">
|
||||
{discord && (
|
||||
<Button
|
||||
leftIcon={<FaDiscord />}
|
||||
colorScheme="blue"
|
||||
size="lg"
|
||||
w="36"
|
||||
onClick={() => signIn(discord.id, { callbackUrl: "/" })}
|
||||
>
|
||||
Discord
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Button w="36" leftIcon={<FaGithub />} colorScheme="blue" size="lg">
|
||||
Github
|
||||
</Button>
|
||||
|
||||
{email && (
|
||||
<div>
|
||||
<Input variant="outline" placeholder="Email Address" ref={emailEl} />
|
||||
<Button w="36" leftIcon={<FaMagic />} colorScheme="blue" size="lg" onClick={signinWithEmail}>
|
||||
Email
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const csrfToken = await getCsrfToken();
|
||||
const providers = await getProviders();
|
||||
return {
|
||||
props: {
|
||||
csrfToken,
|
||||
providers,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Button, Input, Stack } from "@chakra-ui/react";
|
||||
import Head from "next/head";
|
||||
import { FaDiscord, FaEnvelope, FaGithub } from "react-icons/fa";
|
||||
import { getCsrfToken, getProviders, signIn } from "next-auth/react";
|
||||
import { 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 emailEl = useRef(null);
|
||||
const signinWithEmail = () => {
|
||||
signIn(email.id, { callbackUrl: "/", email: emailEl.current.value });
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Sign Up - Open Assistant</title>
|
||||
<meta name="Sign Up" content="Sign up to access Open Assistant" />
|
||||
</Head>
|
||||
<AuthLayout>
|
||||
<Stack spacing="2">
|
||||
{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>
|
||||
)}
|
||||
{discord && (
|
||||
<Button
|
||||
bg="#5865F2"
|
||||
_hover={{ bg: "#4A57E3" }}
|
||||
_active={{
|
||||
bg: "#454FBF",
|
||||
}}
|
||||
size="lg"
|
||||
leftIcon={<FaDiscord />}
|
||||
color="white"
|
||||
onClick={() => signIn(discord, { callbackUrl: "/" })}
|
||||
// isDisabled="false"
|
||||
>
|
||||
Continue with Discord
|
||||
</Button>
|
||||
)}
|
||||
{github && (
|
||||
<Button
|
||||
bg="#333333"
|
||||
_hover={{ bg: "#181818" }}
|
||||
_active={{
|
||||
bg: "#101010",
|
||||
}}
|
||||
size={"lg"}
|
||||
leftIcon={<FaGithub />}
|
||||
colorScheme="blue"
|
||||
// isDisabled="false"
|
||||
>
|
||||
Continue with Github
|
||||
</Button>
|
||||
)}
|
||||
</Stack>
|
||||
<div className="pt-10 text-center">
|
||||
By signing up you agree to our <br></br>
|
||||
<Link href="#" aria-label="Terms of Service" className="hover:underline underline-offset-4">
|
||||
<b>Terms of Service</b>
|
||||
</Link>{" "}
|
||||
and{" "}
|
||||
<Link href="#" aria-label="Terms of Use" className="hover:underline underline-offset-4">
|
||||
<b>Privacy Policy</b>
|
||||
</Link>
|
||||
.
|
||||
</div>
|
||||
<hr className="mt-14 mb-4 h-px bg-gray-200 border-0" />
|
||||
<div className="text-center">
|
||||
Already have an account?{" "}
|
||||
<Link href="#" aria-label="Log In" className="hover:underline underline-offset-4">
|
||||
<b>Log In</b>
|
||||
</Link>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const csrfToken = await getCsrfToken();
|
||||
const providers = await getProviders();
|
||||
return {
|
||||
props: {
|
||||
csrfToken,
|
||||
providers,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import Head from "next/head";
|
||||
import { getCsrfToken, getProviders, signIn } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
|
||||
import { AuthLayout } from "src/components/AuthLayout";
|
||||
|
||||
export default function Verify() {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Sign Up - Open Assistant</title>
|
||||
<meta name="Sign Up" content="Sign up to access Open Assistant" />
|
||||
</Head>
|
||||
<AuthLayout>
|
||||
<h1 className="text-lg">A sign-in link has been sent to your email address.</h1>
|
||||
<hr className="mt-14 mb-4 h-px bg-gray-200 border-0" />
|
||||
<Link
|
||||
href="#"
|
||||
aria-label="Log In"
|
||||
className="flex justify-center font-medium text-black hover:underline underline-offset-4"
|
||||
>
|
||||
Already have an account? Log In
|
||||
</Link>
|
||||
</AuthLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function getServerSideProps(context) {
|
||||
const csrfToken = await getCsrfToken();
|
||||
const providers = await getProviders();
|
||||
return {
|
||||
props: {
|
||||
csrfToken,
|
||||
providers,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { useSession } from "next-auth/react";
|
||||
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { Button, Input, Stack } from "@chakra-ui/react";
|
||||
|
||||
import { CallToAction } from "../components/CallToAction";
|
||||
import { Faq } from "../components/Faq";
|
||||
@@ -25,7 +26,7 @@ export default function Home() {
|
||||
/>
|
||||
</Head>
|
||||
<Header />
|
||||
<main>
|
||||
<main className="z-0">
|
||||
<Hero />
|
||||
<CallToAction />
|
||||
|
||||
@@ -45,13 +46,13 @@ export default function Home() {
|
||||
/>
|
||||
</Head>
|
||||
<Header />
|
||||
<main>
|
||||
<h2>Open Chat Gpt</h2>
|
||||
|
||||
<p>You are logged in</p>
|
||||
|
||||
<Link href="/grading/grade-output">~Rate a prompt and output now~</Link>
|
||||
<Link href="/leaderboard/score-leaderboard">~view the score leaderboard~</Link>
|
||||
<main className="h-3/4 z-0 bg-white flex flex-col items-center justify-center gap-2">
|
||||
<Button size="lg" colorScheme="blue" className="drop-shadow">
|
||||
<Link href="/grading/grade-output">Rate a prompt and output now</Link>
|
||||
</Button>
|
||||
<Button size="lg" colorScheme="blue" className="drop-shadow">
|
||||
<Link href="/summarize/story">Summarize a story</Link>
|
||||
</Button>
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,118 @@
|
||||
// TODO(#65): Unify and simplify the task paths
|
||||
import { Textarea } from "@chakra-ui/react";
|
||||
import { useRef, useState } from "react";
|
||||
import useSWRMutation from "swr/mutation";
|
||||
import useSWRImmutable from "swr/immutable";
|
||||
|
||||
import fetcher from "src/lib/fetcher";
|
||||
import poster from "src/lib/poster";
|
||||
|
||||
const SummarizeStory = () => {
|
||||
// Use an array of tasks that record the sequence of steps until a task is
|
||||
// deemed complete.
|
||||
const [tasks, setTasks] = useState([]);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
// Fetch the very fist task. We can ignore everything except isLoading
|
||||
// because the onSuccess handler will update `tasks` when ready.
|
||||
const { isLoading } = useSWRImmutable("/api/new_task/summarize_story", fetcher, {
|
||||
onSuccess: (data) => {
|
||||
console.log(data);
|
||||
setTasks([data]);
|
||||
},
|
||||
});
|
||||
|
||||
// Every time we submit an answer to the latest task, let the backend handle
|
||||
// all the interactions then add the resulting task to the queue. This ends
|
||||
// when we hit the done task.
|
||||
const { trigger, isMutating } = useSWRMutation("/api/update_task", poster, {
|
||||
onSuccess: async (data) => {
|
||||
const newTask = await data.json();
|
||||
// This is the more efficient way to update a react state array.
|
||||
setTasks((oldTasks) => [...oldTasks, newTask]);
|
||||
},
|
||||
});
|
||||
|
||||
// Trigger a mutation that updates the current task. We should probably
|
||||
// signal somewhere that this interaction is being processed.
|
||||
const submitResponse = (task: { id: string }) => {
|
||||
const text = inputRef.current.value.trim();
|
||||
trigger({
|
||||
id: task.id,
|
||||
content: {
|
||||
update_type: "text_reply_to_post",
|
||||
text,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* TODO: Make this a nicer loading screen.
|
||||
*/
|
||||
if (tasks.length == 0) {
|
||||
return <div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className=" p-6 h-full mx-auto bg-slate-100 text-gray-800">
|
||||
{/* Instrunction and Output panels */}
|
||||
<section className="mb-8 lt-lg:mb-12 ">
|
||||
<div className="grid lg:gap-x-12 lg:grid-cols-2">
|
||||
{/* Instruction panel */}
|
||||
<div className="rounded-lg shadow-lg h-full block bg-white">
|
||||
<div className="p-6">
|
||||
<h5 className="text-lg font-semibold">Instruction</h5>
|
||||
<p className="text-lg py-1">Summarize the following story</p>
|
||||
<div className="bg-slate-800 p-6 rounded-xl text-white whitespace-pre-wrap">{tasks[0].task.story}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Output panel */}
|
||||
<div className="mt-6 lg:mt-0 rounded-lg shadow-lg h-full block bg-white">
|
||||
<div className="flex justify-center p-6">
|
||||
<Textarea name="summary" placeholder="Summary" ref={inputRef} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Info & controls */}
|
||||
<section className="mb-8 p-4 rounded-lg shadow-lg bg-white flex flex-row justify-items-stretch ">
|
||||
<div className="flex flex-col justify-self-start text-gray-700">
|
||||
<div>
|
||||
<span>
|
||||
<b>Prompt</b>
|
||||
</span>
|
||||
<span className="ml-2">{tasks[0].id}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span>
|
||||
<b>Output</b>
|
||||
</span>
|
||||
<span className="ml-2">Submit your answer</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Skip / Submit controls */}
|
||||
<div className="flex justify-center ml-auto">
|
||||
<button
|
||||
type="button"
|
||||
className="mr-2 inline-flex items-center rounded-md border border-transparent bg-indigo-100 px-4 py-2 text-sm font-medium text-indigo-700 hover:bg-indigo-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
Skip
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => submitResponse(tasks[0])}
|
||||
className="inline-flex items-center rounded-md border border-transparent bg-indigo-600 px-4 py-2 text-sm font-medium text-white shadow-sm hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SummarizeStory;
|
||||