Fix misc chat bugs

This commit is contained in:
Weves
2024-06-29 15:19:27 -07:00
committed by Chris Weaver
parent 2035e9f39c
commit 393b3c9343
4 changed files with 116 additions and 97 deletions

View File

@@ -12,7 +12,6 @@ import {
Message, Message,
RetrievalType, RetrievalType,
StreamingError, StreamingError,
ToolCallFinalResult,
ToolCallMetadata, ToolCallMetadata,
} from "./interfaces"; } from "./interfaces";
import { ChatSidebar } from "./sessionSidebar/ChatSidebar"; import { ChatSidebar } from "./sessionSidebar/ChatSidebar";
@@ -35,7 +34,6 @@ import {
removeMessage, removeMessage,
sendMessage, sendMessage,
setMessageAsLatest, setMessageAsLatest,
updateModelOverrideForChatSession,
updateParentChildren, updateParentChildren,
uploadFilesForChat, uploadFilesForChat,
useScrollonStream, useScrollonStream,
@@ -61,11 +59,7 @@ import { AnswerPiecePacket, DanswerDocument } from "@/lib/search/interfaces";
import { buildFilters } from "@/lib/search/utils"; import { buildFilters } from "@/lib/search/utils";
import { SettingsContext } from "@/components/settings/SettingsProvider"; import { SettingsContext } from "@/components/settings/SettingsProvider";
import Dropzone from "react-dropzone"; import Dropzone from "react-dropzone";
import { import { checkLLMSupportsImageInput, getFinalLLM } from "@/lib/llm/utils";
checkLLMSupportsImageInput,
getFinalLLM,
structureValue,
} from "@/lib/llm/utils";
import { ChatInputBar } from "./input/ChatInputBar"; import { ChatInputBar } from "./input/ChatInputBar";
import { ConfigurationModal } from "./modal/configuration/ConfigurationModal"; import { ConfigurationModal } from "./modal/configuration/ConfigurationModal";
import { useChatContext } from "@/components/context/ChatContext"; import { useChatContext } from "@/components/context/ChatContext";
@@ -118,10 +112,10 @@ export function ChatPage({
const existingChatSessionId = existingChatIdRaw const existingChatSessionId = existingChatIdRaw
? parseInt(existingChatIdRaw) ? parseInt(existingChatIdRaw)
: null; : null;
const selectedChatSession = chatSessions.find( const selectedChatSession = chatSessions.find(
(chatSession) => chatSession.id === existingChatSessionId (chatSession) => chatSession.id === existingChatSessionId
); );
const chatSessionIdRef = useRef<number | null>(existingChatSessionId);
const llmOverrideManager = useLlmOverride(selectedChatSession); const llmOverrideManager = useLlmOverride(selectedChatSession);
@@ -140,35 +134,22 @@ export function ChatPage({
existingChatSessionId !== null existingChatSessionId !== null
); );
// needed so closures (e.g. onSubmit) can access the current value
const urlChatSessionId = useRef<number | null>();
// this is triggered every time the user switches which chat // this is triggered every time the user switches which chat
// session they are using // session they are using
useEffect(() => { useEffect(() => {
if ( const priorChatSessionId = chatSessionIdRef.current;
chatSessionId && chatSessionIdRef.current = existingChatSessionId;
!urlChatSessionId.current &&
llmOverrideManager.llmOverride
) {
updateModelOverrideForChatSession(
chatSessionId,
structureValue(
llmOverrideManager.llmOverride.name,
llmOverrideManager.llmOverride.provider,
llmOverrideManager.llmOverride.modelName
) as string
);
}
urlChatSessionId.current = existingChatSessionId;
textAreaRef.current?.focus(); textAreaRef.current?.focus();
// only clear things if we're going from one chat session to another // only clear things if we're going from one chat session to another
const isChatSessionSwitch =
if (chatSessionId !== null && existingChatSessionId !== chatSessionId) { chatSessionIdRef.current !== null &&
existingChatSessionId !== priorChatSessionId;
if (isChatSessionSwitch) {
// de-select documents // de-select documents
clearSelectedDocuments(); clearSelectedDocuments();
// reset all filters
// reset all filters
filterManager.setSelectedDocumentSets([]); filterManager.setSelectedDocumentSets([]);
filterManager.setSelectedSources([]); filterManager.setSelectedSources([]);
filterManager.setSelectedTags([]); filterManager.setSelectedTags([]);
@@ -177,16 +158,21 @@ export function ChatPage({
// reset LLM overrides (based on chat session!) // reset LLM overrides (based on chat session!)
llmOverrideManager.updateModelOverrideForChatSession(selectedChatSession); llmOverrideManager.updateModelOverrideForChatSession(selectedChatSession);
llmOverrideManager.setTemperature(null); llmOverrideManager.setTemperature(null);
// remove uploaded files // remove uploaded files
setCurrentMessageFiles([]); setCurrentMessageFiles([]);
// if switching from one chat to another, then need to scroll again
// if we're creating a brand new chat, then don't need to scroll
if (chatSessionIdRef.current !== null) {
setHasPerformedInitialScroll(false);
}
if (isStreaming) { if (isStreaming) {
setIsCancelled(true); setIsCancelled(true);
} }
} }
setChatSessionId(existingChatSessionId);
async function initialSessionFetch() { async function initialSessionFetch() {
if (existingChatSessionId === null) { if (existingChatSessionId === null) {
setIsFetchingChatMessages(false); setIsFetchingChatMessages(false);
@@ -199,7 +185,10 @@ export function ChatPage({
} else { } else {
setSelectedPersona(undefined); setSelectedPersona(undefined);
} }
setCompleteMessageMap(new Map()); setCompleteMessageDetail({
sessionId: null,
messageMap: new Map(),
});
setChatSessionSharedStatus(ChatSessionSharedStatus.Private); setChatSessionSharedStatus(ChatSessionSharedStatus.Private);
// if we're supposed to submit on initial load, then do that here // if we're supposed to submit on initial load, then do that here
@@ -226,11 +215,14 @@ export function ChatPage({
) )
); );
const newCompleteMessageMap = processRawChatHistory(chatSession.messages); const newMessageMap = processRawChatHistory(chatSession.messages);
const newMessageHistory = buildLatestMessageChain(newCompleteMessageMap); const newMessageHistory = buildLatestMessageChain(newMessageMap);
// if the last message is an error, don't overwrite it // if the last message is an error, don't overwrite it
if (messageHistory[messageHistory.length - 1]?.type !== "error") { if (messageHistory[messageHistory.length - 1]?.type !== "error") {
setCompleteMessageMap(newCompleteMessageMap); setCompleteMessageDetail({
sessionId: chatSession.chat_session_id,
messageMap: newMessageMap,
});
const latestMessageId = const latestMessageId =
newMessageHistory[newMessageHistory.length - 1]?.messageId; newMessageHistory[newMessageHistory.length - 1]?.messageId;
@@ -241,6 +233,13 @@ export function ChatPage({
setChatSessionSharedStatus(chatSession.shared_status); setChatSessionSharedStatus(chatSession.shared_status);
// go to bottom. If initial load, then do a scroll,
// otherwise just appear at the bottom
if (!hasPerformedInitialScroll) {
clientScrollToBottom();
} else if (isChatSessionSwitch) {
clientScrollToBottom(true);
}
setIsFetchingChatMessages(false); setIsFetchingChatMessages(false);
// if this is a seeded chat, then kick off the AI message generation // if this is a seeded chat, then kick off the AI message generation
@@ -279,18 +278,17 @@ export function ChatPage({
} }
}; };
const [chatSessionId, setChatSessionId] = useState<number | null>(
existingChatSessionId
);
const [message, setMessage] = useState( const [message, setMessage] = useState(
searchParams.get(SEARCH_PARAM_NAMES.USER_MESSAGE) || "" searchParams.get(SEARCH_PARAM_NAMES.USER_MESSAGE) || ""
); );
const [completeMessageMap, setCompleteMessageMap] = useState< const [completeMessageDetail, setCompleteMessageDetail] = useState<{
Map<number, Message> sessionId: number | null;
>(new Map()); messageMap: Map<number, Message>;
}>({ sessionId: null, messageMap: new Map() });
const upsertToCompleteMessageMap = ({ const upsertToCompleteMessageMap = ({
messages, messages,
completeMessageMapOverride, completeMessageMapOverride,
chatSessionId,
replacementsMap = null, replacementsMap = null,
makeLatestChildMessage = false, makeLatestChildMessage = false,
}: { }: {
@@ -298,12 +296,13 @@ export function ChatPage({
// if calling this function repeatedly with short delay, stay may not update in time // if calling this function repeatedly with short delay, stay may not update in time
// and result in weird behavipr // and result in weird behavipr
completeMessageMapOverride?: Map<number, Message> | null; completeMessageMapOverride?: Map<number, Message> | null;
chatSessionId?: number;
replacementsMap?: Map<number, number> | null; replacementsMap?: Map<number, number> | null;
makeLatestChildMessage?: boolean; makeLatestChildMessage?: boolean;
}) => { }) => {
// deep copy // deep copy
const frozenCompleteMessageMap = const frozenCompleteMessageMap =
completeMessageMapOverride || completeMessageMap; completeMessageMapOverride || completeMessageDetail.messageMap;
const newCompleteMessageMap = structuredClone(frozenCompleteMessageMap); const newCompleteMessageMap = structuredClone(frozenCompleteMessageMap);
if (newCompleteMessageMap.size === 0) { if (newCompleteMessageMap.size === 0) {
const systemMessageId = messages[0].parentMessageId || SYSTEM_MESSAGE_ID; const systemMessageId = messages[0].parentMessageId || SYSTEM_MESSAGE_ID;
@@ -352,11 +351,17 @@ export function ChatPage({
)!.latestChildMessageId = messages[0].messageId; )!.latestChildMessageId = messages[0].messageId;
} }
} }
setCompleteMessageMap(newCompleteMessageMap); const newCompleteMessageDetail = {
return newCompleteMessageMap; sessionId: chatSessionId || completeMessageDetail.sessionId,
messageMap: newCompleteMessageMap,
};
setCompleteMessageDetail(newCompleteMessageDetail);
return newCompleteMessageDetail;
}; };
const messageHistory = buildLatestMessageChain(completeMessageMap); const messageHistory = buildLatestMessageChain(
completeMessageDetail.messageMap
);
const [isStreaming, setIsStreaming] = useState(false); const [isStreaming, setIsStreaming] = useState(false);
// uploaded files // uploaded files
@@ -393,7 +398,7 @@ export function ChatPage({
useState<ChatSessionSharedStatus>(ChatSessionSharedStatus.Private); useState<ChatSessionSharedStatus>(ChatSessionSharedStatus.Private);
useEffect(() => { useEffect(() => {
if (messageHistory.length === 0 && chatSessionId === null) { if (messageHistory.length === 0 && chatSessionIdRef.current === null) {
setSelectedPersona( setSelectedPersona(
filteredAssistants.find( filteredAssistants.find(
(persona) => persona.id === defaultSelectedPersonaId (persona) => persona.id === defaultSelectedPersonaId
@@ -481,7 +486,10 @@ export function ChatPage({
scrollableDivRef.current scrollableDivRef.current
) { ) {
endPaddingRef.current.style.transition = "height 0.3s ease-out"; endPaddingRef.current.style.transition = "height 0.3s ease-out";
endPaddingRef.current.style.height = `${Math.max(newHeight - 50, 0)}px`; endPaddingRef.current.style.height = `${Math.max(
newHeight - 50,
0
)}px`;
scrollableDivRef?.current.scrollBy({ scrollableDivRef?.current.scrollBy({
left: 0, left: 0,
@@ -495,13 +503,15 @@ export function ChatPage({
}; };
const clientScrollToBottom = (fast?: boolean) => { const clientScrollToBottom = (fast?: boolean) => {
setTimeout( setTimeout(() => {
() => { if (fast) {
endDivRef.current?.scrollIntoView();
} else {
endDivRef.current?.scrollIntoView({ behavior: "smooth" }); endDivRef.current?.scrollIntoView({ behavior: "smooth" });
setHasPerformedInitialScroll(true); }
},
fast ? 50 : 500 setHasPerformedInitialScroll(true);
); }, 50);
}; };
const isCancelledRef = useRef<boolean>(isCancelled); // scroll is cancelled const isCancelledRef = useRef<boolean>(isCancelled); // scroll is cancelled
@@ -521,13 +531,9 @@ export function ChatPage({
debounce, debounce,
}); });
const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = const [hasPerformedInitialScroll, setHasPerformedInitialScroll] = useState(
useState(false); existingChatSessionId === null
);
// on new page
useEffect(() => {
clientScrollToBottom();
}, [chatSessionId]);
// handle re-sizing of the text area // handle re-sizing of the text area
const textAreaRef = useRef<HTMLTextAreaElement>(null); const textAreaRef = useRef<HTMLTextAreaElement>(null);
@@ -639,7 +645,7 @@ export function ChatPage({
clientScrollToBottom(); clientScrollToBottom();
let currChatSessionId: number; let currChatSessionId: number;
let isNewSession = chatSessionId === null; let isNewSession = chatSessionIdRef.current === null;
const searchParamBasedChatSessionName = const searchParamBasedChatSessionName =
searchParams.get(SEARCH_PARAM_NAMES.TITLE) || null; searchParams.get(SEARCH_PARAM_NAMES.TITLE) || null;
@@ -649,18 +655,19 @@ export function ChatPage({
searchParamBasedChatSessionName searchParamBasedChatSessionName
); );
} else { } else {
currChatSessionId = chatSessionId as number; currChatSessionId = chatSessionIdRef.current as number;
} }
setChatSessionId(currChatSessionId); chatSessionIdRef.current = currChatSessionId;
const messageToResend = messageHistory.find( const messageToResend = messageHistory.find(
(message) => message.messageId === messageIdToResend (message) => message.messageId === messageIdToResend
); );
const messageMap = completeMessageDetail.messageMap;
const messageToResendParent = const messageToResendParent =
messageToResend?.parentMessageId !== null && messageToResend?.parentMessageId !== null &&
messageToResend?.parentMessageId !== undefined messageToResend?.parentMessageId !== undefined
? completeMessageMap.get(messageToResend.parentMessageId) ? messageMap.get(messageToResend.parentMessageId)
: null; : null;
const messageToResendIndex = messageToResend const messageToResendIndex = messageToResend
? messageHistory.indexOf(messageToResend) ? messageHistory.indexOf(messageToResend)
@@ -687,9 +694,7 @@ export function ChatPage({
(currMessageHistory.length > 0 (currMessageHistory.length > 0
? currMessageHistory[currMessageHistory.length - 1] ? currMessageHistory[currMessageHistory.length - 1]
: null) || : null) ||
(completeMessageMap.size === 1 (messageMap.size === 1 ? Array.from(messageMap.values())[0] : null);
? Array.from(completeMessageMap.values())[0]
: null);
// if we're resending, set the parent's child to null // if we're resending, set the parent's child to null
// we will use tempMessages until the regenerated message is complete // we will use tempMessages until the regenerated message is complete
@@ -712,14 +717,16 @@ export function ChatPage({
latestChildMessageId: TEMP_USER_MESSAGE_ID, latestChildMessageId: TEMP_USER_MESSAGE_ID,
}); });
} }
const frozenCompleteMessageMap = upsertToCompleteMessageMap({ const { messageMap: frozenMessageMap, sessionId: frozenSessionId } =
messages: messageUpdates, upsertToCompleteMessageMap({
}); messages: messageUpdates,
chatSessionId: currChatSessionId,
});
// on initial message send, we insert a dummy system message // on initial message send, we insert a dummy system message
// set this as the parent here if no parent is set // set this as the parent here if no parent is set
if (!parentMessage && frozenCompleteMessageMap.size === 2) { if (!parentMessage && frozenMessageMap.size === 2) {
parentMessage = frozenCompleteMessageMap.get(SYSTEM_MESSAGE_ID) || null; parentMessage = frozenMessageMap.get(SYSTEM_MESSAGE_ID) || null;
} }
const currentAssistantId = alternativeAssistant const currentAssistantId = alternativeAssistant
@@ -791,7 +798,8 @@ export function ChatPage({
upsertToCompleteMessageMap({ upsertToCompleteMessageMap({
messages: messages, messages: messages,
replacementsMap: replacementsMap, replacementsMap: replacementsMap,
completeMessageMapOverride: frozenCompleteMessageMap, completeMessageMapOverride: frozenMessageMap,
chatSessionId: frozenSessionId!,
}); });
}; };
const delay = (ms: number) => { const delay = (ms: number) => {
@@ -898,7 +906,7 @@ export function ChatPage({
parentMessageId: TEMP_USER_MESSAGE_ID, parentMessageId: TEMP_USER_MESSAGE_ID,
}, },
], ],
completeMessageMapOverride: frozenCompleteMessageMap, completeMessageMapOverride: frozenMessageMap,
}); });
} }
setIsStreaming(false); setIsStreaming(false);
@@ -912,12 +920,13 @@ export function ChatPage({
// NOTE: don't switch pages if the user has navigated away from the chat // NOTE: don't switch pages if the user has navigated away from the chat
if ( if (
currChatSessionId === urlChatSessionId.current || currChatSessionId === chatSessionIdRef.current ||
urlChatSessionId.current === null chatSessionIdRef.current === null
) { ) {
router.push(buildChatUrl(searchParams, currChatSessionId, null), { const newUrl = buildChatUrl(searchParams, currChatSessionId, null);
scroll: false, // newUrl is like /chat?chatId=10
}); // current page is like /chat
router.push(newUrl, { scroll: false });
} }
} }
if ( if (
@@ -936,7 +945,7 @@ export function ChatPage({
feedbackDetails: string, feedbackDetails: string,
predefinedFeedback: string | undefined predefinedFeedback: string | undefined
) => { ) => {
if (chatSessionId === null) { if (chatSessionIdRef.current === null) {
return; return;
} }
@@ -1074,7 +1083,7 @@ export function ChatPage({
setEditingRetrievalEnabled(false); setEditingRetrievalEnabled(false);
} }
}; };
console.log(hasPerformedInitialScroll);
return ( return (
<> <>
<HealthCheckBanner /> <HealthCheckBanner />
@@ -1110,9 +1119,9 @@ export function ChatPage({
/> />
)} )}
{sharingModalVisible && chatSessionId !== null && ( {sharingModalVisible && chatSessionIdRef.current !== null && (
<ShareChatSessionModal <ShareChatSessionModal
chatSessionId={chatSessionId} chatSessionId={chatSessionIdRef.current}
existingSharedStatus={chatSessionSharedStatus} existingSharedStatus={chatSessionSharedStatus}
onClose={() => setSharingModalVisible(false)} onClose={() => setSharingModalVisible(false)}
onShare={(shared) => onShare={(shared) =>
@@ -1126,7 +1135,7 @@ export function ChatPage({
)} )}
<ConfigurationModal <ConfigurationModal
chatSessionId={chatSessionId!} chatSessionId={chatSessionIdRef.current!}
activeTab={configModalActiveTab} activeTab={configModalActiveTab}
setActiveTab={setConfigModalActiveTab} setActiveTab={setConfigModalActiveTab}
onClose={() => setConfigModalActiveTab(null)} onClose={() => setConfigModalActiveTab(null)}
@@ -1174,7 +1183,7 @@ export function ChatPage({
</div> </div>
<div className="ml-auto mr-6 flex"> <div className="ml-auto mr-6 flex">
{chatSessionId !== null && ( {chatSessionIdRef.current !== null && (
<div <div
onClick={() => setSharingModalVisible(true)} onClick={() => setSharingModalVisible(true)}
className={` className={`
@@ -1221,12 +1230,14 @@ export function ChatPage({
} }
> >
{messageHistory.map((message, i) => { {messageHistory.map((message, i) => {
const messageMap = completeMessageDetail.messageMap;
const messageReactComponentKey = `${i}-${completeMessageDetail.sessionId}`;
if (message.type === "user") { if (message.type === "user") {
const parentMessage = message.parentMessageId const parentMessage = message.parentMessageId
? completeMessageMap.get(message.parentMessageId) ? messageMap.get(message.parentMessageId)
: null; : null;
return ( return (
<div key={`${i}-${existingChatSessionId}`}> <div key={messageReactComponentKey}>
<HumanMessage <HumanMessage
content={message.message} content={message.message}
files={message.files} files={message.files}
@@ -1238,7 +1249,7 @@ export function ChatPage({
const parentMessageId = const parentMessageId =
message.parentMessageId!; message.parentMessageId!;
const parentMessage = const parentMessage =
completeMessageMap.get(parentMessageId)!; messageMap.get(parentMessageId)!;
upsertToCompleteMessageMap({ upsertToCompleteMessageMap({
messages: [ messages: [
{ {
@@ -1255,14 +1266,16 @@ export function ChatPage({
}} }}
onMessageSelection={(messageId) => { onMessageSelection={(messageId) => {
const newCompleteMessageMap = new Map( const newCompleteMessageMap = new Map(
completeMessageMap messageMap
); );
newCompleteMessageMap.get( newCompleteMessageMap.get(
message.parentMessageId! message.parentMessageId!
)!.latestChildMessageId = messageId; )!.latestChildMessageId = messageId;
setCompleteMessageMap( setCompleteMessageDetail({
newCompleteMessageMap sessionId:
); completeMessageDetail.sessionId,
messageMap: newCompleteMessageMap,
});
setSelectedMessageForDocDisplay(messageId); setSelectedMessageForDocDisplay(messageId);
// set message as latest so we can edit this message // set message as latest so we can edit this message
// and so it sticks around on page reload // and so it sticks around on page reload
@@ -1292,7 +1305,7 @@ export function ChatPage({
return ( return (
<div <div
key={`${i}-${existingChatSessionId}`} key={messageReactComponentKey}
ref={ ref={
i == messageHistory.length - 1 i == messageHistory.length - 1
? lastMessageRef ? lastMessageRef
@@ -1414,7 +1427,7 @@ export function ChatPage({
); );
} else { } else {
return ( return (
<div key={`${i}-${existingChatSessionId}`}> <div key={messageReactComponentKey}>
<AIMessage <AIMessage
currentPersona={livePersona} currentPersona={livePersona}
messageId={message.messageId} messageId={message.messageId}
@@ -1434,7 +1447,7 @@ export function ChatPage({
messageHistory[messageHistory.length - 1].type === messageHistory[messageHistory.length - 1].type ===
"user" && ( "user" && (
<div <div
key={`${messageHistory.length}-${existingChatSessionId}`} key={`${messageHistory.length}-${chatSessionIdRef.current}`}
> >
<AIMessage <AIMessage
currentPersona={livePersona} currentPersona={livePersona}

View File

@@ -385,7 +385,9 @@ export function processRawChatHistory(
message: messageInfo.message, message: messageInfo.message,
type: messageInfo.message_type as "user" | "assistant", type: messageInfo.message_type as "user" | "assistant",
files: messageInfo.files, files: messageInfo.files,
alternateAssistantID: Number(messageInfo.alternate_assistant_id), alternateAssistantID: messageInfo.alternate_assistant_id
? Number(messageInfo.alternate_assistant_id)
: null,
// only include these fields if this is an assistant message so that // only include these fields if this is an assistant message so that
// this is identical to what is computed at streaming time // this is identical to what is computed at streaming time
...(messageInfo.message_type === "assistant" ...(messageInfo.message_type === "assistant"

View File

@@ -25,6 +25,11 @@ export function CodeBlock({
const [copied, setCopied] = useState(false); const [copied, setCopied] = useState(false);
if (!language) { if (!language) {
// this is the case of a single "`" e.g. `hi`
if (typeof children === "string") {
return <code className={className}>{children}</code>;
}
return ( return (
<pre style={CODE_BLOCK_PADDING_TYPE}> <pre style={CODE_BLOCK_PADDING_TYPE}>
<code {...props} className={`text-sm ${className}`}> <code {...props} className={`text-sm ${className}`}>

View File

@@ -150,8 +150,7 @@ export const AIMessage = ({
} }
const shouldShowLoader = const shouldShowLoader =
!toolCall || !toolCall || (toolCall.tool_name === SEARCH_TOOL_NAME && !content);
(toolCall.tool_name === SEARCH_TOOL_NAME && query === undefined);
const defaultLoader = shouldShowLoader ? ( const defaultLoader = shouldShowLoader ? (
<div className="text-sm my-auto"> <div className="text-sm my-auto">
<ThreeDots <ThreeDots