Skip to content

Commit

Permalink
New feature: editable cha title, copy to clipboard
Browse files Browse the repository at this point in the history
  • Loading branch information
c0sogi committed May 15, 2023
1 parent 39d1f85 commit aaea7fa
Show file tree
Hide file tree
Showing 28 changed files with 41,182 additions and 40,654 deletions.
7 changes: 5 additions & 2 deletions .env-sample
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# DELETE THIS COMMENT LINE!!
# DELETE THESE COMMENT LINE!!
# DEFAULT_LLM_MODEL is defined in `LLM_MODELS` in `app\models\gpt_llms.py`

API_ENV="local"
PORT=8000
DEFAULT_LLM_MODEL="gpt_3_5_turbo"
MYSQL_DATABASE="traffic"
MYSQL_TEST_DATABASE="testing_db"
MYSQL_ROOT_PASSWORD="YOUR_MYSQL_PASSWORD_HERE"
Expand All @@ -10,6 +12,7 @@ MYSQL_PASSWORD="YOUR_DB_ADMIN_PASSWORD_HERE"
REDIS_DATABASE="0"
REDIS_PASSWORD="YOUR_REDIS_PASSWORD_HERE"
JWT_SECRET="ANY_PASSWORD_FOR_JWT_TOKEN_GENERATION_HERE"

OPENAI_API_KEY="sk-*************"
AWS_ACCESS_KEY="OPTIONAL_IF_YOU_NEED"
AWS_SECRET_KEY="OPTIONAL_IF_YOU_NEED"
Expand All @@ -20,7 +23,7 @@ SAMPLE_SECRET_KEY="OPTIONAL_IF_YOU_NEED_FOR_TESTING"
KAKAO_RESTAPI_TOKEN="OPTIONAL_IF_YOU_NEED e.g. Bearer XXXXX"
WEATHERBIT_API_KEY="OPTIONAL_IF_YOU_NEED"
NASA_API_KEY="OPTIONAL_IF_YOU_NEED"
HOST_IP="YOUR_IP_HERE e.g. 192.168.0.2"
HOST_IP="OPTIONAL_YOUR_IP_HERE e.g. 192.168.0.2"
HOST_MAIN="OPTIONAL_YOUR_DOMAIN_HERE e.g. yourdomain.com, if you are running API_ENV as production, this will be needed for TLS certificate registration"
HOST_SUB="OPTIONAL_YOUR_SUB_DOMAIN_HERE e.g. mobile.yourdomain.com"
MY_EMAIL="OPTIONAL_YOUR_DOMAIN_HERE e.g. yourdomain.com, if you are running API_ENV as production, this will be needed for TLS certificate registration"
Expand Down
3 changes: 2 additions & 1 deletion app/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __call__(cls, *args, **kwargs):


# Optional Service Variables
DEFAULT_LLM_MODEL: str = environ.get("DEFAULT_LLM_MODEL", "gpt_3_5_turbo")
OPENAI_API_KEY: str | None = environ.get("OPENAI_API_KEY")
RAPID_API_KEY: str | None = environ.get("RAPID_API_KEY")
GOOGLE_TRANSLATE_API_KEY: str | None = environ.get("GOOGLE_TRANSLATE_API_KEY")
Expand Down Expand Up @@ -93,7 +94,7 @@ class Config(metaclass=SingletonMetaClass):
host_main: str = HOST_MAIN
port: int = 8000
db_pool_recycle: int = 900
db_echo: bool = True
db_echo: bool = False
debug: bool = False
test_mode: bool = False
database_url_format: str = "{dialect}+{driver}://{user}:{password}@{host}:{port}/{database}?charset=utf8mb4"
Expand Down
Binary file added app/contents/edit_title_demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/contents/embed_demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added app/contents/stop_generation_demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified app/contents/ui_demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
86 changes: 43 additions & 43 deletions app/database/schemas/chatgpt.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,49 @@
from sqlalchemy import (
String,
Enum,
ForeignKey,
Float,
Text,
)
from sqlalchemy.orm import (
relationship,
Mapped,
mapped_column,
)
from app.database.schemas.auth import Users
from .. import Base
from . import Mixin
# from sqlalchemy import (
# String,
# Enum,
# ForeignKey,
# Float,
# Text,
# )
# from sqlalchemy.orm import (
# relationship,
# Mapped,
# mapped_column,
# )
# from app.database.schemas.auth import Users
# from .. import Base
# from . import Mixin


