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:
Weves 2023-06-29 20:38:34 -07:00 committed by Chris Weaver
parent cb59e77278
commit af329d31fb
4 changed files with 121 additions and 44 deletions

View File

@ -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">
<MagnifyingGlass className="text-gray-400" />
<textarea
autoFocus
className="flex-grow ml-2 h-6 bg-transparent outline-none placeholder-gray-400 overflow-hidden whitespace-normal resize-none"
role="textarea"
aria-multiline

View File

@ -86,21 +86,34 @@ export const SearchResultsDisplay: React.FC<SearchResultsDisplayProps> = ({
<LoadingAnimation text="Finding quotes" size="text-sm" />
) : (
<div className="flex">
{dedupedQuotes.map((quoteInfo) => (
<a
key={quoteInfo.document_id}
className="p-2 ml-1 border border-gray-800 rounded-lg text-sm flex max-w-[280px] hover:bg-gray-800"
href={quoteInfo.link}
target="_blank"
rel="noopener noreferrer"
>
{getSourceIcon(quoteInfo.source_type, "20")}
<p className="truncate break-all ml-2">
{quoteInfo.semantic_identifier ||
quoteInfo.document_id}
</p>
</a>
))}
{dedupedQuotes.length > 0 ? (
dedupedQuotes.map((quoteInfo) => (
<a
key={quoteInfo.document_id}
className="p-2 ml-1 border border-gray-800 rounded-lg text-sm flex max-w-[280px] hover:bg-gray-800"
href={quoteInfo.link}
target="_blank"
rel="noopener noreferrer"
>
{getSourceIcon(quoteInfo.source_type, "20")}
<p className="truncate break-all ml-2">
{quoteInfo.semantic_identifier ||
quoteInfo.document_id}
</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>
)}
</>
@ -126,30 +139,28 @@ export const SearchResultsDisplay: React.FC<SearchResultsDisplayProps> = ({
<div className="font-bold border-b mb-4 pb-1 border-gray-800">
Results
</div>
{removeDuplicateDocs(documents)
.slice(0, 7)
.map((doc) => (
<div
key={doc.semantic_identifier}
className="text-sm border-b border-gray-800 mb-3"
{removeDuplicateDocs(documents).map((doc) => (
<div
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
className={
"rounded-lg flex font-bold " +
(doc.link ? "" : "pointer-events-none")
}
href={doc.link}
target="_blank"
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>
))}
{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>
)}
</>

View File

@ -1,6 +1,6 @@
"use client";
import { useState } from "react";
import { useRef, useState } from "react";
import { SearchBar } from "./SearchBar";
import { SearchResultsDisplay } from "./SearchResultsDisplay";
import { SourceSelector } from "./Filters";
@ -19,6 +19,7 @@ import {
import { searchRequestStreamed } from "@/lib/search/streaming";
import Cookies from "js-cookie";
import { SearchHelper } from "./SearchHelper";
import { CancellationToken, cancellable } from "@/lib/search/cancellable";
const SEARCH_DEFAULT_OVERRIDES_START: SearchDefaultOverrides = {
forceDisplayQA: false,
@ -88,21 +89,43 @@ export const SearchSection: React.FC<SearchSectionProps> = ({
suggestedFlowType,
}));
let lastSearchCancellationToken = useRef<CancellationToken | null>(null);
const onSearch = async ({
searchType,
offset,
}: SearchRequestOverrides = {}) => {
// cancel the prior search if it hasn't finished
if (lastSearchCancellationToken.current) {
lastSearchCancellationToken.current.cancel();
}
lastSearchCancellationToken.current = new CancellationToken();
setIsFetching(true);
setSearchResponse(initialSearchResponse);
await searchRequestStreamed({
query,
sources,
updateCurrentAnswer,
updateQuotes,
updateDocs,
updateSuggestedSearchType,
updateSuggestedFlowType,
updateCurrentAnswer: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateCurrentAnswer,
}),
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,
offset: offset ?? defaultOverrides.offset,
});

View 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);
};
};