From ae9b556876182f1afa252eb0458ab7fa94633ce3 Mon Sep 17 00:00:00 2001 From: Weves Date: Sat, 30 Dec 2023 17:35:05 -0800 Subject: [PATCH] Revamp new chat screen for chat UI --- .../server/query_and_chat/chat_backend.py | 3 +- .../danswer/server/query_and_chat/models.py | 1 + web/src/app/chat/Chat.tsx | 67 +++-- web/src/app/chat/ChatIntro.tsx | 230 ++++++++++++++++++ web/src/app/chat/ChatPage.tsx | 9 +- web/src/app/chat/ChatPersonaSelector.tsx | 1 - web/src/app/chat/interfaces.ts | 3 + web/src/app/chat/page.tsx | 12 +- web/src/components/Modal.tsx | 4 +- 9 files changed, 303 insertions(+), 27 deletions(-) create mode 100644 web/src/app/chat/ChatIntro.tsx diff --git a/backend/danswer/server/query_and_chat/chat_backend.py b/backend/danswer/server/query_and_chat/chat_backend.py index d305ed868a..2a1714104f 100644 --- a/backend/danswer/server/query_and_chat/chat_backend.py +++ b/backend/danswer/server/query_and_chat/chat_backend.py @@ -67,7 +67,7 @@ def get_user_chat_sessions( @router.get("/get-chat-session/{session_id}") -def get_chat_session_messages( +def get_chat_session( session_id: int, user: User | None = Depends(current_user), db_session: Session = Depends(get_session), @@ -88,6 +88,7 @@ def get_chat_session_messages( return ChatSessionDetailResponse( chat_session_id=session_id, description=chat_session.description, + persona_id=chat_session.persona_id, messages=[ translate_db_message_to_chat_message_detail(msg) for msg in session_messages ], diff --git a/backend/danswer/server/query_and_chat/models.py b/backend/danswer/server/query_and_chat/models.py index d4bc602a63..035331a17b 100644 --- a/backend/danswer/server/query_and_chat/models.py +++ b/backend/danswer/server/query_and_chat/models.py @@ -145,6 +145,7 @@ class ChatMessageDetail(BaseModel): class ChatSessionDetailResponse(BaseModel): chat_session_id: int description: str + persona_id: int messages: list[ChatMessageDetail] diff --git a/web/src/app/chat/Chat.tsx b/web/src/app/chat/Chat.tsx index da66296e94..8cd1f20415 100644 --- a/web/src/app/chat/Chat.tsx +++ b/web/src/app/chat/Chat.tsx @@ -39,6 +39,7 @@ import { SelectedDocuments } from "./modifiers/SelectedDocuments"; import { usePopup } from "@/components/admin/connectors/Popup"; import { ResizableSection } from "@/components/resizable/ResizableSection"; import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader"; +import { ChatIntro } from "./ChatIntro"; const MAX_INPUT_HEIGHT = 200; @@ -48,6 +49,7 @@ export const Chat = ({ availableSources, availableDocumentSets, availablePersonas, + defaultSelectedPersonaId, documentSidebarInitialWidth, shouldhideBeforeScroll, }: { @@ -56,6 +58,7 @@ export const Chat = ({ availableSources: ValidSources[]; availableDocumentSets: DocumentSet[]; availablePersonas: Persona[]; + defaultSelectedPersonaId?: number; // what persona to default to documentSidebarInitialWidth?: number; shouldhideBeforeScroll?: boolean; }) => { @@ -76,6 +79,15 @@ export const Chat = ({ async function initialSessionFetch() { if (existingChatSessionId === null) { setIsFetchingChatMessages(false); + if (defaultSelectedPersonaId !== undefined) { + setSelectedPersona( + availablePersonas.find( + (persona) => persona.id === defaultSelectedPersonaId + ) + ); + } else { + setSelectedPersona(undefined); + } setMessageHistory([]); return; } @@ -85,6 +97,11 @@ export const Chat = ({ `/api/chat/get-chat-session/${existingChatSessionId}` ); const chatSession = (await response.json()) as BackendChatSession; + setSelectedPersona( + availablePersonas.find( + (persona) => persona.id === chatSession.persona_id + ) + ); const newMessageHistory = processRawChatHistory(chatSession.messages); setMessageHistory(newMessageHistory); @@ -126,8 +143,23 @@ export const Chat = ({ ? availablePersonas.find( (persona) => persona.id === existingChatSessionPersonaId ) - : availablePersonas[0] + : defaultSelectedPersonaId !== undefined + ? availablePersonas.find( + (persona) => persona.id === defaultSelectedPersonaId + ) + : undefined ); + const livePersona = selectedPersona || availablePersonas[0]; + + useEffect(() => { + if (messageHistory.length === 0) { + setSelectedPersona( + availablePersonas.find( + (persona) => persona.id === defaultSelectedPersonaId + ) + ); + } + }, [defaultSelectedPersonaId]); const filterManager = useFilters(); @@ -212,7 +244,7 @@ export const Chat = ({ let currChatSessionId: number; let isNewSession = chatSessionId === null; if (isNewSession) { - currChatSessionId = await createChatSession(selectedPersona?.id || 0); + currChatSessionId = await createChatSession(livePersona?.id || 0); } else { currChatSessionId = chatSessionId as number; } @@ -405,15 +437,16 @@ export const Chat = ({ className="w-full h-screen flex flex-col overflow-y-auto relative" ref={scrollableDivRef} > - {selectedPersona && ( + {livePersona && (
{ if (persona) { setSelectedPersona(persona); + router.push(`/chat?personaId=${persona.id}`); } }} /> @@ -424,23 +457,15 @@ export const Chat = ({ {messageHistory.length === 0 && !isFetchingChatMessages && !isStreaming && ( -
-
-
-
- Logo -
-
-
- What are you looking for today? -
-
-
+ { + setSelectedPersona(persona); + router.push(`/chat?personaId=${persona.id}`); + }} + /> )}
+
{title}
+
{description}
+
+ ); +} + +function AllPersonaOptionDisplay({ + availablePersonas, + handlePersonaSelect, + handleClose, +}: { + availablePersonas: Persona[]; + handlePersonaSelect: (persona: Persona) => void; + handleClose: () => void; +}) { + return ( + +
+
+

+
+
+ +
+
+ All Available Assistants +

+ +
+ +
+
+
+ {availablePersonas.map((persona) => ( +
{ + handleClose(); + handlePersonaSelect(persona); + }} + > + +
+ ))} +
+
+
+ ); +} + +export function ChatIntro({ + availableSources, + availablePersonas, + selectedPersona, + handlePersonaSelect, +}: { + availableSources: ValidSources[]; + availablePersonas: Persona[]; + selectedPersona?: Persona; + handlePersonaSelect: (persona: Persona) => void; +}) { + const [isAllPersonaOptionVisible, setIsAllPersonaOptionVisible] = + useState(false); + + const allSources = listSourceMetadata(); + const availableSourceMetadata = allSources.filter((source) => + availableSources.includes(source.internalName) + ); + + return ( + <> + {isAllPersonaOptionVisible && ( + setIsAllPersonaOptionVisible(false)} + availablePersonas={availablePersonas} + handlePersonaSelect={handlePersonaSelect} + /> + )} +
+ {selectedPersona ? ( +
+
+
+
+ Logo +
+
+ {selectedPersona?.name || "How can I help you today?"} +
+ {selectedPersona && ( +
{selectedPersona.description}
+ )} +
+
+ + +
+ {selectedPersona && selectedPersona.document_sets.length > 0 && ( +
+

+ Knowledge Sets:{" "} +

+ {selectedPersona.document_sets.map((documentSet) => ( +
+ +
+ +
+ {documentSet.name} + + } + popupContent={ +
+ +
+ {documentSet.description} +
+
+ } + direction="top" + /> +
+ ))} +
+ )} + {availableSources.length > 0 && ( +
+

+ Connected Sources:{" "} +

+
+ {availableSourceMetadata.map((sourceMetadata) => ( + +
+ {sourceMetadata.icon({})} +
+ {sourceMetadata.displayName} +
+ ))} +
+
+ )} +
+
+ ) : ( +
+
+
+ Logo +
+
+ +
+

+ Which assistant do you want to chat with today?{" "} +

+

+ Or ask a question immediately to use the{" "} + {availablePersonas[0].name} assistant. +

+
+ {availablePersonas + .slice(0, MAX_PERSONAS_TO_DISPLAY) + .map((persona) => ( +
handlePersonaSelect(persona)} + > + +
+ ))} +
+ {availablePersonas.length > MAX_PERSONAS_TO_DISPLAY && ( +
+
setIsAllPersonaOptionVisible(true)} + className="text-sm flex mx-auto p-1 hover:bg-hover-light rounded cursor-default" + > + See more +
+
+ )} +
+
+ )} +
+ + ); +} diff --git a/web/src/app/chat/ChatPage.tsx b/web/src/app/chat/ChatPage.tsx index a8fdd6f1d3..ae3d82835f 100644 --- a/web/src/app/chat/ChatPage.tsx +++ b/web/src/app/chat/ChatPage.tsx @@ -13,6 +13,7 @@ export function ChatLayout({ availableSources, availableDocumentSets, availablePersonas, + defaultSelectedPersonaId, documentSidebarInitialWidth, }: { user: User | null; @@ -20,12 +21,17 @@ export function ChatLayout({ availableSources: ValidSources[]; availableDocumentSets: DocumentSet[]; availablePersonas: Persona[]; + defaultSelectedPersonaId?: number; // what persona to default to documentSidebarInitialWidth?: number; }) { const searchParams = useSearchParams(); const chatIdRaw = searchParams.get("chatId"); const chatId = chatIdRaw ? parseInt(chatIdRaw) : null; + const selectedChatSession = chatSessions.find( + (chatSession) => chatSession.id === chatId + ); + return ( <>
@@ -37,10 +43,11 @@ export function ChatLayout({
diff --git a/web/src/app/chat/ChatPersonaSelector.tsx b/web/src/app/chat/ChatPersonaSelector.tsx index 7b26647afa..606610d661 100644 --- a/web/src/app/chat/ChatPersonaSelector.tsx +++ b/web/src/app/chat/ChatPersonaSelector.tsx @@ -1,6 +1,5 @@ import { Persona } from "@/app/admin/personas/interfaces"; import { FiCheck, FiChevronDown } from "react-icons/fi"; -import { FaRobot } from "react-icons/fa"; import { CustomDropdown } from "@/components/Dropdown"; function PersonaItem({ diff --git a/web/src/app/chat/interfaces.ts b/web/src/app/chat/interfaces.ts index 0f612000be..7eb9f50ce9 100644 --- a/web/src/app/chat/interfaces.ts +++ b/web/src/app/chat/interfaces.ts @@ -33,6 +33,9 @@ export interface Message { } export interface BackendChatSession { + chat_session_id: number; + description: string; + persona_id: number; messages: BackendMessage[]; } diff --git a/web/src/app/chat/page.tsx b/web/src/app/chat/page.tsx index 939a3bb0dc..3821dc0174 100644 --- a/web/src/app/chat/page.tsx +++ b/web/src/app/chat/page.tsx @@ -22,7 +22,11 @@ import { DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME } from "@/components/resizable/conta import { personaComparator } from "../admin/personas/lib"; import { ChatLayout } from "./ChatPage"; -export default async function Page() { +export default async function Page({ + searchParams, +}: { + searchParams: { [key: string]: string }; +}) { noStore(); const tasks = [ @@ -110,6 +114,11 @@ export default async function Page() { // sort them in priority order personas.sort(personaComparator); + const defaultPersonaIdRaw = searchParams["personaId"]; + const defaultPersonaId = defaultPersonaIdRaw + ? parseInt(defaultPersonaIdRaw) + : undefined; + const documentSidebarCookieInitialWidth = cookies().get( DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME ); @@ -130,6 +139,7 @@ export default async function Page() { availableSources={availableSources} availableDocumentSets={documentSets} availablePersonas={personas} + defaultSelectedPersonaId={defaultPersonaId} documentSidebarInitialWidth={finalDocumentSidebarInitialWidth} /> diff --git a/web/src/components/Modal.tsx b/web/src/components/Modal.tsx index 4ee8b701cc..b4cff3849e 100644 --- a/web/src/components/Modal.tsx +++ b/web/src/components/Modal.tsx @@ -22,8 +22,8 @@ export function Modal({ >
event.stopPropagation()}