class ChatRooms(Base, Mixin):
__tablename__ = "chat_rooms"
uuid: Mapped[str] = mapped_column(String(length=36), index=True, unique=True)
status: Mapped[str] = mapped_column(Enum("active", "deleted", "blocked"), default="active")
chat_room_type: Mapped[str] = mapped_column(String(length=20), index=True)
name: Mapped[str] = mapped_column(String(length=20))
description: Mapped[str | None] = mapped_column(String(length=100))
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
users: Mapped["Users"] = relationship(back_populates="chat_rooms")
chat_messages: Mapped["ChatMessages"] = relationship(back_populates="chat_rooms", cascade="all, delete-orphan")
# class ChatRooms(Base, Mixin):
# __tablename__ = "chat_rooms"
# uuid: Mapped[str] = mapped_column(String(length=36), index=True, unique=True)
# status: Mapped[str] = mapped_column(Enum("active", "deleted", "blocked"), default="active")
# chat_room_type: Mapped[str] = mapped_column(String(length=20), index=True)
# name: Mapped[str] = mapped_column(String(length=20))
# description: Mapped[str | None] = mapped_column(String(length=100))
# user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
# users: Mapped["Users"] = relationship(back_populates="chat_rooms")
# chat_messages: Mapped["ChatMessages"] = relationship(back_populates="chat_rooms", cascade="all, delete-orphan")


class ChatMessages(Base, Mixin):
__tablename__ = "chat_messages"
uuid: Mapped[str] = mapped_column(String(length=36), index=True, unique=True)
status: Mapped[str] = mapped_column(Enum("active", "deleted", "blocked"), default="active")
role: Mapped[str] = mapped_column(String(length=20), default="user")
message: Mapped[str] = mapped_column(Text)
chat_room_id: Mapped[int] = mapped_column(ForeignKey("chat_rooms.id"))
chat_rooms: Mapped["ChatRooms"] = relationship(back_populates="chat_messages")
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
users: Mapped["Users"] = relationship(back_populates="chat_messages")
# class ChatMessages(Base, Mixin):
# __tablename__ = "chat_messages"
# uuid: Mapped[str] = mapped_column(String(length=36), index=True, unique=True)
# status: Mapped[str] = mapped_column(Enum("active", "deleted", "blocked"), default="active")
# role: Mapped[str] = mapped_column(String(length=20), default="user")
# message: Mapped[str] = mapped_column(Text)
# chat_room_id: Mapped[int] = mapped_column(ForeignKey("chat_rooms.id"))
# chat_rooms: Mapped["ChatRooms"] = relationship(back_populates="chat_messages")
# user_id: Mapped[int] = mapped_column(ForeignKey("users.id"))
# users: Mapped["Users"] = relationship(back_populates="chat_messages")


class GptPresets(Base, Mixin):
__tablename__ = "gpt_presets"
temperature: Mapped[float] = mapped_column(Float, default=0.9)
top_p: Mapped[float] = mapped_column(Float, default=1.0)
presence_penalty: Mapped[float] = mapped_column(Float, default=0)
frequency_penalty: Mapped[float] = mapped_column(Float, default=0)
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), unique=True)
users: Mapped["Users"] = relationship(back_populates="gpt_presets")
# class GptPresets(Base, Mixin):
# __tablename__ = "gpt_presets"
# temperature: Mapped[float] = mapped_column(Float, default=0.9)
# top_p: Mapped[float] = mapped_column(Float, default=1.0)
# presence_penalty: Mapped[float] = mapped_column(Float, default=0)
# frequency_penalty: Mapped[float] = mapped_column(Float, default=0)
# user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), unique=True)
# users: Mapped["Users"] = relationship(back_populates="gpt_presets")
6 changes: 6 additions & 0 deletions app/errors/gpt_exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,9 @@ class GptContinueException(GptException):
def __init__(self, *, msg: str | None = None) -> None:
self.msg = msg
super().__init__(msg=msg)


