Just display docs if QA fails

This commit is contained in:
Weves 2023-05-20 21:01:58 -07:00 committed by Chris Weaver
parent 0b8c69ceeb
commit 51e05e3948
4 changed files with 116 additions and 76 deletions

View File

@ -9,6 +9,7 @@ import {
Notebook,
Key,
Trash,
Info,
} from "@phosphor-icons/react";
interface IconProps {
@ -66,3 +67,10 @@ export const GoogleDriveIcon = ({
}: IconProps) => {
return <GoogleDriveLogo size={size} className={className} />;
};
export const InfoIcon = ({
size = "16",
className = defaultTailwindCSS,
}: IconProps) => {
return <Info size={size} className={className} />;
};

View File

@ -1,7 +1,8 @@
import React from "react";
import { Quote, Document } from "./types";
import { Quote, Document, SearchResponse } from "./types";
import { getSourceIcon } from "../source";
import { LoadingAnimation } from "../Loading";
import { InfoIcon } from "../icons/icons";
const removeDuplicateDocs = (documents: Document[]) => {
const seen = new Set<string>();
@ -19,33 +20,35 @@ const removeDuplicateDocs = (documents: Document[]) => {
};
interface SearchResultsDisplayProps {
answer: string | null;
quotes: Record<string, Quote> | null;
documents: Document[] | null;
searchResponse: SearchResponse | null;
isFetching: boolean;
}
export const SearchResultsDisplay: React.FC<SearchResultsDisplayProps> = ({
answer,
quotes,
documents,
searchResponse,
isFetching,
}) => {
if (!answer) {
if (isFetching) {
return (
<div className="flex">
<div className="mx-auto">
<LoadingAnimation />
</div>
</div>
);
}
if (!searchResponse) {
return null;
}
if (answer === null) {
return <div>Unable to find an answer</div>;
if (isFetching) {
return (
<div className="flex">
<div className="mx-auto">
<LoadingAnimation />
</div>
</div>
);
}
const { answer, quotes, documents } = searchResponse;
if (answer === null && documents === null && quotes === null) {
return (
<div className="text-red-800">
Something went wrong, please try again.
</div>
);
}
const dedupedQuotes: Quote[] = [];
@ -61,38 +64,53 @@ export const SearchResultsDisplay: React.FC<SearchResultsDisplayProps> = ({
return (
<>
<div className="p-4 border-2 rounded-md border-gray-700">
<div className="flex mb-1">
<h2 className="text font-bold my-auto">AI Answer</h2>
</div>
<p className="mb-4">{answer}</p>
{answer && (
<div className="p-4 border-2 rounded-md border-gray-700">
<div className="flex mb-1">
<h2 className="text font-bold my-auto">AI Answer</h2>
</div>
<p className="mb-4">{answer}</p>
{quotes !== null && (
<>
<h2 className="text-sm font-bold mb-2">Sources</h2>
{isFetching && dedupedQuotes.length === 0 ? (
<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-[230px] hover:bg-gray-800"
href={quoteInfo.link}
target="_blank"
rel="noopener noreferrer"
>
{getSourceIcon(quoteInfo.source_type, "20")}
<p className="truncate break-all ml-0.5">
{quoteInfo.semantic_identifier || quoteInfo.document_id}
</p>
</a>
))}
</div>
)}
</>
)}
</div>
)}
{!answer && !isFetching && (
<div className="flex">
<InfoIcon
size="20"
className="text-red-800 my-auto flex flex-shrink-0"
/>
<div className="text-red-800 text-xs my-auto ml-1">
GPT hurt itself in its confusion :(
</div>
</div>
)}
{quotes !== null && (
<>
<h2 className="text-sm font-bold mb-2">Sources</h2>
{isFetching && dedupedQuotes.length === 0 ? (
<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-[230px] hover:bg-gray-800"
href={quoteInfo.link}
target="_blank"
rel="noopener noreferrer"
>
{getSourceIcon(quoteInfo.source_type, "20")}
<p className="truncate break-all ml-0.5">
{quoteInfo.semantic_identifier || quoteInfo.document_id}
</p>
</a>
))}
</div>
)}
</>
)}
</div>
{/* Only display docs once we're done fetching to avoid distracting from the AI answer*/}
{!isFetching && documents && documents.length > 0 && (
<div className="mt-4">
@ -103,7 +121,7 @@ export const SearchResultsDisplay: React.FC<SearchResultsDisplayProps> = ({
.slice(0, 7)
.map((doc) => (
<div
key={doc.document_id}
key={doc.semantic_identifier}
className="text-sm border-b border-gray-800 mb-3"
>
<a

View File

@ -3,7 +3,13 @@
import { useState } from "react";
import { SearchBar } from "./SearchBar";
import { SearchResultsDisplay } from "./SearchResultsDisplay";
import { Quote, Document } from "./types";
import { Quote, Document, SearchResponse } from "./types";
const initialSearchResponse: SearchResponse = {
answer: null,
quotes: null,
documents: null,
};
const processSingleChunk = (
chunk: string,
@ -99,9 +105,6 @@ const searchRequestStreamed = async (
answer += answerChunk;
updateCurrentAnswer(answer);
} else if (chunk.answer_finished) {
// set quotes as non-null to signify that the answer is finished and
// we're now looking for quotes
updateQuotes({});
if (
!answer.endsWith(".") &&
!answer.endsWith("?") &&
@ -129,9 +132,9 @@ const searchRequestStreamed = async (
};
export const SearchSection: React.FC<{}> = () => {
const [answer, setAnswer] = useState<string | null>("");
const [quotes, setQuotes] = useState<Record<string, Quote> | null>(null);
const [documents, setDocuments] = useState<Document[] | null>(null);
const [searchResponse, setSearchResponse] = useState<SearchResponse | null>(
null
);
const [isFetching, setIsFetching] = useState(false);
return (
@ -139,26 +142,36 @@ export const SearchSection: React.FC<{}> = () => {
<SearchBar
onSearch={(query) => {
setIsFetching(true);
setAnswer(null);
setQuotes(null);
setDocuments(null);
searchRequestStreamed(query, setAnswer, setQuotes, setDocuments).then(
({ quotes }) => {
setIsFetching(false);
// if no quotes were given, set to empty object so that the SearchResultsDisplay
// component knows that the search was successful but no quotes were found
if (!quotes) {
setQuotes({});
}
}
);
setSearchResponse({
answer: null,
quotes: null,
documents: null,
});
searchRequestStreamed(
query,
(answer) =>
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
answer,
})),
(quotes) =>
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
quotes,
})),
(documents) =>
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
documents,
}))
).then(() => {
setIsFetching(false);
});
}}
/>
<div className="mt-2">
<SearchResultsDisplay
answer={answer}
quotes={quotes}
documents={documents}
searchResponse={searchResponse}
isFetching={isFetching}
/>
</div>

View File

@ -17,6 +17,7 @@ export interface Document {
}
export interface SearchResponse {
answer: string;
quotes: Record<string, Quote>;
answer: string | null;
quotes: Record<string, Quote> | null;
documents: Document[] | null;
}