From b12edb4a7a0c7aec768b95c895bab13a67ac21ef Mon Sep 17 00:00:00 2001 From: "Timothy J. Baek" Date: Sat, 20 Apr 2024 18:24:18 -0500 Subject: [PATCH] refac: replace timestamp field --- .../internal/migrations/005_add_updated_at.py | 77 +++++++++++++++++++ backend/apps/web/models/chats.py | 43 ++++++++--- backend/apps/web/routers/chats.py | 12 +++ src/lib/apis/chats/index.ts | 31 ++++++++ src/lib/components/common/Modal.svelte | 4 +- src/lib/components/layout/Sidebar.svelte | 32 ++++++-- .../layout/Sidebar/ArchivedChatsModal.svelte | 60 +++++++++++++++ 7 files changed, 240 insertions(+), 19 deletions(-) create mode 100644 backend/apps/web/internal/migrations/005_add_updated_at.py create mode 100644 src/lib/components/layout/Sidebar/ArchivedChatsModal.svelte diff --git a/backend/apps/web/internal/migrations/005_add_updated_at.py b/backend/apps/web/internal/migrations/005_add_updated_at.py new file mode 100644 index 00000000..63a023cd --- /dev/null +++ b/backend/apps/web/internal/migrations/005_add_updated_at.py @@ -0,0 +1,77 @@ +"""Peewee migrations -- 002_add_local_sharing.py. + +Some examples (model - class or model name):: + + > Model = migrator.orm['table_name'] # Return model in current state by name + > Model = migrator.ModelClass # Return model in current state by name + + > migrator.sql(sql) # Run custom SQL + > migrator.run(func, *args, **kwargs) # Run python function with the given args + > migrator.create_model(Model) # Create a model (could be used as decorator) + > migrator.remove_model(model, cascade=True) # Remove a model + > migrator.add_fields(model, **fields) # Add fields to a model + > migrator.change_fields(model, **fields) # Change fields + > migrator.remove_fields(model, *field_names, cascade=True) + > migrator.rename_field(model, old_field_name, new_field_name) + > migrator.rename_table(model, new_table_name) + > migrator.add_index(model, *col_names, unique=False) + > migrator.add_not_null(model, *field_names) + > migrator.add_default(model, field_name, default) + > migrator.add_constraint(model, name, sql) + > migrator.drop_index(model, *col_names) + > migrator.drop_not_null(model, *field_names) + > migrator.drop_constraints(model, *constraints) + +""" + +from contextlib import suppress + +import peewee as pw +from peewee_migrate import Migrator + + +with suppress(ImportError): + import playhouse.postgres_ext as pw_pext + + +def migrate(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your migrations here.""" + + # Adding fields created_at and updated_at to the 'chat' table + migrator.add_fields( + "chat", + created_at=pw.DateTimeField(null=True), # Allow null for transition + updated_at=pw.DateTimeField(null=True), # Allow null for transition + ) + + # Populate the new fields from an existing 'timestamp' field + migrator.sql( + "UPDATE chat SET created_at = timestamp, updated_at = timestamp WHERE timestamp IS NOT NULL" + ) + + # Now that the data has been copied, remove the original 'timestamp' field + migrator.remove_fields("chat", "timestamp") + + # Update the fields to be not null now that they are populated + migrator.change_fields( + "chat", + created_at=pw.DateTimeField(null=False), + updated_at=pw.DateTimeField(null=False), + ) + + +def rollback(migrator: Migrator, database: pw.Database, *, fake=False): + """Write your rollback migrations here.""" + + # Recreate the timestamp field initially allowing null values for safe transition + migrator.add_fields("chat", timestamp=pw.DateTimeField(null=True)) + + # Copy the earliest created_at date back into the new timestamp field + # This assumes created_at was originally a copy of timestamp + migrator.sql("UPDATE chat SET timestamp = created_at") + + # Remove the created_at and updated_at fields + migrator.remove_fields("chat", "created_at", "updated_at") + + # Finally, alter the timestamp field to not allow nulls if that was the original setting + migrator.change_fields("chat", timestamp=pw.DateTimeField(null=False)) diff --git a/backend/apps/web/models/chats.py b/backend/apps/web/models/chats.py index 0594b5cb..f7cc0120 100644 --- a/backend/apps/web/models/chats.py +++ b/backend/apps/web/models/chats.py @@ -19,7 +19,10 @@ class Chat(Model): user_id = CharField() title = CharField() chat = TextField() # Save Chat JSON as Text - timestamp = DateField() + + created_at = DateTimeField() + updated_at = DateTimeField() + share_id = CharField(null=True, unique=True) archived = BooleanField(default=False) @@ -32,7 +35,10 @@ class ChatModel(BaseModel): user_id: str title: str chat: str - timestamp: int # timestamp in epoch + + created_at: int # timestamp in epoch + updated_at: int # timestamp in epoch + share_id: Optional[str] = None archived: bool = False @@ -55,13 +61,16 @@ class ChatResponse(BaseModel): user_id: str title: str chat: dict - timestamp: int # timestamp in epoch + updated_at: int # timestamp in epoch + created_at: int # timestamp in epoch share_id: Optional[str] = None # id of the chat to be shared class ChatTitleIdResponse(BaseModel): id: str title: str + updated_at: int + created_at: int class ChatTable: @@ -79,7 +88,8 @@ class ChatTable: form_data.chat["title"] if "title" in form_data.chat else "New Chat" ), "chat": json.dumps(form_data.chat), - "timestamp": int(time.time()), + "created_at": int(time.time()), + "updated_at": int(time.time()), } ) @@ -91,7 +101,7 @@ class ChatTable: query = Chat.update( chat=json.dumps(chat), title=chat["title"] if "title" in chat else "New Chat", - timestamp=int(time.time()), + updated_at=int(time.time()), ).where(Chat.id == id) query.execute() @@ -113,7 +123,7 @@ class ChatTable: "user_id": f"shared-{chat_id}", "title": chat.title, "chat": chat.chat, - "timestamp": int(time.time()), + "created_at": int(time.time()), } ) shared_result = Chat.create(**shared_chat.model_dump()) @@ -179,6 +189,19 @@ class ChatTable: except: return None + def get_archived_chat_lists_by_user_id( + self, user_id: str, skip: int = 0, limit: int = 50 + ) -> List[ChatModel]: + return [ + ChatModel(**model_to_dict(chat)) + for chat in Chat.select() + .where(Chat.archived == True) + .where(Chat.user_id == user_id) + .order_by(Chat.updated_at.desc()) + # .limit(limit) + # .offset(skip) + ] + def get_chat_lists_by_user_id( self, user_id: str, skip: int = 0, limit: int = 50 ) -> List[ChatModel]: @@ -187,7 +210,7 @@ class ChatTable: for chat in Chat.select() .where(Chat.archived == False) .where(Chat.user_id == user_id) - .order_by(Chat.timestamp.desc()) + .order_by(Chat.updated_at.desc()) # .limit(limit) # .offset(skip) ] @@ -200,7 +223,7 @@ class ChatTable: for chat in Chat.select() .where(Chat.archived == False) .where(Chat.id.in_(chat_ids)) - .order_by(Chat.timestamp.desc()) + .order_by(Chat.updated_at.desc()) ] def get_all_chats(self) -> List[ChatModel]: @@ -208,7 +231,7 @@ class ChatTable: ChatModel(**model_to_dict(chat)) for chat in Chat.select() .where(Chat.archived == False) - .order_by(Chat.timestamp.desc()) + .order_by(Chat.updated_at.desc()) ] def get_all_chats_by_user_id(self, user_id: str) -> List[ChatModel]: @@ -217,7 +240,7 @@ class ChatTable: for chat in Chat.select() .where(Chat.archived == False) .where(Chat.user_id == user_id) - .order_by(Chat.timestamp.desc()) + .order_by(Chat.updated_at.desc()) ] def get_chat_by_id(self, id: str) -> Optional[ChatModel]: diff --git a/backend/apps/web/routers/chats.py b/backend/apps/web/routers/chats.py index 8eb89aa5..678c9aea 100644 --- a/backend/apps/web/routers/chats.py +++ b/backend/apps/web/routers/chats.py @@ -47,6 +47,18 @@ async def get_user_chats( return Chats.get_chat_lists_by_user_id(user.id, skip, limit) +############################ +# GetArchivedChats +############################ + + +@router.get("/archived", response_model=List[ChatTitleIdResponse]) +async def get_archived_user_chats( + user=Depends(get_current_user), skip: int = 0, limit: int = 50 +): + return Chats.get_archived_chat_lists_by_user_id(user.id, skip, limit) + + ############################ # GetAllChats ############################ diff --git a/src/lib/apis/chats/index.ts b/src/lib/apis/chats/index.ts index 321a5c78..5a9071bb 100644 --- a/src/lib/apis/chats/index.ts +++ b/src/lib/apis/chats/index.ts @@ -62,6 +62,37 @@ export const getChatList = async (token: string = '') => { return res; }; +export const getArchivedChatList = async (token: string = '') => { + let error = null; + + const res = await fetch(`${WEBUI_API_BASE_URL}/chats/archived`, { + method: 'GET', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .then((json) => { + return json; + }) + .catch((err) => { + error = err; + console.log(err); + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const getAllChats = async (token: string) => { let error = null; diff --git a/src/lib/components/common/Modal.svelte b/src/lib/components/common/Modal.svelte index d817d67b..6d8b8299 100644 --- a/src/lib/components/common/Modal.svelte +++ b/src/lib/components/common/Modal.svelte @@ -15,8 +15,10 @@ return 'w-[16rem]'; } else if (size === 'sm') { return 'w-[30rem]'; - } else { + } else if (size === 'md') { return 'w-[44rem]'; + } else { + return 'w-[48rem]'; } }; diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte index f0531942..74abba32 100644 --- a/src/lib/components/layout/Sidebar.svelte +++ b/src/lib/components/layout/Sidebar.svelte @@ -27,6 +27,7 @@ import ChatMenu from './Sidebar/ChatMenu.svelte'; import ShareChatModal from '../chat/ShareChatModal.svelte'; import ArchiveBox from '../icons/ArchiveBox.svelte'; + import ArchivedChatsModal from './Sidebar/ArchivedChatsModal.svelte'; let show = false; let navElement; @@ -42,6 +43,7 @@ let chatTitleEditId = null; let chatTitle = ''; + let showArchivedChatsModal = false; let showShareChatModal = false; let showDropdown = false; let isEditing = false; @@ -148,6 +150,7 @@ +