class GptInterruptedException(GptException):
def __init__(self, *, msg: str | None = None) -> None:
self.msg = msg
super().__init__(msg=msg)
2 changes: 1 addition & 1 deletion app/middlewares/token_validator.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ async def api_key(
secret_key=matched_api_key.secret_key,
):
raise Responses_401.invalid_api_header
now_timestamp: int = UTC.timestamp(hour_diff=9)
now_timestamp: int = UTC.timestamp()
if not (now_timestamp - 60 < int(timestamp) < now_timestamp + 60):
raise Responses_401.invalid_timestamp
return UserToken(**matched_user.to_dict())
Expand Down
14 changes: 12 additions & 2 deletions app/models/gpt_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from orjson import dumps as orjson_dumps
from orjson import loads as orjson_loads
from app.common.config import DEFAULT_LLM_MODEL

from app.models.gpt_llms import LLMModels
from app.utils.date_utils import UTC
Expand Down Expand Up @@ -62,7 +63,7 @@ class MessageHistory: # message history for user and gpt
content: str
tokens: int
is_user: bool
timestamp: int = field(default_factory=lambda: UTC.timestamp(hour_diff=9))
timestamp: int = field(default_factory=UTC.timestamp)
uuid: str = field(default_factory=lambda: uuid4().hex)
model_name: str | None = None

Expand All @@ -81,6 +82,7 @@ def datetime(self) -> datetime:
class UserGptProfile: # user gpt profile for user and gpt
user_id: str
chat_room_id: str = field(default_factory=lambda: uuid4().hex)
chat_room_name: str = field(default_factory=lambda: UTC.now_isoformat())
created_at: int = field(default_factory=lambda: UTC.timestamp(hour_diff=9))
user_role: str = field(default=GptRoles.USER.value)
gpt_role: str = field(default=GptRoles.GPT.value)
Expand Down Expand Up @@ -162,6 +164,10 @@ def user_id(self) -> str:
def chat_room_id(self) -> str:
return self.user_gpt_profile.chat_room_id

@property
def chat_room_name(self) -> str:
return self.user_gpt_profile.chat_room_name

