FIx chat refresh + add stop button

This commit is contained in:
Weves 2023-12-28 20:02:59 -08:00 committed by Chris Weaver
parent 73483b5e09
commit cdf260b277
10 changed files with 342 additions and 297 deletions

View File

@ -6,6 +6,7 @@ import { FiRefreshCcw, FiSend, FiStopCircle } from "react-icons/fi";
import { AIMessage, HumanMessage } from "./message/Messages";
import { AnswerPiecePacket, DanswerDocument } from "@/lib/search/interfaces";
import {
BackendChatSession,
BackendMessage,
DocumentsResponse,
Message,
@ -22,6 +23,7 @@ import {
handleAutoScroll,
handleChatFeedback,
nameChatSession,
processRawChatHistory,
sendMessage,
} from "./lib";
import { ThreeDots } from "react-loader-spinner";
@ -33,7 +35,6 @@ import { useFilters } from "@/lib/hooks";
import { DocumentSet, ValidSources } from "@/lib/types";
import { ChatFilters } from "./modifiers/ChatFilters";
import { buildFilters } from "@/lib/search/utils";
import { QA, SearchTypeSelector } from "./modifiers/SearchTypeSelector";
import { SelectedDocuments } from "./modifiers/SelectedDocuments";
import { usePopup } from "@/components/admin/connectors/Popup";
import { ResizableSection } from "@/components/resizable/ResizableSection";
@ -44,7 +45,6 @@ const MAX_INPUT_HEIGHT = 200;
export const Chat = ({
existingChatSessionId,
existingChatSessionPersonaId,
existingMessages,
availableSources,
availableDocumentSets,
availablePersonas,
@ -53,7 +53,6 @@ export const Chat = ({
}: {
existingChatSessionId: number | null;
existingChatSessionPersonaId: number | undefined;
existingMessages: Message[];
availableSources: ValidSources[];
availableDocumentSets: DocumentSet[];
availablePersonas: Persona[];
@ -63,18 +62,55 @@ export const Chat = ({
const router = useRouter();
const { popup, setPopup } = usePopup();
const [chatSessionId, setChatSessionId] = useState(existingChatSessionId);
// fetch messages for the chat session
const [isFetchingChatMessages, setIsFetchingChatMessages] = useState(
existingChatSessionId !== null
);
// this is triggered every time the user switches which chat
// session they are using
useEffect(() => {
textareaRef.current?.focus();
setChatSessionId(existingChatSessionId);
async function initialSessionFetch() {
if (existingChatSessionId === null) {
setIsFetchingChatMessages(false);
setMessageHistory([]);
return;
}
setIsFetchingChatMessages(true);
const response = await fetch(
`/api/chat/get-chat-session/${existingChatSessionId}`
);
const chatSession = (await response.json()) as BackendChatSession;
const newMessageHistory = processRawChatHistory(chatSession.messages);
setMessageHistory(newMessageHistory);
const latestMessageId =
newMessageHistory[newMessageHistory.length - 1]?.messageId;
setSelectedMessageForDocDisplay(
latestMessageId !== undefined ? latestMessageId : null
);
setIsFetchingChatMessages(false);
}
initialSessionFetch();
}, [existingChatSessionId]);
const [chatSessionId, setChatSessionId] = useState<number | null>(
existingChatSessionId
);
const [message, setMessage] = useState("");
const [messageHistory, setMessageHistory] =
useState<Message[]>(existingMessages);
const [messageHistory, setMessageHistory] = useState<Message[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
// for document display
// NOTE: -1 is a special designation that means the latest AI message
const [selectedMessageForDocDisplay, setSelectedMessageForDocDisplay] =
useState<number | null>(
messageHistory[messageHistory.length - 1]?.messageId || null
);
useState<number | null>(null);
const { aiMessage } = selectedMessageForDocDisplay
? getHumanAndAIMessageFromMessageNumber(
messageHistory,
@ -95,8 +131,6 @@ export const Chat = ({
const filterManager = useFilters();
const [selectedSearchType, setSelectedSearchType] = useState(QA);
// state for cancelling streaming
const [isCancelled, setIsCancelled] = useState(false);
const isCancelledRef = useRef(isCancelled);
@ -124,12 +158,7 @@ export const Chat = ({
useEffect(() => {
endDivRef.current?.scrollIntoView();
setHasPerformedInitialScroll(true);
}, []);
// handle refreshes of the server-side props
useEffect(() => {
setMessageHistory(existingMessages);
}, [existingMessages]);
}, [isFetchingChatMessages]);
// handle re-sizing of the text area
const textareaRef = useRef<HTMLTextAreaElement>(null);
@ -215,9 +244,7 @@ export const Chat = ({
message: currMessage,
parentMessageId: lastSuccessfulMessageId,
chatSessionId: currChatSessionId,
// if search-only set prompt to null to tell backend to not give an answer
promptId:
selectedSearchType === QA ? selectedPersona?.prompts[0]?.id : null,
promptId: 0,
filters: buildFilters(
filterManager.selectedSources,
filterManager.selectedDocumentSets,
@ -292,7 +319,7 @@ export const Chat = ({
setSelectedMessageForDocDisplay(finalMessage.message_id);
}
await nameChatSession(currChatSessionId, currMessage);
router.push(`/chat/${currChatSessionId}?shouldhideBeforeScroll=true`, {
router.push(`/chat?chatId=${currChatSessionId}`, {
scroll: false,
});
}
@ -372,25 +399,27 @@ export const Chat = ({
</div>
)}
{messageHistory.length === 0 && !isStreaming && (
<div className="flex justify-center items-center h-full">
<div className="px-8 w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar">
<div className="flex">
<div className="mx-auto h-[80px] w-[80px]">
<Image
src="/logo.png"
alt="Logo"
width="1419"
height="1520"
/>
{messageHistory.length === 0 &&
!isFetchingChatMessages &&
!isStreaming && (
<div className="flex justify-center items-center h-full">
<div className="px-8 w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar">
<div className="flex">
<div className="mx-auto h-[80px] w-[80px]">
<Image
src="/logo.png"
alt="Logo"
width="1419"
height="1520"
/>
</div>
</div>
<div className="mx-auto text-2xl font-bold text-strong p-4 w-fit">
What are you looking for today?
</div>
</div>
<div className="mx-auto text-2xl font-bold text-strong p-4 w-fit">
What are you looking for today?
</div>
</div>
</div>
)}
)}
<div
className={
@ -530,13 +559,6 @@ export const Chat = ({
<div className="flex">
<div className="w-searchbar-xs 2xl:w-searchbar-sm 3xl:w-searchbar mx-auto px-4 pt-1 flex">
<div className="mr-3">
<SearchTypeSelector
selectedSearchType={selectedSearchType}
setSelectedSearchType={setSelectedSearchType}
/>
</div>
{selectedDocuments.length > 0 ? (
<SelectedDocuments
selectedDocuments={selectedDocuments}
@ -588,7 +610,11 @@ export const Chat = ({
value={message}
onChange={(e) => setMessage(e.target.value)}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
if (
event.key === "Enter" &&
!event.shiftKey &&
message
) {
onSubmit();
event.preventDefault();
}
@ -598,15 +624,32 @@ export const Chat = ({
<div className="absolute bottom-4 right-10">
<div
className={"cursor-pointer"}
onClick={() => onSubmit()}
>
<FiSend
size={18}
className={
"text-emphasis w-9 h-9 p-2 rounded-lg " +
(message ? "bg-blue-200" : "")
onClick={() => {
if (!isStreaming) {
if (message) {
onSubmit();
}
} else {
setIsCancelled(true);
}
/>
}}
>
{isStreaming ? (
<FiStopCircle
size={18}
className={
"text-emphasis w-9 h-9 p-2 rounded-lg hover:bg-hover"
}
/>
) : (
<FiSend
size={18}
className={
"text-emphasis w-9 h-9 p-2 rounded-lg " +
(message ? "bg-blue-200" : "")
}
/>
)}
</div>
</div>
</div>
@ -624,6 +667,7 @@ export const Chat = ({
selectedMessage={aiMessage}
selectedDocuments={selectedDocuments}
setSelectedDocuments={setSelectedDocuments}
isLoading={isFetchingChatMessages}
/>
</ResizableSection>
</>

View File

@ -1,226 +1,47 @@
import {
AuthTypeMetadata,
getAuthTypeMetadataSS,
getCurrentUserSS,
} from "@/lib/userSS";
import { redirect } from "next/navigation";
import { fetchSS } from "@/lib/utilsSS";
import { Connector, DocumentSet, User, ValidSources } from "@/lib/types";
"use client";
import { useSearchParams } from "next/navigation";
import { ChatSession } from "./interfaces";
import { ChatSidebar } from "./sessionSidebar/ChatSidebar";
import { Chat } from "./Chat";
import {
BackendMessage,
ChatSession,
Message,
RetrievalType,
} from "./interfaces";
import { unstable_noStore as noStore } from "next/cache";
import { DocumentSet, User, ValidSources } from "@/lib/types";
import { Persona } from "../admin/personas/interfaces";
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import { WelcomeModal } from "@/components/WelcomeModal";
import { ApiKeyModal } from "@/components/openai/ApiKeyModal";
import { cookies } from "next/headers";
import { DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME } from "@/components/resizable/contants";
import { personaComparator } from "../admin/personas/lib";
export default async function ChatPage({
chatId,
shouldhideBeforeScroll,
export function ChatLayout({
user,
chatSessions,
availableSources,
availableDocumentSets,
availablePersonas,
documentSidebarInitialWidth,
}: {
chatId: string | null;
shouldhideBeforeScroll?: boolean;
user: User | null;
chatSessions: ChatSession[];
availableSources: ValidSources[];
availableDocumentSets: DocumentSet[];
availablePersonas: Persona[];
documentSidebarInitialWidth?: number;
}) {
noStore();
const currentChatId = chatId ? parseInt(chatId) : null;
const tasks = [
getAuthTypeMetadataSS(),
getCurrentUserSS(),
fetchSS("/manage/connector"),
fetchSS("/manage/document-set"),
fetchSS("/persona?include_default=true"),
fetchSS("/chat/get-user-chat-sessions"),
chatId !== null
? fetchSS(`/chat/get-chat-session/${chatId}`)
: (async () => null)(),
];
// catch cases where the backend is completely unreachable here
// without try / catch, will just raise an exception and the page
// will not render
let results: (User | Response | AuthTypeMetadata | null)[] = [
null,
null,
null,
null,
null,
null,
null,
];
try {
results = await Promise.all(tasks);
} catch (e) {
console.log(`Some fetch failed for the main search page - ${e}`);
}
const authTypeMetadata = results[0] as AuthTypeMetadata | null;
const user = results[1] as User | null;
const connectorsResponse = results[2] as Response | null;
const documentSetsResponse = results[3] as Response | null;
const personasResponse = results[4] as Response | null;
const chatSessionsResponse = results[5] as Response | null;
const chatSessionMessagesResponse = results[6] as Response | null;
const authDisabled = authTypeMetadata?.authType === "disabled";
if (!authDisabled && !user) {
return redirect("/auth/login");
}
if (user && !user.is_verified && authTypeMetadata?.requiresVerification) {
return redirect("/auth/waiting-on-verification");
}
let connectors: Connector<any>[] = [];
if (connectorsResponse?.ok) {
connectors = await connectorsResponse.json();
} else {
console.log(`Failed to fetch connectors - ${connectorsResponse?.status}`);
}
const availableSources: ValidSources[] = [];
connectors.forEach((connector) => {
if (!availableSources.includes(connector.source)) {
availableSources.push(connector.source);
}
});
let chatSessions: ChatSession[] = [];
if (chatSessionsResponse?.ok) {
chatSessions = (await chatSessionsResponse.json()).sessions;
} else {
console.log(
`Failed to fetch chat sessions - ${chatSessionsResponse?.text()}`
);
}
// Larger ID -> created later
chatSessions.sort((a, b) => (a.id > b.id ? -1 : 1));
const currentChatSession = chatSessions.find(
(chatSession) => chatSession.id === currentChatId
);
let documentSets: DocumentSet[] = [];
if (documentSetsResponse?.ok) {
documentSets = await documentSetsResponse.json();
} else {
console.log(
`Failed to fetch document sets - ${documentSetsResponse?.status}`
);
}
let personas: Persona[] = [];
if (personasResponse?.ok) {
personas = await personasResponse.json();
} else {
console.log(`Failed to fetch personas - ${personasResponse?.status}`);
}
// remove those marked as hidden by an admin
personas = personas.filter((persona) => persona.is_visible);
// sort them in priority order
personas.sort(personaComparator);
let messages: Message[] = [];
if (chatSessionMessagesResponse?.ok) {
const chatSessionDetailJson = await chatSessionMessagesResponse.json();
const rawMessages = chatSessionDetailJson.messages as BackendMessage[];
const messageMap: Map<number, BackendMessage> = new Map(
rawMessages.map((message) => [message.message_id, message])
);
const rootMessage = rawMessages.find(
(message) => message.parent_message === null
);
const finalMessageList: BackendMessage[] = [];
if (rootMessage) {
let currMessage: BackendMessage | null = rootMessage;
while (currMessage) {
finalMessageList.push(currMessage);
const childMessageNumber = currMessage.latest_child_message;
if (childMessageNumber && messageMap.has(childMessageNumber)) {
currMessage = messageMap.get(childMessageNumber) as BackendMessage;
} else {
currMessage = null;
}
}
}
messages = finalMessageList
.filter((messageInfo) => messageInfo.message_type !== "system")
.map((messageInfo) => {
const hasContextDocs =
(messageInfo?.context_docs?.top_documents || []).length > 0;
let retrievalType;
if (hasContextDocs) {
if (messageInfo.rephrased_query) {
retrievalType = RetrievalType.Search;
} else {
retrievalType = RetrievalType.SelectedDocs;
}
} else {
retrievalType = RetrievalType.None;
}
return {
messageId: messageInfo.message_id,
message: messageInfo.message,
type: messageInfo.message_type as "user" | "assistant",
// only include these fields if this is an assistant message so that
// this is identical to what is computed at streaming time
...(messageInfo.message_type === "assistant"
? {
retrievalType: retrievalType,
query: messageInfo.rephrased_query,
documents: messageInfo?.context_docs?.top_documents || [],
citations: messageInfo?.citations || {},
}
: {}),
};
});
} else {
console.log(
`Failed to fetch chat session messages - ${chatSessionMessagesResponse?.text()}`
);
}
const documentSidebarCookieInitialWidth = cookies().get(
DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME
);
const finalDocumentSidebarInitialWidth = documentSidebarCookieInitialWidth
? parseInt(documentSidebarCookieInitialWidth.value)
: undefined;
const searchParams = useSearchParams();
const chatIdRaw = searchParams.get("chatId");
const chatId = chatIdRaw ? parseInt(chatIdRaw) : null;
return (
<>
<InstantSSRAutoRefresh />
<ApiKeyModal />
{connectors.length === 0 && connectorsResponse?.ok && <WelcomeModal />}
<div className="flex relative bg-background text-default h-screen overflow-x-hidden">
<ChatSidebar
existingChats={chatSessions}
currentChatId={currentChatId}
currentChatId={chatId}
user={user}
/>
<Chat
existingChatSessionId={currentChatId}
existingChatSessionPersonaId={currentChatSession?.persona_id}
existingMessages={messages}
existingChatSessionId={chatId}
existingChatSessionPersonaId={0}
availableSources={availableSources}
availableDocumentSets={documentSets}
availablePersonas={personas}
documentSidebarInitialWidth={finalDocumentSidebarInitialWidth}
shouldhideBeforeScroll={shouldhideBeforeScroll}
availableDocumentSets={availableDocumentSets}
availablePersonas={availablePersonas}
documentSidebarInitialWidth={documentSidebarInitialWidth}
/>
</div>
</>

View File

@ -1,14 +0,0 @@
import ChatPage from "../ChatPage";
export default async function Page({
params,
searchParams,
}: {
params: { chatId: string };
searchParams: { shouldhideBeforeScroll?: string };
}) {
return await ChatPage({
chatId: params.chatId,
shouldhideBeforeScroll: searchParams.shouldhideBeforeScroll === "true",
});
}

View File

@ -2,7 +2,7 @@ import { DanswerDocument } from "@/lib/search/interfaces";
import { Text } from "@tremor/react";
import { ChatDocumentDisplay } from "./ChatDocumentDisplay";
import { usePopup } from "@/components/admin/connectors/Popup";
import { FiFileText, FiSearch } from "react-icons/fi";
import { FiFileText } from "react-icons/fi";
import { SelectedDocumentDisplay } from "./SelectedDocumentDisplay";
import { removeDuplicateDocs } from "@/lib/documentUtils";
import { BasicSelectable } from "@/components/BasicClickable";
@ -27,10 +27,12 @@ export function DocumentSidebar({
selectedMessage,
selectedDocuments,
setSelectedDocuments,
isLoading,
}: {
selectedMessage: Message | null;
selectedDocuments: DanswerDocument[] | null;
setSelectedDocuments: (documents: DanswerDocument[]) => void;
isLoading: boolean;
}) {
const { popup, setPopup } = usePopup();
@ -115,12 +117,14 @@ export function DocumentSidebar({
</div>
</div>
) : (
<div className="ml-4 mr-3">
<Text>
When you run ask a question, the retrieved documents will show up
here!
</Text>
</div>
!isLoading && (
<div className="ml-4 mr-3">
<Text>
When you run ask a question, the retrieved documents will show
up here!
</Text>
</div>
)
)}
</div>
@ -157,10 +161,12 @@ export function DocumentSidebar({
))}
</div>
) : (
<Text className="mx-3 py-3">
Select documents from the retrieved documents section to chat
specifically with them!
</Text>
!isLoading && (
<Text className="mx-3 py-3">
Select documents from the retrieved documents section to chat
specifically with them!
</Text>
)
)}
</div>
</div>

View File

@ -32,6 +32,10 @@ export interface Message {
citations?: CitationMap;
}
export interface BackendChatSession {
messages: BackendMessage[];
}
export interface BackendMessage {
message_id: number;
parent_message: number | null;

View File

@ -11,6 +11,7 @@ import {
ChatSession,
DocumentsResponse,
Message,
RetrievalType,
StreamingError,
} from "./interfaces";
@ -279,3 +280,62 @@ export function getLastSuccessfulMessageId(messageHistory: Message[]) {
);
return lastSuccessfulMessage ? lastSuccessfulMessage?.messageId : null;
}
export function processRawChatHistory(rawMessages: BackendMessage[]) {
const messageMap: Map<number, BackendMessage> = new Map(
rawMessages.map((message) => [message.message_id, message])
);
const rootMessage = rawMessages.find(
(message) => message.parent_message === null
);
const finalMessageList: BackendMessage[] = [];
if (rootMessage) {
let currMessage: BackendMessage | null = rootMessage;
while (currMessage) {
finalMessageList.push(currMessage);
const childMessageNumber = currMessage.latest_child_message;
if (childMessageNumber && messageMap.has(childMessageNumber)) {
currMessage = messageMap.get(childMessageNumber) as BackendMessage;
} else {
currMessage = null;
}
}
}
const messages: Message[] = finalMessageList
.filter((messageInfo) => messageInfo.message_type !== "system")
.map((messageInfo) => {
const hasContextDocs =
(messageInfo?.context_docs?.top_documents || []).length > 0;
let retrievalType;
if (hasContextDocs) {
if (messageInfo.rephrased_query) {
retrievalType = RetrievalType.Search;
} else {
retrievalType = RetrievalType.SelectedDocs;
}
} else {
retrievalType = RetrievalType.None;
}
return {
messageId: messageInfo.message_id,
message: messageInfo.message,
type: messageInfo.message_type as "user" | "assistant",
// only include these fields if this is an assistant message so that
// this is identical to what is computed at streaming time
...(messageInfo.message_type === "assistant"
? {
retrievalType: retrievalType,
query: messageInfo.rephrased_query,
documents: messageInfo?.context_docs?.top_documents || [],
citations: messageInfo?.citations || {},
}
: {}),
};
});
return messages;
}

View File

@ -116,9 +116,7 @@ export const AIMessage = ({
content
)}
</>
) : isComplete ? (
<div>I just performed the requested search!</div>
) : (
) : isComplete ? null : (
<div className="text-sm my-auto">
<ThreeDots
height="30"

View File

@ -1,12 +1,137 @@
import ChatPage from "./ChatPage";
import {
AuthTypeMetadata,
getAuthTypeMetadataSS,
getCurrentUserSS,
} from "@/lib/userSS";
import { redirect } from "next/navigation";
import { fetchSS } from "@/lib/utilsSS";
import { Connector, DocumentSet, User, ValidSources } from "@/lib/types";
import {
BackendMessage,
ChatSession,
Message,
RetrievalType,
} from "./interfaces";
import { unstable_noStore as noStore } from "next/cache";
import { Persona } from "../admin/personas/interfaces";
import { InstantSSRAutoRefresh } from "@/components/SSRAutoRefresh";
import { WelcomeModal } from "@/components/WelcomeModal";
import { ApiKeyModal } from "@/components/openai/ApiKeyModal";
import { cookies } from "next/headers";
import { DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME } from "@/components/resizable/contants";
import { personaComparator } from "../admin/personas/lib";
import { ChatLayout } from "./ChatPage";
export default async function Page({
searchParams,
}: {
searchParams: { shouldhideBeforeScroll?: string };
}) {
return await ChatPage({
chatId: null,
shouldhideBeforeScroll: searchParams.shouldhideBeforeScroll === "true",
export default async function Page() {
noStore();
const tasks = [
getAuthTypeMetadataSS(),
getCurrentUserSS(),
fetchSS("/manage/connector"),
fetchSS("/manage/document-set"),
fetchSS("/persona?include_default=true"),
fetchSS("/chat/get-user-chat-sessions"),
];
// catch cases where the backend is completely unreachable here
// without try / catch, will just raise an exception and the page
// will not render
let results: (User | Response | AuthTypeMetadata | null)[] = [
null,
null,
null,
null,
null,
null,
];
try {
results = await Promise.all(tasks);
} catch (e) {
console.log(`Some fetch failed for the main search page - ${e}`);
}
const authTypeMetadata = results[0] as AuthTypeMetadata | null;
const user = results[1] as User | null;
const connectorsResponse = results[2] as Response | null;
const documentSetsResponse = results[3] as Response | null;
const personasResponse = results[4] as Response | null;
const chatSessionsResponse = results[5] as Response | null;
const authDisabled = authTypeMetadata?.authType === "disabled";
if (!authDisabled && !user) {
return redirect("/auth/login");
}
if (user && !user.is_verified && authTypeMetadata?.requiresVerification) {
return redirect("/auth/waiting-on-verification");
}
let connectors: Connector<any>[] = [];
if (connectorsResponse?.ok) {
connectors = await connectorsResponse.json();
} else {
console.log(`Failed to fetch connectors - ${connectorsResponse?.status}`);
}
const availableSources: ValidSources[] = [];
connectors.forEach((connector) => {
if (!availableSources.includes(connector.source)) {
availableSources.push(connector.source);
}
});
let chatSessions: ChatSession[] = [];
if (chatSessionsResponse?.ok) {
chatSessions = (await chatSessionsResponse.json()).sessions;
} else {
console.log(
`Failed to fetch chat sessions - ${chatSessionsResponse?.text()}`
);
}
// Larger ID -> created later
chatSessions.sort((a, b) => (a.id > b.id ? -1 : 1));
let documentSets: DocumentSet[] = [];
if (documentSetsResponse?.ok) {
documentSets = await documentSetsResponse.json();
} else {
console.log(
`Failed to fetch document sets - ${documentSetsResponse?.status}`
);
}
let personas: Persona[] = [];
if (personasResponse?.ok) {
personas = await personasResponse.json();
} else {
console.log(`Failed to fetch personas - ${personasResponse?.status}`);
}
// remove those marked as hidden by an admin
personas = personas.filter((persona) => persona.is_visible);
// sort them in priority order
personas.sort(personaComparator);
const documentSidebarCookieInitialWidth = cookies().get(
DOCUMENT_SIDEBAR_WIDTH_COOKIE_NAME
);
const finalDocumentSidebarInitialWidth = documentSidebarCookieInitialWidth
? parseInt(documentSidebarCookieInitialWidth.value)
: undefined;
return (
<>
<InstantSSRAutoRefresh />
<ApiKeyModal />
{connectors.length === 0 && <WelcomeModal />}
<ChatLayout
user={user}
chatSessions={chatSessions}
availableSources={availableSources}
availableDocumentSets={documentSets}
availablePersonas={personas}
documentSidebarInitialWidth={finalDocumentSidebarInitialWidth}
/>
</>
);
}

View File

@ -18,6 +18,7 @@ import Image from "next/image";
import { ChatSessionDisplay } from "./SessionDisplay";
import { ChatSession } from "../interfaces";
import { groupSessionsByDateRange } from "../lib";
interface ChatSidebarProps {
existingChats: ChatSession[];
currentChatId: number | null;

View File

@ -52,7 +52,7 @@ export function ChatSessionDisplay({
<Link
className="flex my-1"
key={chatSession.id}
href={`/chat/${chatSession.id}`}
href={`/chat?chatId=${chatSession.id}`}
scroll={false}
>
<BasicSelectable fullWidth selected={isSelected}>