From db0779dd022e16d9cac0596f558efebcc313c5ed Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Wed, 16 Oct 2024 15:18:45 -0700 Subject: [PATCH] Session id: int -> UUID (#2814) * session id: int -> UUID * nit * validated * validated downgrade + upgrade + all functionality * nit * minor nit * fix test case --- .../6756efa39ada_id_uuid_for_chat_session.py | 153 ++++++++++++++++++ backend/danswer/chat/chat_utils.py | 3 +- backend/danswer/db/chat.py | 22 +-- backend/danswer/db/models.py | 11 +- .../danswer/server/features/folder/models.py | 4 +- .../server/query_and_chat/chat_backend.py | 7 +- .../danswer/server/query_and_chat/models.py | 22 ++- .../server/query_and_chat/query_backend.py | 4 +- backend/danswer/tools/models.py | 3 +- backend/ee/danswer/db/usage_export.py | 2 +- .../danswer/server/query_and_chat/models.py | 4 +- .../ee/danswer/server/query_history/api.py | 9 +- .../server/reporting/usage_export_models.py | 3 +- .../integration/common_utils/managers/chat.py | 3 +- .../integration/common_utils/test_models.py | 4 +- .../danswer/tools/custom/test_custom_tools.py | 3 +- web/src/app/chat/ChatPage.tsx | 44 +++-- web/src/app/chat/folders/FolderList.tsx | 9 +- web/src/app/chat/folders/FolderManagement.tsx | 4 +- web/src/app/chat/input/ChatInputBar.tsx | 2 +- web/src/app/chat/interfaces.ts | 8 +- web/src/app/chat/lib.tsx | 15 +- .../app/chat/modal/ShareChatSessionModal.tsx | 8 +- .../app/chat/modal/configuration/LlmTab.tsx | 2 +- web/src/app/chat/sessionSidebar/PagesTab.tsx | 7 +- web/src/app/chat/shared/[chatId]/page.tsx | 5 +- web/src/components/search/SearchSection.tsx | 13 +- web/src/lib/chat/fetchChatData.ts | 6 +- web/src/lib/search/interfaces.ts | 2 +- 29 files changed, 276 insertions(+), 106 deletions(-) create mode 100644 backend/alembic/versions/6756efa39ada_id_uuid_for_chat_session.py diff --git a/backend/alembic/versions/6756efa39ada_id_uuid_for_chat_session.py b/backend/alembic/versions/6756efa39ada_id_uuid_for_chat_session.py new file mode 100644 index 000000000..057521d0f --- /dev/null +++ b/backend/alembic/versions/6756efa39ada_id_uuid_for_chat_session.py @@ -0,0 +1,153 @@ +""" +Revision ID: 6756efa39ada +Revises: 5d12a446f5c0 +Create Date: 2024-10-15 17:47:44.108537 +""" +from alembic import op +import sqlalchemy as sa + +revision = "6756efa39ada" +down_revision = "5d12a446f5c0" +branch_labels = None +depends_on = None + +""" +Migrate chat_session and chat_message tables to use UUID primary keys. + +This script: +1. Adds UUID columns to chat_session and chat_message +2. Populates new columns with UUIDs +3. Updates foreign key relationships +4. Removes old integer ID columns + +Note: Downgrade will assign new integer IDs, not restore original ones. +""" + + +def upgrade() -> None: + op.execute("CREATE EXTENSION IF NOT EXISTS pgcrypto;") + + op.add_column( + "chat_session", + sa.Column( + "new_id", + sa.UUID(as_uuid=True), + server_default=sa.text("gen_random_uuid()"), + nullable=False, + ), + ) + + op.execute("UPDATE chat_session SET new_id = gen_random_uuid();") + + op.add_column( + "chat_message", + sa.Column("new_chat_session_id", sa.UUID(as_uuid=True), nullable=True), + ) + + op.execute( + """ + UPDATE chat_message + SET new_chat_session_id = cs.new_id + FROM chat_session cs + WHERE chat_message.chat_session_id = cs.id; + """ + ) + + op.drop_constraint( + "chat_message_chat_session_id_fkey", "chat_message", type_="foreignkey" + ) + + op.drop_column("chat_message", "chat_session_id") + op.alter_column( + "chat_message", "new_chat_session_id", new_column_name="chat_session_id" + ) + + op.drop_constraint("chat_session_pkey", "chat_session", type_="primary") + op.drop_column("chat_session", "id") + op.alter_column("chat_session", "new_id", new_column_name="id") + + op.create_primary_key("chat_session_pkey", "chat_session", ["id"]) + + op.create_foreign_key( + "chat_message_chat_session_id_fkey", + "chat_message", + "chat_session", + ["chat_session_id"], + ["id"], + ondelete="CASCADE", + ) + + +def downgrade() -> None: + op.drop_constraint( + "chat_message_chat_session_id_fkey", "chat_message", type_="foreignkey" + ) + + op.add_column( + "chat_session", + sa.Column("old_id", sa.Integer, autoincrement=True, nullable=True), + ) + + op.execute("CREATE SEQUENCE chat_session_old_id_seq OWNED BY chat_session.old_id;") + op.execute( + "ALTER TABLE chat_session ALTER COLUMN old_id SET DEFAULT nextval('chat_session_old_id_seq');" + ) + + op.execute( + "UPDATE chat_session SET old_id = nextval('chat_session_old_id_seq') WHERE old_id IS NULL;" + ) + + op.alter_column("chat_session", "old_id", nullable=False) + + op.drop_constraint("chat_session_pkey", "chat_session", type_="primary") + op.create_primary_key("chat_session_pkey", "chat_session", ["old_id"]) + + op.add_column( + "chat_message", + sa.Column("old_chat_session_id", sa.Integer, nullable=True), + ) + + op.execute( + """ + UPDATE chat_message + SET old_chat_session_id = cs.old_id + FROM chat_session cs + WHERE chat_message.chat_session_id = cs.id; + """ + ) + + op.drop_column("chat_message", "chat_session_id") + op.alter_column( + "chat_message", "old_chat_session_id", new_column_name="chat_session_id" + ) + + op.create_foreign_key( + "chat_message_chat_session_id_fkey", + "chat_message", + "chat_session", + ["chat_session_id"], + ["old_id"], + ondelete="CASCADE", + ) + + op.drop_column("chat_session", "id") + op.alter_column("chat_session", "old_id", new_column_name="id") + + op.alter_column( + "chat_session", + "id", + type_=sa.Integer(), + existing_type=sa.Integer(), + existing_nullable=False, + existing_server_default=False, + ) + + # Rename the sequence + op.execute("ALTER SEQUENCE chat_session_old_id_seq RENAME TO chat_session_id_seq;") + + # Update the default value to use the renamed sequence + op.alter_column( + "chat_session", + "id", + server_default=sa.text("nextval('chat_session_id_seq'::regclass)"), + ) diff --git a/backend/danswer/chat/chat_utils.py b/backend/danswer/chat/chat_utils.py index cf0fa28c1..f5961010b 100644 --- a/backend/danswer/chat/chat_utils.py +++ b/backend/danswer/chat/chat_utils.py @@ -1,5 +1,6 @@ import re from typing import cast +from uuid import UUID from fastapi.datastructures import Headers from sqlalchemy.orm import Session @@ -34,7 +35,7 @@ def llm_doc_from_inference_section(inference_section: InferenceSection) -> LlmDo def create_chat_chain( - chat_session_id: int, + chat_session_id: UUID, db_session: Session, prefetch_tool_calls: bool = True, # Optional id at which we finish processing diff --git a/backend/danswer/db/chat.py b/backend/danswer/db/chat.py index feb2e2b4b..d885e1efd 100644 --- a/backend/danswer/db/chat.py +++ b/backend/danswer/db/chat.py @@ -43,7 +43,7 @@ logger = setup_logger() def get_chat_session_by_id( - chat_session_id: int, + chat_session_id: UUID, user_id: UUID | None, db_session: Session, include_deleted: bool = False, @@ -87,9 +87,9 @@ def get_chat_sessions_by_slack_thread_id( def get_valid_messages_from_query_sessions( - chat_session_ids: list[int], + chat_session_ids: list[UUID], db_session: Session, -) -> dict[int, str]: +) -> dict[UUID, str]: user_message_subquery = ( select( ChatMessage.chat_session_id, func.min(ChatMessage.id).label("user_msg_id") @@ -196,7 +196,7 @@ def delete_orphaned_search_docs(db_session: Session) -> None: def delete_messages_and_files_from_chat_session( - chat_session_id: int, db_session: Session + chat_session_id: UUID, db_session: Session ) -> None: # Select messages older than cutoff_time with files messages_with_files = db_session.execute( @@ -253,7 +253,7 @@ def create_chat_session( def update_chat_session( db_session: Session, user_id: UUID | None, - chat_session_id: int, + chat_session_id: UUID, description: str | None = None, sharing_status: ChatSessionSharedStatus | None = None, ) -> ChatSession: @@ -276,7 +276,7 @@ def update_chat_session( def delete_chat_session( user_id: UUID | None, - chat_session_id: int, + chat_session_id: UUID, db_session: Session, hard_delete: bool = HARD_DELETE_CHATS, ) -> None: @@ -337,7 +337,7 @@ def get_chat_message( def get_chat_messages_by_sessions( - chat_session_ids: list[int], + chat_session_ids: list[UUID], user_id: UUID | None, db_session: Session, skip_permission_check: bool = False, @@ -370,7 +370,7 @@ def get_search_docs_for_chat_message( def get_chat_messages_by_session( - chat_session_id: int, + chat_session_id: UUID, user_id: UUID | None, db_session: Session, skip_permission_check: bool = False, @@ -397,7 +397,7 @@ def get_chat_messages_by_session( def get_or_create_root_message( - chat_session_id: int, + chat_session_id: UUID, db_session: Session, ) -> ChatMessage: try: @@ -433,7 +433,7 @@ def get_or_create_root_message( def reserve_message_id( db_session: Session, - chat_session_id: int, + chat_session_id: UUID, parent_message: int, message_type: MessageType, ) -> int: @@ -460,7 +460,7 @@ def reserve_message_id( def create_new_chat_message( - chat_session_id: int, + chat_session_id: UUID, parent_message: ChatMessage, message: str, prompt_id: int | None, diff --git a/backend/danswer/db/models.py b/backend/danswer/db/models.py index c7cbbe9e8..9f8f1e237 100644 --- a/backend/danswer/db/models.py +++ b/backend/danswer/db/models.py @@ -5,9 +5,12 @@ from typing import Any from typing import Literal from typing import NotRequired from typing import Optional +from uuid import uuid4 from typing_extensions import TypedDict # noreorder from uuid import UUID +from sqlalchemy.dialects.postgresql import UUID as PGUUID + from fastapi_users_db_sqlalchemy import SQLAlchemyBaseOAuthAccountTableUUID from fastapi_users_db_sqlalchemy import SQLAlchemyBaseUserTableUUID from fastapi_users_db_sqlalchemy.access_token import SQLAlchemyBaseAccessTokenTableUUID @@ -920,7 +923,9 @@ class ToolCall(Base): class ChatSession(Base): __tablename__ = "chat_session" - id: Mapped[int] = mapped_column(primary_key=True) + id: Mapped[UUID] = mapped_column( + PGUUID(as_uuid=True), primary_key=True, default=uuid4 + ) user_id: Mapped[UUID | None] = mapped_column( ForeignKey("user.id", ondelete="CASCADE"), nullable=True ) @@ -990,7 +995,9 @@ class ChatMessage(Base): __tablename__ = "chat_message" id: Mapped[int] = mapped_column(primary_key=True) - chat_session_id: Mapped[int] = mapped_column(ForeignKey("chat_session.id")) + chat_session_id: Mapped[UUID] = mapped_column( + PGUUID(as_uuid=True), ForeignKey("chat_session.id") + ) alternate_assistant_id = mapped_column( Integer, ForeignKey("persona.id"), nullable=True diff --git a/backend/danswer/server/features/folder/models.py b/backend/danswer/server/features/folder/models.py index d7b161414..3f7e1304c 100644 --- a/backend/danswer/server/features/folder/models.py +++ b/backend/danswer/server/features/folder/models.py @@ -1,3 +1,5 @@ +from uuid import UUID + from pydantic import BaseModel from danswer.server.query_and_chat.models import ChatSessionDetails @@ -23,7 +25,7 @@ class FolderUpdateRequest(BaseModel): class FolderChatSessionRequest(BaseModel): - chat_session_id: int + chat_session_id: UUID class DeleteFolderOptions(BaseModel): diff --git a/backend/danswer/server/query_and_chat/chat_backend.py b/backend/danswer/server/query_and_chat/chat_backend.py index c26bc9c5c..5b0cc30a1 100644 --- a/backend/danswer/server/query_and_chat/chat_backend.py +++ b/backend/danswer/server/query_and_chat/chat_backend.py @@ -4,6 +4,7 @@ import uuid from collections.abc import Callable from collections.abc import Generator from typing import Tuple +from uuid import UUID from fastapi import APIRouter from fastapi import Depends @@ -131,7 +132,7 @@ def update_chat_session_model( @router.get("/get-chat-session/{session_id}") def get_chat_session( - session_id: int, + session_id: UUID, is_shared: bool = False, user: User | None = Depends(current_user), db_session: Session = Depends(get_session), @@ -254,7 +255,7 @@ def rename_chat_session( @router.patch("/chat-session/{session_id}") def patch_chat_session( - session_id: int, + session_id: UUID, chat_session_update_req: ChatSessionUpdateRequest, user: User | None = Depends(current_user), db_session: Session = Depends(get_session), @@ -271,7 +272,7 @@ def patch_chat_session( @router.delete("/delete-chat-session/{session_id}") def delete_chat_session_by_id( - session_id: int, + session_id: UUID, user: User | None = Depends(current_user), db_session: Session = Depends(get_session), ) -> None: diff --git a/backend/danswer/server/query_and_chat/models.py b/backend/danswer/server/query_and_chat/models.py index c9109b141..42f4100a2 100644 --- a/backend/danswer/server/query_and_chat/models.py +++ b/backend/danswer/server/query_and_chat/models.py @@ -1,5 +1,6 @@ from datetime import datetime from typing import Any +from uuid import UUID from pydantic import BaseModel from pydantic import model_validator @@ -34,7 +35,7 @@ class SimpleQueryRequest(BaseModel): class UpdateChatSessionThreadRequest(BaseModel): # If not specified, use Danswer default persona - chat_session_id: int + chat_session_id: UUID new_alternate_model: str @@ -45,7 +46,7 @@ class ChatSessionCreationRequest(BaseModel): class CreateChatSessionID(BaseModel): - chat_session_id: int + chat_session_id: UUID class ChatFeedbackRequest(BaseModel): @@ -75,7 +76,7 @@ Currently the different branches are generated by changing the search query class CreateChatMessageRequest(ChunkContext): """Before creating messages, be sure to create a chat_session and get an id""" - chat_session_id: int + chat_session_id: UUID # This is the primary-key (unique identifier) for the previous message of the tree parent_message_id: int | None # New message contents @@ -115,13 +116,18 @@ class CreateChatMessageRequest(ChunkContext): ) return self + def model_dump(self, *args: Any, **kwargs: Any) -> dict[str, Any]: + data = super().model_dump(*args, **kwargs) + data["chat_session_id"] = str(data["chat_session_id"]) + return data + class ChatMessageIdentifier(BaseModel): message_id: int class ChatRenameRequest(BaseModel): - chat_session_id: int + chat_session_id: UUID name: str | None = None @@ -134,7 +140,7 @@ class RenameChatSessionResponse(BaseModel): class ChatSessionDetails(BaseModel): - id: int + id: UUID name: str persona_id: int | None = None time_created: str @@ -175,7 +181,7 @@ class ChatMessageDetail(BaseModel): overridden_model: str | None alternate_assistant_id: int | None = None # Dict mapping citation number to db_doc_id - chat_session_id: int | None = None + chat_session_id: UUID | None = None citations: dict[int, int] | None = None files: list[FileDescriptor] tool_calls: list[ToolCallFinalResult] @@ -187,14 +193,14 @@ class ChatMessageDetail(BaseModel): class SearchSessionDetailResponse(BaseModel): - search_session_id: int + search_session_id: UUID description: str documents: list[SearchDoc] messages: list[ChatMessageDetail] class ChatSessionDetailResponse(BaseModel): - chat_session_id: int + chat_session_id: UUID description: str persona_id: int | None = None persona_name: str | None diff --git a/backend/danswer/server/query_and_chat/query_backend.py b/backend/danswer/server/query_and_chat/query_backend.py index 96f674276..703ef2c04 100644 --- a/backend/danswer/server/query_and_chat/query_backend.py +++ b/backend/danswer/server/query_and_chat/query_backend.py @@ -1,3 +1,5 @@ +from uuid import UUID + from fastapi import APIRouter from fastapi import Depends from fastapi import HTTPException @@ -186,7 +188,7 @@ def get_user_search_sessions( @basic_router.get("/search-session/{session_id}") def get_search_session( - session_id: int, + session_id: UUID, is_shared: bool = False, user: User | None = Depends(current_user), db_session: Session = Depends(get_session), diff --git a/backend/danswer/tools/models.py b/backend/danswer/tools/models.py index 6317a95e2..4f56aecd3 100644 --- a/backend/danswer/tools/models.py +++ b/backend/danswer/tools/models.py @@ -1,4 +1,5 @@ from typing import Any +from uuid import UUID from pydantic import BaseModel from pydantic import model_validator @@ -40,7 +41,7 @@ class ToolCallFinalResult(ToolCallKickoff): class DynamicSchemaInfo(BaseModel): - chat_session_id: int | None + chat_session_id: UUID | None message_id: int | None diff --git a/backend/ee/danswer/db/usage_export.py b/backend/ee/danswer/db/usage_export.py index bf53362e9..ac9535bad 100644 --- a/backend/ee/danswer/db/usage_export.py +++ b/backend/ee/danswer/db/usage_export.py @@ -42,7 +42,7 @@ def get_empty_chat_messages_entries__paginated( message_skeletons.append( ChatMessageSkeleton( - message_id=chat_session.id, + message_id=message.id, chat_session_id=chat_session.id, user_id=str(chat_session.user_id) if chat_session.user_id else None, flow_type=flow_type, diff --git a/backend/ee/danswer/server/query_and_chat/models.py b/backend/ee/danswer/server/query_and_chat/models.py index ec9db73ec..052be683e 100644 --- a/backend/ee/danswer/server/query_and_chat/models.py +++ b/backend/ee/danswer/server/query_and_chat/models.py @@ -1,3 +1,5 @@ +from uuid import UUID + from pydantic import BaseModel from pydantic import Field @@ -36,7 +38,7 @@ class BasicCreateChatMessageRequest(ChunkContext): Note, for simplicity this option only allows for a single linear chain of messages """ - chat_session_id: int + chat_session_id: UUID # New message contents message: str # Defaults to using retrieval with no additional filters diff --git a/backend/ee/danswer/server/query_history/api.py b/backend/ee/danswer/server/query_history/api.py index 3fc0a9815..1411f973e 100644 --- a/backend/ee/danswer/server/query_history/api.py +++ b/backend/ee/danswer/server/query_history/api.py @@ -4,6 +4,7 @@ from datetime import datetime from datetime import timedelta from datetime import timezone from typing import Literal +from uuid import UUID from fastapi import APIRouter from fastapi import Depends @@ -83,7 +84,7 @@ class MessageSnapshot(BaseModel): class ChatSessionMinimal(BaseModel): - id: int + id: UUID user_email: str name: str | None first_user_message: str @@ -95,7 +96,7 @@ class ChatSessionMinimal(BaseModel): class ChatSessionSnapshot(BaseModel): - id: int + id: UUID user_email: str name: str | None messages: list[MessageSnapshot] @@ -105,7 +106,7 @@ class ChatSessionSnapshot(BaseModel): class QuestionAnswerPairSnapshot(BaseModel): - chat_session_id: int + chat_session_id: UUID # 1-indexed message number in the chat_session # e.g. the first message pair in the chat_session is 1, the second is 2, etc. message_pair_num: int @@ -350,7 +351,7 @@ def get_chat_session_history( @router.get("/admin/chat-session-history/{chat_session_id}") def get_chat_session_admin( - chat_session_id: int, + chat_session_id: UUID, _: User | None = Depends(current_admin_user), db_session: Session = Depends(get_session), ) -> ChatSessionSnapshot: diff --git a/backend/ee/danswer/server/reporting/usage_export_models.py b/backend/ee/danswer/server/reporting/usage_export_models.py index 98d9021f8..21cd104e8 100644 --- a/backend/ee/danswer/server/reporting/usage_export_models.py +++ b/backend/ee/danswer/server/reporting/usage_export_models.py @@ -1,5 +1,6 @@ from datetime import datetime from enum import Enum +from uuid import UUID from pydantic import BaseModel @@ -14,7 +15,7 @@ class FlowType(str, Enum): class ChatMessageSkeleton(BaseModel): message_id: int - chat_session_id: int + chat_session_id: UUID user_id: str | None flow_type: FlowType time_sent: datetime diff --git a/backend/tests/integration/common_utils/managers/chat.py b/backend/tests/integration/common_utils/managers/chat.py index 696baa2ad..a8643e9e8 100644 --- a/backend/tests/integration/common_utils/managers/chat.py +++ b/backend/tests/integration/common_utils/managers/chat.py @@ -1,4 +1,5 @@ import json +from uuid import UUID import requests from requests.models import Response @@ -44,7 +45,7 @@ class ChatSessionManager: @staticmethod def send_message( - chat_session_id: int, + chat_session_id: UUID, message: str, parent_message_id: int | None = None, user_performing_action: DATestUser | None = None, diff --git a/backend/tests/integration/common_utils/test_models.py b/backend/tests/integration/common_utils/test_models.py index ca573663e..af7cd882a 100644 --- a/backend/tests/integration/common_utils/test_models.py +++ b/backend/tests/integration/common_utils/test_models.py @@ -123,14 +123,14 @@ class DATestPersona(BaseModel): # class DATestChatSession(BaseModel): - id: int + id: UUID persona_id: int description: str class DATestChatMessage(BaseModel): id: str | None = None - chat_session_id: int + chat_session_id: UUID parent_message_id: str | None message: str response: str diff --git a/backend/tests/unit/danswer/tools/custom/test_custom_tools.py b/backend/tests/unit/danswer/tools/custom/test_custom_tools.py index fcc48b98d..5b07e21bb 100644 --- a/backend/tests/unit/danswer/tools/custom/test_custom_tools.py +++ b/backend/tests/unit/danswer/tools/custom/test_custom_tools.py @@ -1,4 +1,5 @@ import unittest +import uuid from typing import Any from unittest.mock import patch @@ -73,7 +74,7 @@ class TestCustomTool(unittest.TestCase): } validate_openapi_schema(self.openapi_schema) self.dynamic_schema_info: DynamicSchemaInfo = DynamicSchemaInfo( - chat_session_id=10, message_id=20 + chat_session_id=uuid.uuid4(), message_id=20 ) @patch("danswer.tools.custom.custom_tool.requests.request") diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index f218f7fab..bed27f6a1 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -145,19 +145,17 @@ export function ChatPage({ const existingChatIdRaw = searchParams.get("chatId"); const currentPersonaId = searchParams.get(SEARCH_PARAM_NAMES.PERSONA_ID); - const existingChatSessionId = existingChatIdRaw - ? parseInt(existingChatIdRaw) - : null; + const existingChatSessionId = existingChatIdRaw ? existingChatIdRaw : null; const selectedChatSession = chatSessions.find( (chatSession) => chatSession.id === existingChatSessionId ); - const chatSessionIdRef = useRef(existingChatSessionId); + const chatSessionIdRef = useRef(existingChatSessionId); // Only updates on session load (ie. rename / switching chat session) // Useful for determining which session has been loaded (i.e. still on `new, empty session` or `previous session`) - const loadedIdSessionRef = useRef(existingChatSessionId); + const loadedIdSessionRef = useRef(existingChatSessionId); // Assistants in order const { finalAssistants } = useMemo(() => { @@ -448,11 +446,11 @@ export function ChatPage({ ); const [completeMessageDetail, setCompleteMessageDetail] = useState< - Map> + Map> >(new Map()); const updateCompleteMessageDetail = ( - sessionId: number | null, + sessionId: string | null, messageMap: Map ) => { setCompleteMessageDetail((prevState) => { @@ -463,13 +461,13 @@ export function ChatPage({ }; const currentMessageMap = ( - messageDetail: Map> + messageDetail: Map> ) => { return ( messageDetail.get(chatSessionIdRef.current) || new Map() ); }; - const currentSessionId = (): number => { + const currentSessionId = (): string => { return chatSessionIdRef.current!; }; @@ -484,7 +482,7 @@ export function ChatPage({ // if calling this function repeatedly with short delay, stay may not update in time // and result in weird behavipr completeMessageMapOverride?: Map | null; - chatSessionId?: number; + chatSessionId?: string; replacementsMap?: Map | null; makeLatestChildMessage?: boolean; }) => { @@ -559,23 +557,23 @@ export function ChatPage({ const [submittedMessage, setSubmittedMessage] = useState(""); - const [chatState, setChatState] = useState>( + const [chatState, setChatState] = useState>( new Map([[chatSessionIdRef.current, "input"]]) ); const [regenerationState, setRegenerationState] = useState< - Map + Map >(new Map([[null, null]])); const [abortControllers, setAbortControllers] = useState< - Map + Map >(new Map()); // Updates "null" session values to new session id for // regeneration, chat, and abort controller state, messagehistory - const updateStatesWithNewSessionId = (newSessionId: number) => { + const updateStatesWithNewSessionId = (newSessionId: string) => { const updateState = ( - setState: Dispatch>>, + setState: Dispatch>>, defaultValue?: any ) => { setState((prevState) => { @@ -610,7 +608,7 @@ export function ChatPage({ chatSessionIdRef.current = newSessionId; }; - const updateChatState = (newState: ChatState, sessionId?: number | null) => { + const updateChatState = (newState: ChatState, sessionId?: string | null) => { setChatState((prevState) => { const newChatState = new Map(prevState); newChatState.set( @@ -635,7 +633,7 @@ export function ChatPage({ const updateRegenerationState = ( newState: RegenerationState | null, - sessionId?: number | null + sessionId?: string | null ) => { setRegenerationState((prevState) => { const newRegenerationState = new Map(prevState); @@ -647,18 +645,18 @@ export function ChatPage({ }); }; - const resetRegenerationState = (sessionId?: number | null) => { + const resetRegenerationState = (sessionId?: string | null) => { updateRegenerationState(null, sessionId); }; const currentRegenerationState = (): RegenerationState | null => { return regenerationState.get(currentSessionId()) || null; }; - const [canContinue, setCanContinue] = useState>( + const [canContinue, setCanContinue] = useState>( new Map([[null, false]]) ); - const updateCanContinue = (newState: boolean, sessionId?: number | null) => { + const updateCanContinue = (newState: boolean, sessionId?: string | null) => { setCanContinue((prevState) => { const newCanContinueState = new Map(prevState); newCanContinueState.set( @@ -1003,7 +1001,7 @@ export function ChatPage({ setAlternativeGeneratingAssistant(alternativeAssistantOverride); clientScrollToBottom(); - let currChatSessionId: number; + let currChatSessionId: string; const isNewSession = chatSessionIdRef.current === null; const searchParamBasedChatSessionName = searchParams.get(SEARCH_PARAM_NAMES.TITLE) || null; @@ -1014,7 +1012,7 @@ export function ChatPage({ searchParamBasedChatSessionName ); } else { - currChatSessionId = chatSessionIdRef.current as number; + currChatSessionId = chatSessionIdRef.current as string; } frozenSessionId = currChatSessionId; @@ -1598,7 +1596,7 @@ export function ChatPage({ } const [visibleRange, setVisibleRange] = useState< - Map + Map >(() => { const initialRange: VisibleRange = { start: 0, diff --git a/web/src/app/chat/folders/FolderList.tsx b/web/src/app/chat/folders/FolderList.tsx index 01e69b3a1..047cd1e9b 100644 --- a/web/src/app/chat/folders/FolderList.tsx +++ b/web/src/app/chat/folders/FolderList.tsx @@ -30,7 +30,7 @@ const FolderItem = ({ initiallySelected, }: { folder: Folder; - currentChatId?: number; + currentChatId?: string; isInitiallyExpanded: boolean; initiallySelected: boolean; }) => { @@ -145,10 +145,7 @@ const FolderItem = ({ const handleDrop = async (event: React.DragEvent) => { event.preventDefault(); setIsDragOver(false); - const chatSessionId = parseInt( - event.dataTransfer.getData(CHAT_SESSION_ID_KEY), - 10 - ); + const chatSessionId = event.dataTransfer.getData(CHAT_SESSION_ID_KEY); try { await addChatToFolder(folder.folder_id, chatSessionId); router.refresh(); // Refresh to show the updated folder contents @@ -302,7 +299,7 @@ export const FolderList = ({ newFolderId, }: { folders: Folder[]; - currentChatId?: number; + currentChatId?: string; openedFolders?: { [key: number]: boolean }; newFolderId: number | null; }) => { diff --git a/web/src/app/chat/folders/FolderManagement.tsx b/web/src/app/chat/folders/FolderManagement.tsx index 7e5abca0a..417bb903f 100644 --- a/web/src/app/chat/folders/FolderManagement.tsx +++ b/web/src/app/chat/folders/FolderManagement.tsx @@ -17,7 +17,7 @@ export async function createFolder(folderName: string): Promise { // Function to add a chat session to a folder export async function addChatToFolder( folderId: number, - chatSessionId: number + chatSessionId: string ): Promise { const response = await fetch(`/api/folder/${folderId}/add-chat-session`, { method: "POST", @@ -34,7 +34,7 @@ export async function addChatToFolder( // Function to remove a chat session from a folder export async function removeChatFromFolder( folderId: number, - chatSessionId: number + chatSessionId: string ): Promise { const response = await fetch(`/api/folder/${folderId}/remove-chat-session`, { method: "POST", diff --git a/web/src/app/chat/input/ChatInputBar.tsx b/web/src/app/chat/input/ChatInputBar.tsx index cce1bba53..e6e220f9f 100644 --- a/web/src/app/chat/input/ChatInputBar.tsx +++ b/web/src/app/chat/input/ChatInputBar.tsx @@ -86,7 +86,7 @@ export function ChatInputBar({ setFiles: (files: FileDescriptor[]) => void; handleFileUpload: (files: File[]) => void; textAreaRef: React.RefObject; - chatSessionId?: number; + chatSessionId?: string; refreshUser: () => void; }) { useEffect(() => { diff --git a/web/src/app/chat/interfaces.ts b/web/src/app/chat/interfaces.ts index dfc24aaa6..5e25566a7 100644 --- a/web/src/app/chat/interfaces.ts +++ b/web/src/app/chat/interfaces.ts @@ -60,7 +60,7 @@ export interface ToolCallFinalResult { } export interface ChatSession { - id: number; + id: string; name: string; persona_id: number; time_created: string; @@ -70,7 +70,7 @@ export interface ChatSession { } export interface SearchSession { - search_session_id: number; + search_session_id: string; documents: SearchDanswerDocument[]; messages: BackendMessage[]; description: string; @@ -97,7 +97,7 @@ export interface Message { } export interface BackendChatSession { - chat_session_id: number; + chat_session_id: string; description: string; persona_id: number; persona_name: string; @@ -110,7 +110,7 @@ export interface BackendChatSession { export interface BackendMessage { message_id: number; comments: any; - chat_session_id: number; + chat_session_id: string; parent_message: number | null; latest_child_message: number | null; message: string; diff --git a/web/src/app/chat/lib.tsx b/web/src/app/chat/lib.tsx index 7ce86ac2c..3fbb9397a 100644 --- a/web/src/app/chat/lib.tsx +++ b/web/src/app/chat/lib.tsx @@ -55,7 +55,7 @@ export function getChatRetentionInfo( } export async function updateModelOverrideForChatSession( - chatSessionId: number, + chatSessionId: string, newAlternateModel: string ) { const response = await fetch("/api/chat/update-chat-session-model", { @@ -74,7 +74,7 @@ export async function updateModelOverrideForChatSession( export async function createChatSession( personaId: number, description: string | null -): Promise { +): Promise { const createChatSessionResponse = await fetch( "/api/chat/create-chat-session", { @@ -131,7 +131,7 @@ export async function* sendMessage({ message: string; fileDescriptors: FileDescriptor[]; parentMessageId: number | null; - chatSessionId: number; + chatSessionId: string; promptId: number | null | undefined; filters: Filters | null; selectedDocumentIds: number[] | null; @@ -203,7 +203,7 @@ export async function* sendMessage({ yield* handleSSEStream(response); } -export async function nameChatSession(chatSessionId: number, message: string) { +export async function nameChatSession(chatSessionId: string, message: string) { const response = await fetch("/api/chat/rename-chat-session", { method: "PUT", headers: { @@ -252,7 +252,7 @@ export async function handleChatFeedback( return response; } export async function renameChatSession( - chatSessionId: number, + chatSessionId: string, newName: string ) { const response = await fetch(`/api/chat/rename-chat-session`, { @@ -269,7 +269,7 @@ export async function renameChatSession( return response; } -export async function deleteChatSession(chatSessionId: number) { +export async function deleteChatSession(chatSessionId: string) { const response = await fetch( `/api/chat/delete-chat-session/${chatSessionId}`, { @@ -348,6 +348,7 @@ export function getCitedDocumentsFromMessage(message: Message) { } export function groupSessionsByDateRange(chatSessions: ChatSession[]) { + console.log(chatSessions); const today = new Date(); today.setHours(0, 0, 0, 0); // Set to start of today for accurate comparison @@ -584,7 +585,7 @@ const PARAMS_TO_SKIP = [ export function buildChatUrl( existingSearchParams: ReadonlyURLSearchParams, - chatSessionId: number | null, + chatSessionId: string | null, personaId: number | null, search?: boolean ) { diff --git a/web/src/app/chat/modal/ShareChatSessionModal.tsx b/web/src/app/chat/modal/ShareChatSessionModal.tsx index e220fdb6d..16a9147b5 100644 --- a/web/src/app/chat/modal/ShareChatSessionModal.tsx +++ b/web/src/app/chat/modal/ShareChatSessionModal.tsx @@ -6,12 +6,12 @@ import { ChatSessionSharedStatus } from "../interfaces"; import { FiCopy } from "react-icons/fi"; import { CopyButton } from "@/components/CopyButton"; -function buildShareLink(chatSessionId: number) { +function buildShareLink(chatSessionId: string) { const baseUrl = `${window.location.protocol}//${window.location.host}`; return `${baseUrl}/chat/shared/${chatSessionId}`; } -async function generateShareLink(chatSessionId: number) { +async function generateShareLink(chatSessionId: string) { const response = await fetch(`/api/chat/chat-session/${chatSessionId}`, { method: "PATCH", headers: { @@ -26,7 +26,7 @@ async function generateShareLink(chatSessionId: number) { return null; } -async function deleteShareLink(chatSessionId: number) { +async function deleteShareLink(chatSessionId: string) { const response = await fetch(`/api/chat/chat-session/${chatSessionId}`, { method: "PATCH", headers: { @@ -44,7 +44,7 @@ export function ShareChatSessionModal({ onShare, onClose, }: { - chatSessionId: number; + chatSessionId: string; existingSharedStatus: ChatSessionSharedStatus; onShare?: (shared: boolean) => void; onClose: () => void; diff --git a/web/src/app/chat/modal/configuration/LlmTab.tsx b/web/src/app/chat/modal/configuration/LlmTab.tsx index 2cd40290d..2d05e12b2 100644 --- a/web/src/app/chat/modal/configuration/LlmTab.tsx +++ b/web/src/app/chat/modal/configuration/LlmTab.tsx @@ -14,7 +14,7 @@ interface LlmTabProps { llmOverrideManager: LlmOverrideManager; currentLlm: string; openModelSettings: () => void; - chatSessionId?: number; + chatSessionId?: string; close: () => void; currentAssistant: Persona; } diff --git a/web/src/app/chat/sessionSidebar/PagesTab.tsx b/web/src/app/chat/sessionSidebar/PagesTab.tsx index 8477e197d..be8de43ee 100644 --- a/web/src/app/chat/sessionSidebar/PagesTab.tsx +++ b/web/src/app/chat/sessionSidebar/PagesTab.tsx @@ -23,7 +23,7 @@ export function PagesTab({ }: { page: pageType; existingChats?: ChatSession[]; - currentChatId?: number; + currentChatId?: string; folders?: Folder[]; openedFolders?: { [key: number]: boolean }; closeSidebar?: () => void; @@ -44,10 +44,7 @@ export function PagesTab({ ) => { event.preventDefault(); setIsDragOver(false); // Reset drag over state on drop - const chatSessionId = parseInt( - event.dataTransfer.getData(CHAT_SESSION_ID_KEY), - 10 - ); + const chatSessionId = event.dataTransfer.getData(CHAT_SESSION_ID_KEY); const folderId = event.dataTransfer.getData(FOLDER_ID_KEY); if (folderId) { diff --git a/web/src/app/chat/shared/[chatId]/page.tsx b/web/src/app/chat/shared/[chatId]/page.tsx index 2e1b87a56..e620e85cd 100644 --- a/web/src/app/chat/shared/[chatId]/page.tsx +++ b/web/src/app/chat/shared/[chatId]/page.tsx @@ -48,7 +48,7 @@ export default async function Page({ params }: { params: { chatId: string } }) { const user = results[1] as User | null; const chatSession = results[2] as BackendChatSession | null; const assistantsResponse = results[3] as FetchAssistantsResponse | null; - const [availableAssistants, _] = assistantsResponse ?? [[], null]; + const [availableAssistants, error] = assistantsResponse ?? [[], null]; const authDisabled = authTypeMetadata?.authType === "disabled"; if (!authDisabled && !user) { @@ -58,7 +58,6 @@ export default async function Page({ params }: { params: { chatId: string } }) { if (user && !user.is_verified && authTypeMetadata?.requiresVerification) { return redirect("/auth/waiting-on-verification"); } - const persona: Persona = chatSession?.persona_id && availableAssistants?.length ? (availableAssistants.find((p) => p.id === chatSession.persona_id) ?? @@ -72,7 +71,7 @@ export default async function Page({ params }: { params: { chatId: string } }) {
- +
); diff --git a/web/src/components/search/SearchSection.tsx b/web/src/components/search/SearchSection.tsx index 8ff4fdd31..b1215a370 100644 --- a/web/src/components/search/SearchSection.tsx +++ b/web/src/components/search/SearchSection.tsx @@ -168,13 +168,10 @@ export const SearchSection = ({ }); const searchParams = useSearchParams(); - const existingSearchIdRaw = searchParams.get("searchId"); - const existingSearchessionId = existingSearchIdRaw - ? parseInt(existingSearchIdRaw) - : null; + const existingSearchessionId = searchParams.get("searchId"); useEffect(() => { - if (existingSearchIdRaw == null) { + if (existingSearchessionId == null) { return; } function extractFirstMessageByType( @@ -207,7 +204,7 @@ export const SearchSection = ({ quotes: null, selectedDocIndices: null, error: null, - messageId: existingSearchIdRaw ? parseInt(existingSearchIdRaw) : null, + messageId: searchSession.messages[0].message_id, suggestedFlowType: null, additional_relevance: undefined, }; @@ -219,7 +216,7 @@ export const SearchSection = ({ } } initialSessionFetch(); - }, [existingSearchessionId, existingSearchIdRaw]); + }, [existingSearchessionId]); // Overrides for default behavior that only last a single query const [defaultOverrides, setDefaultOverrides] = @@ -328,7 +325,7 @@ export const SearchSection = ({ }; const updateMessageAndThreadId = ( messageId: number, - chat_session_id: number + chat_session_id: string ) => { setSearchResponse((prevState) => ({ ...(prevState || initialSearchResponse), diff --git a/web/src/lib/chat/fetchChatData.ts b/web/src/lib/chat/fetchChatData.ts index c41887206..82b10887d 100644 --- a/web/src/lib/chat/fetchChatData.ts +++ b/web/src/lib/chat/fetchChatData.ts @@ -136,8 +136,10 @@ export async function fetchChatData(searchParams: { ); } - // Larger ID -> created later - chatSessions.sort((a, b) => (a.id > b.id ? -1 : 1)); + chatSessions.sort( + (a, b) => + new Date(b.time_created).getTime() - new Date(a.time_created).getTime() + ); let documentSets: DocumentSet[] = []; if (documentSetsResponse?.ok) { diff --git a/web/src/lib/search/interfaces.ts b/web/src/lib/search/interfaces.ts index 6983bd336..1129bc672 100644 --- a/web/src/lib/search/interfaces.ts +++ b/web/src/lib/search/interfaces.ts @@ -152,7 +152,7 @@ export interface SearchRequestArgs { updateError: (error: string) => void; updateMessageAndThreadId: ( messageId: number, - chat_session_id: number + chat_session_id: string ) => void; finishedSearching: () => void; updateComments: (comments: any) => void;