def __repr__(self) -> str:
gpt_model: LLMModels = self.gpt_model
time_string: str = datetime.strptime(
Expand Down Expand Up @@ -202,7 +208,11 @@ def construct_default(
cls,
user_id: str,
chat_room_id: str,
gpt_model: LLMModels = LLMModels.gpt_3_5_turbo,
gpt_model: LLMModels = getattr(
LLMModels,
DEFAULT_LLM_MODEL,
LLMModels.gpt_3_5_turbo,
),
):
return cls(
user_gpt_profile=UserGptProfile(user_id=user_id, chat_room_id=chat_room_id),
Expand Down
4 changes: 2 additions & 2 deletions app/routers/websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from app.database.schemas.auth import Users
from app.errors.api_exceptions import Responses_400
from app.utils.logger import api_logger
from app.utils.chatgpt.chatgpt_stream_manager import begin_chat
from app.utils.chatgpt.chatgpt_stream_manager import ChatGptStreamManager
from app.common.config import API_ENV

router = APIRouter()
Expand All @@ -24,7 +24,7 @@ async def ws_chatgpt(websocket: WebSocket, api_key: str):
except Exception as exception:
api_logger.error(exception, exc_info=True)
return
await begin_chat(
await ChatGptStreamManager.begin_chat(
websocket=websocket,
user_id=user.email,
)
Expand Down
15 changes: 14 additions & 1 deletion app/utils/chatgpt/chatgpt_buffer.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import asyncio
from dataclasses import dataclass, field

from fastapi import WebSocket

from app.models.gpt_models import UserGptContext
Expand All @@ -10,6 +10,8 @@ class BufferedUserContext:
user_id: str
websocket: WebSocket
sorted_ctxts: list[UserGptContext]
queue: asyncio.Queue = field(default_factory=asyncio.Queue)
done: asyncio.Event = field(default_factory=asyncio.Event)
_current_ctxt: UserGptContext = field(init=False)

def __post_init__(self) -> None:
Expand Down Expand Up @@ -46,6 +48,17 @@ def sorted_user_gpt_contexts(self) -> list[UserGptContext]:
def sorted_chat_room_ids(self) -> list[str]:
return [context.chat_room_id for context in self.sorted_ctxts]

@property
def sorted_chat_room_names(self) -> list[str]:
return [context.chat_room_name for context in self.sorted_ctxts]

@property
def sorted_chat_rooms(self) -> list[dict[str, str]]:
return [
{"chat_room_id": context.chat_room_id, "chat_room_name": context.chat_room_name}
for context in self.sorted_ctxts
]

@property
def current_user_gpt_context(self) -> UserGptContext:
return self._current_ctxt
23 changes: 23 additions & 0 deletions app/utils/chatgpt/chatgpt_cache_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,29 @@ async def update_profile_and_model(
success &= bool(result)
return success

@classmethod
async def update_profile(
cls,
user_gpt_context: UserGptContext,
only_if_exists: bool = True,
) -> bool:
json_data = user_gpt_context.json()

field: str = "user_gpt_profile"
key: str = cls._generate_key(
user_id=user_gpt_context.user_id,
chat_room_id=user_gpt_context.chat_room_id,
field=field,
)

return (
await cache.redis.set(
key,
orjson_dumps(json_data[field]),
xx=only_if_exists,
)
) is True

@classmethod
async def update_message_histories(
cls,
Expand Down
34 changes: 16 additions & 18 deletions app/utils/chatgpt/chatgpt_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
from app.utils.chatgpt.chatgpt_cache_manager import ChatGptCacheManager
from app.utils.chatgpt.chatgpt_message_manager import MessageManager
from app.utils.chatgpt.chatgpt_vectorstore_manager import Document, VectorStoreManager
from app.utils.chatgpt.chatgpt_websocket_manager import HandleMessage, SendToWebsocket
from app.viewmodels.base_models import MessageFromWebsocket
from app.utils.chatgpt.chatgpt_websocket_manager import SendToWebsocket
from app.utils.chatgpt.chatgpt_message_handler import MessageHandler
from app.models.gpt_models import GptRoles, MessageHistory, LLMModels, UserGptContext


Expand Down Expand Up @@ -107,8 +107,7 @@ async def get_contexts_sorted_from_recent_to_past(user_id: str, chat_room_ids: l
async def command_handler(
callback_name: str,
callback_args: list[str],
received: MessageFromWebsocket,
websocket: WebSocket,
translate: bool,
buffer: BufferedUserContext,
):
callback_response, response_type = await ChatGptCommands._get_command_response(
Expand All @@ -119,26 +118,26 @@ async def command_handler(
if response_type is ResponseType.DO_NOTHING:
return
elif response_type is ResponseType.HANDLE_GPT:
await HandleMessage.gpt(
translate=received.translate,
await MessageHandler.gpt(
translate=translate,
buffer=buffer,
)
return
elif response_type is ResponseType.HANDLE_USER:
await HandleMessage.user(
await MessageHandler.user(
msg=callback_response,
translate=received.translate,
translate=translate,
buffer=buffer,
)
return
elif response_type is ResponseType.HANDLE_BOTH:
await HandleMessage.user(
await MessageHandler.user(
msg=callback_response,
translate=received.translate,
translate=translate,
buffer=buffer,
)
await HandleMessage.gpt(
translate=received.translate,
await MessageHandler.gpt(
translate=translate,
buffer=buffer,
)
return
Expand All @@ -147,8 +146,7 @@ async def command_handler(
await command_handler(
callback_name=splitted[0][1:] if splitted[0].startswith("/") else splitted[0],
callback_args=splitted[1:],
received=received,
websocket=websocket,
translate=translate,
buffer=buffer,
)

Expand Down Expand Up @@ -298,16 +296,16 @@ async def deletechatroom(chat_room_id: str, buffer: BufferedUserContext) -> None
buffer=buffer,
)
if buffer.current_chat_room_id == chat_room_id_before:
await SendToWebsocket.initiation_of_chat(
await SendToWebsocket.init(
buffer=buffer,
send_previous_chats=False,
send_chat_room_ids=delete_result,
send_chat_rooms=delete_result,
)
else:
await SendToWebsocket.initiation_of_chat(
await SendToWebsocket.init(
buffer=buffer,
send_previous_chats=True,
send_chat_room_ids=delete_result,
send_chat_rooms=delete_result,
)

@staticmethod
Expand Down
Loading

0 comments on commit aaea7fa

Please sign in to comment.