mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-06-25 07:21:00 +02:00
Cancelling searches when submitting a new one, no longer truncating at 7 docs, showing a warning message when no quotes are found
This commit is contained in:
parent
cb59e77278
commit
af329d31fb
@ -34,6 +34,7 @@ export const SearchBar: React.FC<SearchBarProps> = ({
|
|||||||
<div className="flex items-center w-full border-2 border-gray-600 rounded px-4 py-2 focus-within:border-blue-500">
|
<div className="flex items-center w-full border-2 border-gray-600 rounded px-4 py-2 focus-within:border-blue-500">
|
||||||
<MagnifyingGlass className="text-gray-400" />
|
<MagnifyingGlass className="text-gray-400" />
|
||||||
<textarea
|
<textarea
|
||||||
|
autoFocus
|
||||||
className="flex-grow ml-2 h-6 bg-transparent outline-none placeholder-gray-400 overflow-hidden whitespace-normal resize-none"
|
className="flex-grow ml-2 h-6 bg-transparent outline-none placeholder-gray-400 overflow-hidden whitespace-normal resize-none"
|
||||||
role="textarea"
|
role="textarea"
|
||||||
aria-multiline
|
aria-multiline
|
||||||
|
@ -86,21 +86,34 @@ export const SearchResultsDisplay: React.FC<SearchResultsDisplayProps> = ({
|
|||||||
<LoadingAnimation text="Finding quotes" size="text-sm" />
|
<LoadingAnimation text="Finding quotes" size="text-sm" />
|
||||||
) : (
|
) : (
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
{dedupedQuotes.map((quoteInfo) => (
|
{dedupedQuotes.length > 0 ? (
|
||||||
<a
|
dedupedQuotes.map((quoteInfo) => (
|
||||||
key={quoteInfo.document_id}
|
<a
|
||||||
className="p-2 ml-1 border border-gray-800 rounded-lg text-sm flex max-w-[280px] hover:bg-gray-800"
|
key={quoteInfo.document_id}
|
||||||
href={quoteInfo.link}
|
className="p-2 ml-1 border border-gray-800 rounded-lg text-sm flex max-w-[280px] hover:bg-gray-800"
|
||||||
target="_blank"
|
href={quoteInfo.link}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
{getSourceIcon(quoteInfo.source_type, "20")}
|
>
|
||||||
<p className="truncate break-all ml-2">
|
{getSourceIcon(quoteInfo.source_type, "20")}
|
||||||
{quoteInfo.semantic_identifier ||
|
<p className="truncate break-all ml-2">
|
||||||
quoteInfo.document_id}
|
{quoteInfo.semantic_identifier ||
|
||||||
</p>
|
quoteInfo.document_id}
|
||||||
</a>
|
</p>
|
||||||
))}
|
</a>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<div className="flex">
|
||||||
|
<InfoIcon
|
||||||
|
size="20"
|
||||||
|
className="text-red-500 my-auto flex flex-shrink-0"
|
||||||
|
/>
|
||||||
|
<div className="text-red-500 text-sm my-auto ml-1">
|
||||||
|
Did not find any exact quotes to support the above
|
||||||
|
answer.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@ -126,30 +139,28 @@ export const SearchResultsDisplay: React.FC<SearchResultsDisplayProps> = ({
|
|||||||
<div className="font-bold border-b mb-4 pb-1 border-gray-800">
|
<div className="font-bold border-b mb-4 pb-1 border-gray-800">
|
||||||
Results
|
Results
|
||||||
</div>
|
</div>
|
||||||
{removeDuplicateDocs(documents)
|
{removeDuplicateDocs(documents).map((doc) => (
|
||||||
.slice(0, 7)
|
<div
|
||||||
.map((doc) => (
|
key={doc.semantic_identifier}
|
||||||
<div
|
className="text-sm border-b border-gray-800 mb-3"
|
||||||
key={doc.semantic_identifier}
|
>
|
||||||
className="text-sm border-b border-gray-800 mb-3"
|
<a
|
||||||
|
className={
|
||||||
|
"rounded-lg flex font-bold " +
|
||||||
|
(doc.link ? "" : "pointer-events-none")
|
||||||
|
}
|
||||||
|
href={doc.link}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
>
|
>
|
||||||
<a
|
{getSourceIcon(doc.source_type, "20")}
|
||||||
className={
|
<p className="truncate break-all ml-2">
|
||||||
"rounded-lg flex font-bold " +
|
{doc.semantic_identifier || doc.document_id}
|
||||||
(doc.link ? "" : "pointer-events-none")
|
</p>
|
||||||
}
|
</a>
|
||||||
href={doc.link}
|
<p className="pl-1 py-3 text-gray-200">{doc.blurb}</p>
|
||||||
target="_blank"
|
</div>
|
||||||
rel="noopener noreferrer"
|
))}
|
||||||
>
|
|
||||||
{getSourceIcon(doc.source_type, "20")}
|
|
||||||
<p className="truncate break-all ml-2">
|
|
||||||
{doc.semantic_identifier || doc.document_id}
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
<p className="pl-1 py-3 text-gray-200">{doc.blurb}</p>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { SearchBar } from "./SearchBar";
|
import { SearchBar } from "./SearchBar";
|
||||||
import { SearchResultsDisplay } from "./SearchResultsDisplay";
|
import { SearchResultsDisplay } from "./SearchResultsDisplay";
|
||||||
import { SourceSelector } from "./Filters";
|
import { SourceSelector } from "./Filters";
|
||||||
@ -19,6 +19,7 @@ import {
|
|||||||
import { searchRequestStreamed } from "@/lib/search/streaming";
|
import { searchRequestStreamed } from "@/lib/search/streaming";
|
||||||
import Cookies from "js-cookie";
|
import Cookies from "js-cookie";
|
||||||
import { SearchHelper } from "./SearchHelper";
|
import { SearchHelper } from "./SearchHelper";
|
||||||
|
import { CancellationToken, cancellable } from "@/lib/search/cancellable";
|
||||||
|
|
||||||
const SEARCH_DEFAULT_OVERRIDES_START: SearchDefaultOverrides = {
|
const SEARCH_DEFAULT_OVERRIDES_START: SearchDefaultOverrides = {
|
||||||
forceDisplayQA: false,
|
forceDisplayQA: false,
|
||||||
@ -88,21 +89,43 @@ export const SearchSection: React.FC<SearchSectionProps> = ({
|
|||||||
suggestedFlowType,
|
suggestedFlowType,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
let lastSearchCancellationToken = useRef<CancellationToken | null>(null);
|
||||||
const onSearch = async ({
|
const onSearch = async ({
|
||||||
searchType,
|
searchType,
|
||||||
offset,
|
offset,
|
||||||
}: SearchRequestOverrides = {}) => {
|
}: SearchRequestOverrides = {}) => {
|
||||||
|
// cancel the prior search if it hasn't finished
|
||||||
|
if (lastSearchCancellationToken.current) {
|
||||||
|
lastSearchCancellationToken.current.cancel();
|
||||||
|
}
|
||||||
|
lastSearchCancellationToken.current = new CancellationToken();
|
||||||
|
|
||||||
setIsFetching(true);
|
setIsFetching(true);
|
||||||
setSearchResponse(initialSearchResponse);
|
setSearchResponse(initialSearchResponse);
|
||||||
|
|
||||||
await searchRequestStreamed({
|
await searchRequestStreamed({
|
||||||
query,
|
query,
|
||||||
sources,
|
sources,
|
||||||
updateCurrentAnswer,
|
updateCurrentAnswer: cancellable({
|
||||||
updateQuotes,
|
cancellationToken: lastSearchCancellationToken.current,
|
||||||
updateDocs,
|
fn: updateCurrentAnswer,
|
||||||
updateSuggestedSearchType,
|
}),
|
||||||
updateSuggestedFlowType,
|
updateQuotes: cancellable({
|
||||||
|
cancellationToken: lastSearchCancellationToken.current,
|
||||||
|
fn: updateQuotes,
|
||||||
|
}),
|
||||||
|
updateDocs: cancellable({
|
||||||
|
cancellationToken: lastSearchCancellationToken.current,
|
||||||
|
fn: updateDocs,
|
||||||
|
}),
|
||||||
|
updateSuggestedSearchType: cancellable({
|
||||||
|
cancellationToken: lastSearchCancellationToken.current,
|
||||||
|
fn: updateSuggestedSearchType,
|
||||||
|
}),
|
||||||
|
updateSuggestedFlowType: cancellable({
|
||||||
|
cancellationToken: lastSearchCancellationToken.current,
|
||||||
|
fn: updateSuggestedFlowType,
|
||||||
|
}),
|
||||||
selectedSearchType: searchType ?? selectedSearchType,
|
selectedSearchType: searchType ?? selectedSearchType,
|
||||||
offset: offset ?? defaultOverrides.offset,
|
offset: offset ?? defaultOverrides.offset,
|
||||||
});
|
});
|
||||||
|
42
web/src/lib/search/cancellable.ts
Normal file
42
web/src/lib/search/cancellable.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
export class CancellationToken {
|
||||||
|
private shouldCancel = false;
|
||||||
|
|
||||||
|
cancel() {
|
||||||
|
this.shouldCancel = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isCancellationRequested() {
|
||||||
|
return this.shouldCancel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CancellableArgs {
|
||||||
|
cancellationToken: CancellationToken;
|
||||||
|
fn: (...args: any[]) => any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cancellable = ({ cancellationToken, fn }: CancellableArgs) => {
|
||||||
|
return (...args: any[]): any => {
|
||||||
|
if (cancellationToken.isCancellationRequested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return fn(...args);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface AsyncCancellableArgs {
|
||||||
|
cancellationToken: CancellationToken;
|
||||||
|
fn: (...args: any[]) => Promise<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const asyncCancellable = ({
|
||||||
|
cancellationToken,
|
||||||
|
fn,
|
||||||
|
}: AsyncCancellableArgs) => {
|
||||||
|
return async (...args: any[]): Promise<any> => {
|
||||||
|
if (cancellationToken.isCancellationRequested) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return await fn(...args);
|
||||||
|
};
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user