diff --git a/web/src/app/chat/Chat.tsx b/web/src/app/chat/Chat.tsx index 467150321..d563e2c00 100644 --- a/web/src/app/chat/Chat.tsx +++ b/web/src/app/chat/Chat.tsx @@ -74,13 +74,20 @@ export const Chat = ({ existingChatSessionId !== null ); + // needed so closures (e.g. onSubmit) can access the current value + const urlChatSessionId = useRef(); // this is triggered every time the user switches which chat // session they are using useEffect(() => { + urlChatSessionId.current = existingChatSessionId; + textareaRef.current?.focus(); // only clear things if we're going from one chat session to another - if (chatSessionId !== null && existingChatSessionId !== chatSessionId) { + if ( + existingChatSessionId !== null && + existingChatSessionId !== chatSessionId + ) { // de-select documents clearSelectedDocuments(); // reset all filters @@ -286,7 +293,12 @@ export const Chat = ({ const onSubmit = async ({ messageIdToResend, queryOverride, - }: { messageIdToResend?: number; queryOverride?: string } = {}) => { + forceSearch, + }: { + messageIdToResend?: number; + queryOverride?: string; + forceSearch?: boolean; + } = {}) => { let currChatSessionId: number; let isNewSession = chatSessionId === null; if (isNewSession) { @@ -357,6 +369,7 @@ export const Chat = ({ ) .map((document) => document.db_doc_id as number), queryOverride, + forceSearch, })) { for (const packet of packetBunch) { if (Object.hasOwn(packet, "answer_piece")) { @@ -422,7 +435,10 @@ export const Chat = ({ await nameChatSession(currChatSessionId, currMessage); // NOTE: don't switch pages if the user has navigated away from the chat - if (currChatSessionId === chatSessionId) { + if ( + currChatSessionId === urlChatSessionId.current || + urlChatSessionId.current === null + ) { router.push(`/chat?chatId=${currChatSessionId}`, { scroll: false, }); @@ -603,6 +619,20 @@ export const Chat = ({ } } }} + handleForceSearch={() => { + if (previousMessage && previousMessage.messageId) { + onSubmit({ + messageIdToResend: previousMessage.messageId, + forceSearch: true, + }); + } else { + setPopup({ + type: "error", + message: + "Failed to force search - please refresh the page and try again.", + }); + } + }} /> ); diff --git a/web/src/app/chat/lib.tsx b/web/src/app/chat/lib.tsx index 7fc125d7f..16b49e3d0 100644 --- a/web/src/app/chat/lib.tsx +++ b/web/src/app/chat/lib.tsx @@ -46,6 +46,7 @@ export interface SendMessageRequest { filters: Filters | null; selectedDocumentIds: number[] | null; queryOverride?: string; + forceSearch?: boolean; } export async function* sendMessage({ @@ -56,6 +57,7 @@ export async function* sendMessage({ filters, selectedDocumentIds, queryOverride, + forceSearch, }: SendMessageRequest) { const documentsAreSelected = selectedDocumentIds && selectedDocumentIds.length > 0; @@ -73,7 +75,10 @@ export async function* sendMessage({ retrieval_options: !documentsAreSelected ? { run_search: - promptId === null || promptId === undefined || queryOverride + promptId === null || + promptId === undefined || + queryOverride || + forceSearch ? "always" : "auto", real_time: true, diff --git a/web/src/app/chat/message/Messages.tsx b/web/src/app/chat/message/Messages.tsx index 5d92fdd63..4af3b4b24 100644 --- a/web/src/app/chat/message/Messages.tsx +++ b/web/src/app/chat/message/Messages.tsx @@ -13,6 +13,7 @@ import { DanswerDocument } from "@/lib/search/interfaces"; import { SearchSummary, ShowHideDocsButton } from "./SearchSummary"; import { SourceIcon } from "@/components/SourceIcon"; import { ThreeDots } from "react-loader-spinner"; +import { SkippedSearch } from "./SkippedSearch"; export const Hoverable: React.FC<{ children: JSX.Element; @@ -39,6 +40,7 @@ export const AIMessage = ({ isCurrentlyShowingRetrieved, handleShowRetrieved, handleSearchQueryEdit, + handleForceSearch, }: { messageId: number | null; content: string | JSX.Element; @@ -50,6 +52,7 @@ export const AIMessage = ({ isCurrentlyShowingRetrieved?: boolean; handleShowRetrieved?: (messageNumber: number | null) => void; handleSearchQueryEdit?: (query: string) => void; + handleForceSearch?: () => void; }) => { const [copyClicked, setCopyClicked] = useState(false); return ( @@ -96,6 +99,11 @@ export const AIMessage = ({ /> )} + {handleForceSearch && content && query === undefined && ( +
+ +
+ )} {content ? ( <> diff --git a/web/src/app/chat/message/SkippedSearch.tsx b/web/src/app/chat/message/SkippedSearch.tsx new file mode 100644 index 000000000..9deed79e2 --- /dev/null +++ b/web/src/app/chat/message/SkippedSearch.tsx @@ -0,0 +1,43 @@ +import { EmphasizedClickable } from "@/components/BasicClickable"; +import { FiArchive, FiBook, FiSearch } from "react-icons/fi"; + +function ForceSearchButton({ + messageId, + handleShowRetrieved, +}: { + messageId: number | null; + isCurrentlyShowingRetrieved: boolean; + handleShowRetrieved: (messageId: number | null) => void; +}) { + return ( +
handleShowRetrieved(messageId)} + > + +
Force Search
+
+
+ ); +} + +export function SkippedSearch({ + handleForceSearch, +}: { + handleForceSearch: () => void; +}) { + return ( +
+ +
+ The AI decided this query didn't need a search +
+ +
+ +
Force Search
+
+
+
+ ); +}