Add answers to search (#2020)

This commit is contained in:
pablodanswer
2024-08-04 23:02:55 -07:00
committed by GitHub
parent 76b7792e69
commit 6d67d472cd
47 changed files with 806 additions and 445 deletions

View File

@@ -94,6 +94,7 @@ const AssistantCard = ({
))}
</div>
)}
<div className="text-xs text-subtle">
<span className="font-semibold">Default model:</span>{" "}
{getDisplayNameForModel(

View File

@@ -36,7 +36,7 @@ import ToggleSearch from "./WrappedSearch";
import {
AGENTIC_SEARCH_TYPE_COOKIE_NAME,
NEXT_PUBLIC_DEFAULT_SIDEBAR_OPEN,
DISABLE_AGENTIC_SEARCH,
DISABLE_LLM_DOC_RELEVANCE,
} from "@/lib/constants";
import WrappedSearch from "./WrappedSearch";
@@ -206,7 +206,7 @@ export default async function Home() {
<InstantSSRAutoRefresh />
<WrappedSearch
disabledAgentic={DISABLE_AGENTIC_SEARCH}
disabledAgentic={DISABLE_LLM_DOC_RELEVANCE}
initiallyToggled={toggleSidebar}
querySessions={querySessions}
user={user}

View File

@@ -1711,24 +1711,9 @@ export const ThumbsUpIcon = ({
size = 16,
className = defaultTailwindCSS,
}: IconProps) => {
return (
<svg
style={{ width: `${size}px`, height: `${size}px` }}
className={`w-[${size}px] h-[${size}px] ` + className}
xmlns="http://www.w3.org/2000/svg"
width="200"
height="200"
viewBox="0 0 20 20"
>
<path
fill="currentColor"
fillRule="evenodd"
d="M10 2c-2.236 0-4.43.18-6.57.524C1.993 2.755 1 4.014 1 5.426v5.148c0 1.413.993 2.67 2.43 2.902c1.168.188 2.352.327 3.55.414c.28.02.521.18.642.413l1.713 3.293a.75.75 0 0 0 1.33 0l1.713-3.293a.783.783 0 0 1 .642-.413a41.102 41.102 0 0 0 3.55-.414c1.437-.231 2.43-1.49 2.43-2.902V5.426c0-1.413-.993-2.67-2.43-2.902A41.289 41.289 0 0 0 10 2ZM6.75 6a.75.75 0 0 0 0 1.5h6.5a.75.75 0 0 0 0-1.5h-6.5Zm0 2.5a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5h-3.5Z"
clipRule="evenodd"
/>
</svg>
);
return <FiThumbsUp size={size} className={className} />;
};
export const RobotIcon = ({
size = 16,
className = defaultTailwindCSS,

View File

@@ -38,9 +38,11 @@ export const TODAY = "Today";
export function DateRangeSelector({
value,
onValueChange,
isHoritontal,
}: {
value: DateRangePickerValue | null;
onValueChange: (value: DateRangePickerValue | null) => void;
isHoritontal?: boolean;
}) {
return (
<div>
@@ -106,6 +108,7 @@ export function DateRangeSelector({
flex
text-sm
px-3
line-clamp-1
py-1.5
rounded-lg
border
@@ -113,12 +116,16 @@ export function DateRangeSelector({
cursor-pointer
hover:bg-hover`}
>
<FiCalendar className="my-auto mr-2" />{" "}
{value?.selectValue ? (
<div className="text-emphasis">{value.selectValue}</div>
) : (
"Any time..."
)}
<FiCalendar className="flex-none my-auto mr-2" />{" "}
<p className="line-clamp-1">
{value?.selectValue ? (
<div className="text-emphasis">{value.selectValue}</div>
) : isHoritontal ? (
"Date"
) : (
"Any time..."
)}
</p>
{value?.selectValue ? (
<div
className="my-auto ml-auto p-0.5 rounded-full w-fit"

View File

@@ -16,7 +16,7 @@ import { BookIcon, CheckmarkIcon, LightBulbIcon, XIcon } from "../icons/icons";
import { FaStar } from "react-icons/fa";
import { FiTag } from "react-icons/fi";
import { DISABLE_AGENTIC_SEARCH } from "@/lib/constants";
import { DISABLE_LLM_DOC_RELEVANCE } from "@/lib/constants";
import { SettingsContext } from "../settings/SettingsProvider";
export const buildDocumentSummaryDisplay = (

View File

@@ -1,15 +1,18 @@
import React, { KeyboardEvent, ChangeEvent, useContext } from "react";
import { searchState } from "./SearchSection";
import { MagnifyingGlass } from "@phosphor-icons/react";
interface FullSearchBarProps {
query: string;
setQuery: (query: string) => void;
onSearch: (fast?: boolean) => void;
searchState?: searchState;
agentic?: boolean;
toggleAgentic?: () => void;
ccPairs: CCPairBasicInfo[];
documentSets: DocumentSet[];
filterManager: any; // You might want to replace 'any' with a more specific type
finalAvailableDocumentSets: DocumentSet[];
finalAvailableSources: string[];
tags: Tag[];
}
import { useState, useEffect, useRef } from "react";
@@ -18,6 +21,9 @@ import { Divider } from "@tremor/react";
import { CustomTooltip } from "../tooltip/CustomTooltip";
import KeyboardSymbol from "@/lib/browserUtilities";
import { SettingsContext } from "../settings/SettingsProvider";
import { HorizontalSourceSelector, SourceSelector } from "./filtering/Filters";
import { CCPairBasicInfo, DocumentSet, Tag } from "@/lib/types";
import { SourceMetadata } from "@/lib/search/interfaces";
export const AnimatedToggle = ({
isOn,
@@ -116,12 +122,17 @@ export const AnimatedToggle = ({
export default AnimatedToggle;
export const FullSearchBar = ({
searchState,
query,
setQuery,
onSearch,
agentic,
toggleAgentic,
ccPairs,
documentSets,
filterManager,
finalAvailableDocumentSets,
finalAvailableSources,
tags,
}: FullSearchBarProps) => {
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
const target = event.target;
@@ -196,47 +207,44 @@ export const FullSearchBar = ({
suppressContentEditableWarning={true}
/>
<div className="flex justify-end w-full items-center space-x-3 mr-12 px-4 pb-2">
{searchState == "searching" && (
<div key={"Reading"} className="mr-auto relative inline-block">
<span className="loading-text">Searching...</span>
</div>
)}
{searchState == "reading" && (
<div key={"Reading"} className="mr-auto relative inline-block">
<span className="loading-text">
Reading{settings?.isMobile ? "" : " Documents"}...
</span>
</div>
)}
{searchState == "analyzing" && (
<div key={"Generating"} className="mr-auto relative inline-block">
<span className="loading-text">
Generating{settings?.isMobile ? "" : " Analysis"}...
</span>
</div>
)}
{toggleAgentic && (
<AnimatedToggle isOn={agentic!} handleToggle={toggleAgentic} />
)}
<div className="my-auto pl-2">
<button
onClick={() => {
onSearch(agentic);
}}
className="flex my-auto cursor-pointer"
>
<SendIcon
size={28}
className={`text-emphasis text-white p-1 rounded-full ${
query ? "bg-background-800" : "bg-[#D7D7D7]"
}`}
<div
className={`flex 2xl:justify-end justify-between w-full items-center space-x-3 px-4 pb-2`}
>
{/* <div className="absolute z-10 mobile:px-4 mobile:max-w-searchbar-max mobile:w-[90%] top-12 desktop:left-0 hidden 2xl:block mobile:left-1/2 mobile:transform mobile:-translate-x-1/2 desktop:w-52 3xl:w-64"> */}
<div className="2xl:hidden">
{(ccPairs.length > 0 || documentSets.length > 0) && (
<HorizontalSourceSelector
isHorizontal
{...filterManager}
showDocSidebar={false}
availableDocumentSets={finalAvailableDocumentSets}
existingSources={finalAvailableSources}
availableTags={tags}
/>
</button>
)}
</div>
{/* ccPairs, documentSets, filterManager, finalAvailableDocumentSets, finalAvailableSources, tags */}
{/* </div>/ */}
<div className="flex my-auto gap-x-3">
{toggleAgentic && (
<AnimatedToggle isOn={agentic!} handleToggle={toggleAgentic} />
)}
<div className="my-auto pl-2">
<button
onClick={() => {
onSearch(agentic);
}}
className="flex my-auto cursor-pointer"
>
<SendIcon
size={28}
className={`text-emphasis text-white p-1 rounded-full ${
query ? "bg-background-800" : "bg-[#D7D7D7]"
}`}
/>
</button>
</div>
</div>
</div>
<div className="absolute bottom-2.5 right-10"></div>

View File

@@ -1,24 +1,19 @@
"use client";
import { removeDuplicateDocs } from "@/lib/documentUtils";
import {
DanswerDocument,
DocumentRelevance,
FlowType,
Quote,
Relevance,
SearchDanswerDocument,
SearchDefaultOverrides,
SearchResponse,
ValidQuestionResponse,
} from "@/lib/search/interfaces";
import { usePopup } from "../admin/connectors/Popup";
import { AlertIcon, BroomIcon, UndoIcon } from "../icons/icons";
import { AgenticDocumentDisplay, DocumentDisplay } from "./DocumentDisplay";
import { searchState } from "./SearchSection";
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { Tooltip } from "../tooltip/Tooltip";
import KeyboardSymbol from "@/lib/browserUtilities";
import { SettingsContext } from "../settings/SettingsProvider";
const getSelectedDocumentIds = (
documents: SearchDanswerDocument[],
@@ -135,31 +130,17 @@ export const SearchResultsDisplay = ({
);
}
const dedupedQuotes: Quote[] = [];
const seen = new Set<string>();
if (quotes) {
quotes.forEach((quote) => {
if (!seen.has(quote.document_id)) {
dedupedQuotes.push(quote);
seen.add(quote.document_id);
}
});
}
const selectedDocumentIds = getSelectedDocumentIds(
documents || [],
searchResponse.selectedDocIndices || []
);
const relevantDocs = documents
? documents.filter((doc) => {
return (
showAll ||
(searchResponse &&
searchResponse.additional_relevance &&
searchResponse.additional_relevance[
`${doc.document_id}-${doc.chunk_ind}`
].relevant) ||
searchResponse.additional_relevance[doc.document_id].relevant) ||
doc.is_relevant
);
})
@@ -183,6 +164,7 @@ export const SearchResultsDisplay = ({
return (
<>
{popup}
{documents && documents.length == 0 && (
<p className="flex text-lg font-bold">
No docs found! Ensure that you have enabled at least one connector
@@ -248,9 +230,7 @@ export const SearchResultsDisplay = ({
{uniqueDocuments.map((document, ind) => {
const relevance: DocumentRelevance | null =
searchResponse.additional_relevance
? searchResponse.additional_relevance[
`${document.document_id}-${document.chunk_ind}`
]
? searchResponse.additional_relevance[document.document_id]
: null;
return agenticResults ? (

View File

@@ -17,13 +17,11 @@ import {
SearchDanswerDocument,
} from "@/lib/search/interfaces";
import { searchRequestStreamed } from "@/lib/search/streamingQa";
import { CancellationToken, cancellable } from "@/lib/search/cancellable";
import { useFilters, useObjectState } from "@/lib/hooks";
import { questionValidationStreamed } from "@/lib/search/streamingQuestionValidation";
import { Persona } from "@/app/admin/assistants/interfaces";
import { computeAvailableFilters } from "@/lib/filters";
import { redirect, useRouter, useSearchParams } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { SettingsContext } from "../settings/SettingsProvider";
import { HistorySidebar } from "@/app/chat/sessionSidebar/HistorySidebar";
import { ChatSession, SearchSession } from "@/app/chat/interfaces";
@@ -33,13 +31,19 @@ import { SIDEBAR_TOGGLED_COOKIE_NAME } from "../resizable/constants";
import { AGENTIC_SEARCH_TYPE_COOKIE_NAME } from "@/lib/constants";
import Cookies from "js-cookie";
import FixedLogo from "@/app/chat/shared_chat_search/FixedLogo";
import { AnswerSection } from "./results/AnswerSection";
import { QuotesSection } from "./results/QuotesSection";
import { QAFeedbackBlock } from "./QAFeedback";
import { usePopup } from "../admin/connectors/Popup";
export type searchState =
| "input"
| "searching"
| "reading"
| "analyzing"
| "summarizing";
| "summarizing"
| "generating"
| "citing";
const SEARCH_DEFAULT_OVERRIDES_START: SearchDefaultOverrides = {
forceDisplayQA: false,
@@ -48,7 +52,6 @@ const SEARCH_DEFAULT_OVERRIDES_START: SearchDefaultOverrides = {
const VALID_QUESTION_RESPONSE_DEFAULT: ValidQuestionResponse = {
reasoning: null,
answerable: null,
error: null,
};
@@ -223,35 +226,48 @@ export const SearchSection = ({
additional_relevance: undefined,
};
// Streaming updates
const updateCurrentAnswer = (answer: string) =>
const updateCurrentAnswer = (answer: string) => {
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
answer,
}));
const updateQuotes = (quotes: Quote[]) =>
setSearchState((searchState) => {
if (searchState != "input") {
return "generating";
}
return "input";
});
};
const updateQuotes = (quotes: Quote[]) => {
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
quotes,
}));
setSearchState((searchState) => "input");
};
const updateDocs = (documents: SearchDanswerDocument[]) => {
setTimeout(() => {
setSearchState((searchState) => {
if (searchState != "input") {
return "reading";
}
return "input";
});
}, 1500);
if (agentic) {
setTimeout(() => {
setSearchState((searchState) => {
if (searchState != "input") {
return "reading";
}
return "input";
});
}, 1500);
setTimeout(() => {
setSearchState((searchState) => {
if (searchState != "input") {
return "analyzing";
}
return "input";
});
}, 4500);
setTimeout(() => {
setSearchState((searchState) => {
if (searchState != "input") {
return "analyzing";
}
return "input";
});
}, 4500);
}
setSearchResponse((prevState) => ({
...(prevState || initialSearchResponse),
@@ -294,8 +310,9 @@ export const SearchSection = ({
messageId,
}));
router.refresh();
setSearchState("input");
// setSearchState("input");
setIsFetching(false);
setSearchState((searchState) => "input");
// router.replace(`/search?searchId=${chat_session_id}`);
};
@@ -309,7 +326,11 @@ export const SearchSection = ({
setContentEnriched(true);
setIsFetching(false);
setSearchState("input");
if (disabledAgentic) {
setSearchState("input");
} else {
setSearchState("analyzing");
}
};
const updateComments = (comments: any) => {
@@ -317,7 +338,9 @@ export const SearchSection = ({
};
const finishedSearching = () => {
setSearchState("input");
if (disabledAgentic) {
setSearchState("input");
}
};
const resetInput = () => {
@@ -414,15 +437,7 @@ export const SearchSection = ({
offset: offset ?? defaultOverrides.offset,
};
const questionValidationArgs = {
query,
update: setValidQuestionResponse,
};
await Promise.all([
searchRequestStreamed(searchFnArgs),
questionValidationStreamed(questionValidationArgs),
]);
await Promise.all([searchRequestStreamed(searchFnArgs)]);
};
// handle redirect if search page is disabled
@@ -481,6 +496,20 @@ export const SearchSection = ({
setShowDocSidebar,
mobile: settings?.isMobile,
});
const { answer, quotes, documents, error, messageId } = searchResponse;
const dedupedQuotes: Quote[] = [];
const seen = new Set<string>();
if (quotes) {
quotes.forEach((quote) => {
if (!seen.has(quote.document_id)) {
dedupedQuotes.push(quote);
seen.add(quote.document_id);
}
});
}
const { popup, setPopup } = usePopup();
return (
<>
@@ -600,15 +629,113 @@ export const SearchSection = ({
disabledAgentic ? undefined : toggleAgentic
}
agentic={agentic}
searchState={searchState}
query={query}
setQuery={setQuery}
onSearch={async (agentic?: boolean) => {
setDefaultOverrides(SEARCH_DEFAULT_OVERRIDES_START);
await onSearch({ agentic, offset: 0 });
}}
finalAvailableDocumentSets={finalAvailableDocumentSets}
finalAvailableSources={finalAvailableSources}
filterManager={filterManager}
documentSets={documentSets}
ccPairs={ccPairs}
tags={tags}
/>
</div>
{!firstSearch && (
<div className="my-4 min-h-[16rem] p-4 border-2 border-border rounded-lg relative">
<div>
<div className="flex gap-x-2 mb-1">
<h2 className="text-emphasis font-bold my-auto mb-1 ">
AI Answer
</h2>
{searchState == "generating" && (
<div
key={"generating"}
className="relative inline-block"
>
<span className="loading-text">
Generating response...
</span>
</div>
)}
{searchState == "citing" && (
<div
key={"citing"}
className="relative inline-block"
>
<span className="loading-text">
Generating citations...
</span>
</div>
)}
{searchState == "searching" && (
<div
key={"Reading"}
className="relative inline-block"
>
<span className="loading-text">Searching...</span>
</div>
)}
{searchState == "reading" && (
<div
key={"Reading"}
className="relative inline-block"
>
<span className="loading-text">
Reading{settings?.isMobile ? "" : " Documents"}
...
</span>
</div>
)}
{searchState == "analyzing" && (
<div
key={"Generating"}
className="relative inline-block"
>
<span className="loading-text">
Generating
{settings?.isMobile ? "" : " Analysis"}...
</span>
</div>
)}
</div>
<div className="mb-2 pt-1 border-t border-border w-full">
<AnswerSection
answer={answer}
quotes={quotes}
error={error}
isFetching={isFetching}
/>
</div>
{quotes !== null && answer && (
<div className="pt-1 border-t border-border w-full">
<QuotesSection
quotes={dedupedQuotes}
isFetching={isFetching}
/>
{searchResponse.messageId !== null && (
<div className="absolute right-3 bottom-3">
<QAFeedbackBlock
messageId={searchResponse.messageId}
setPopup={setPopup}
/>
</div>
)}
</div>
)}
</div>
</div>
)}
{!settings?.isMobile && (
<div className="mt-6">

View File

@@ -6,22 +6,23 @@ interface Option {
display: string | JSX.Element;
displayName?: string;
}
export function FilterDropdown({
options,
selected,
handleSelect,
icon,
defaultDisplay,
width = "w-64",
}: {
options: Option[];
selected: string[];
handleSelect: (option: Option) => void;
icon: JSX.Element;
defaultDisplay: string | JSX.Element;
width?: string;
}) {
return (
<div className="w-64">
<div>
<CustomDropdown
dropdown={
<div
@@ -32,7 +33,7 @@ export function FilterDropdown({
bg-background
flex
flex-col
w-64
${width}
max-h-96
overflow-y-auto
overscroll-contain`}
@@ -76,7 +77,7 @@ export function FilterDropdown({
<div
className={`
flex
w-64
${width}
text-sm
px-3
py-1.5

View File

@@ -3,7 +3,14 @@ import { DocumentSet, Tag, ValidSources } from "@/lib/types";
import { SourceMetadata } from "@/lib/search/interfaces";
import { InfoIcon, defaultTailwindCSS } from "../../icons/icons";
import { HoverPopup } from "../../HoverPopup";
import { FiBook, FiBookmark, FiFilter, FiMap, FiX } from "react-icons/fi";
import {
FiBook,
FiBookmark,
FiFilter,
FiMap,
FiTag,
FiX,
} from "react-icons/fi";
import { DateRangeSelector } from "../DateRangeSelector";
import { DateRangePickerValue } from "@tremor/react";
import { FilterDropdown } from "./FilterDropdown";
@@ -72,9 +79,9 @@ export function SourceSelector({
<div
className={`hidden ${
showDocSidebar ? "4xl:block" : "!block"
} duration-1000 ease-out transition-all transform origin-top-right`}
} duration-1000 flex ease-out transition-all transform origin-top-right`}
>
<div className="flex mb-4 pb-2 border-b border-border text-emphasis">
<div className=" mb-4 pb-2 border-b border-border text-emphasis">
<h2 className="font-bold my-auto">Filters</h2>
<FiFilter className="my-auto ml-2" size="16" />
</div>
@@ -324,3 +331,184 @@ export function HorizontalFilters({
</div>
);
}
export function HorizontalSourceSelector({
timeRange,
setTimeRange,
selectedSources,
setSelectedSources,
selectedDocumentSets,
setSelectedDocumentSets,
selectedTags,
setSelectedTags,
availableDocumentSets,
existingSources,
availableTags,
}: SourceSelectorProps) {
const handleSourceSelect = (source: SourceMetadata) => {
setSelectedSources((prev: SourceMetadata[]) => {
if (prev.map((s) => s.internalName).includes(source.internalName)) {
return prev.filter((s) => s.internalName !== source.internalName);
} else {
return [...prev, source];
}
});
};
const handleDocumentSetSelect = (documentSetName: string) => {
setSelectedDocumentSets((prev: string[]) => {
if (prev.includes(documentSetName)) {
return prev.filter((s) => s !== documentSetName);
} else {
return [...prev, documentSetName];
}
});
};
const handleTagSelect = (tag: Tag) => {
setSelectedTags((prev: Tag[]) => {
if (
prev.some(
(t) => t.tag_key === tag.tag_key && t.tag_value === tag.tag_value
)
) {
return prev.filter(
(t) => !(t.tag_key === tag.tag_key && t.tag_value === tag.tag_value)
);
} else {
return [...prev, tag];
}
});
};
return (
<div className="flex flex-col space-y-4">
<div className="flex space-x-2">
<div className="w-24">
<DateRangeSelector
isHoritontal
value={timeRange}
onValueChange={setTimeRange}
/>
</div>
{existingSources.length > 0 && (
<FilterDropdown
options={listSourceMetadata()
.filter((source) => existingSources.includes(source.internalName))
.map((source) => ({
key: source.internalName,
display: (
<>
<SourceIcon
sourceType={source.internalName}
iconSize={16}
/>
<span className="ml-2 text-sm">{source.displayName}</span>
</>
),
}))}
selected={selectedSources.map((source) => source.internalName)}
handleSelect={(option) =>
handleSourceSelect(
listSourceMetadata().find((s) => s.internalName === option.key)!
)
}
icon={<FiMap size={16} />}
defaultDisplay="Sources"
width="w-fit max-w-24 ellipsis truncate"
/>
)}
{availableDocumentSets.length > 0 && (
<FilterDropdown
options={availableDocumentSets.map((documentSet) => ({
key: documentSet.name,
display: (
<>
<FiBookmark />
<span className="ml-2 text-sm">{documentSet.name}</span>
</>
),
}))}
selected={selectedDocumentSets}
handleSelect={(option) => handleDocumentSetSelect(option.key)}
icon={<FiBook size={16} />}
defaultDisplay="Sets"
width="w-fit max-w-24 ellipsis"
/>
)}
{availableTags.length > 0 && (
<FilterDropdown
options={availableTags.map((tag) => ({
key: `${tag.tag_key}=${tag.tag_value}`,
display: (
<span className="text-sm">
{tag.tag_key}
<b>=</b>
{tag.tag_value}
</span>
),
}))}
selected={selectedTags.map(
(tag) => `${tag.tag_key}=${tag.tag_value}`
)}
handleSelect={(option) => {
const [tag_key, tag_value] = option.key.split("=");
const selectedTag = availableTags.find(
(tag) => tag.tag_key === tag_key && tag.tag_value === tag_value
);
if (selectedTag) {
handleTagSelect(selectedTag);
}
}}
icon={<FiTag size={16} />}
defaultDisplay="Tags"
width="w-fit max-w-24 ellipsis"
/>
)}
</div>
{/* <div className="flex flex-wrap gap-2">
{timeRange && timeRange.selectValue && (
<SelectedBubble onClick={() => setTimeRange(null)}>
<div className="text-sm flex">{timeRange.selectValue}</div>
</SelectedBubble>
)}
{selectedSources.map((source) => (
<SelectedBubble
key={source.internalName}
onClick={() => handleSourceSelect(source)}
>
<>
<SourceIcon sourceType={source.internalName} iconSize={16} />
<span className="ml-2 text-sm">{source.displayName}</span>
</>
</SelectedBubble>
))}
{selectedDocumentSets.map((documentSetName) => (
<SelectedBubble
key={documentSetName}
onClick={() => handleDocumentSetSelect(documentSetName)}
>
<>
<FiBookmark />
<span className="ml-2 text-sm">{documentSetName}</span>
</>
</SelectedBubble>
))}
{selectedTags.map((tag) => (
<SelectedBubble
key={`${tag.tag_key}=${tag.tag_value}`}
onClick={() => handleTagSelect(tag)}
>
<span className="text-sm">
{tag.tag_key}<b>=</b>{tag.tag_value}
</span>
</SelectedBubble>
))}
</div> */}
</div>
);
}

View File

@@ -26,31 +26,28 @@ interface AnswerSectionProps {
answer: string | null;
quotes: Quote[] | null;
error: string | null;
nonAnswerableReason: string | null;
isFetching: boolean;
}
export const AnswerSection = (props: AnswerSectionProps) => {
let status = "in-progress" as StatusOptions;
let header = <>Building answer...</>;
let header = <></>;
let body = null;
// finished answer
if (props.quotes !== null || !props.isFetching) {
status = "success";
header = <>AI answer</>;
if (props.answer) {
body = (
<ReactMarkdown
className="prose text-sm max-w-full"
remarkPlugins={[remarkGfm]}
>
{replaceNewlines(props.answer)}
</ReactMarkdown>
);
} else {
body = <div>Information not found</div>;
}
header = <></>;
body = (
<ReactMarkdown
className="prose text-sm max-w-full"
remarkPlugins={[remarkGfm]}
>
{replaceNewlines(props.answer || "")}
</ReactMarkdown>
);
// error while building answer (NOTE: if error occurs during quote generation
// the above if statement will hit and the error will not be displayed)
} else if (props.error) {
@@ -64,7 +61,7 @@ export const AnswerSection = (props: AnswerSectionProps) => {
// answer is streaming
} else if (props.answer) {
status = "success";
header = <>AI answer</>;
header = <></>;
body = (
<ReactMarkdown
className="prose text-sm max-w-full"
@@ -74,10 +71,6 @@ export const AnswerSection = (props: AnswerSectionProps) => {
</ReactMarkdown>
);
}
if (props.nonAnswerableReason) {
status = "warning";
header = <>Building best effort AI answer...</>;
}
return (
<ResponseSection
@@ -87,20 +80,7 @@ export const AnswerSection = (props: AnswerSectionProps) => {
<div className="ml-2 text-strong">{header}</div>
</div>
}
body={
<div className="">
{body}
{props.nonAnswerableReason && !props.isFetching && (
<div className="mt-4 text-sm">
<b className="font-medium">Warning:</b> the AI did not think this
question was answerable.{" "}
<div className="italic mt-1 ml-2">
{props.nonAnswerableReason}
</div>
</div>
)}
</div>
}
body={<div className="">{body}</div>}
desiredOpenStatus={true}
isNotControllable={true}
/>

View File

@@ -65,7 +65,6 @@ const QuoteDisplay = ({ quoteInfo }: { quoteInfo: Quote }) => {
interface QuotesSectionProps {
quotes: Quote[] | null;
isAnswerable: boolean | null;
isFetching: boolean;
}
@@ -110,11 +109,7 @@ export const QuotesSection = (props: QuotesSectionProps) => {
let status: StatusOptions = "in-progress";
if (!props.isFetching) {
if (props.quotes && props.quotes.length > 0) {
if (props.isAnswerable === false) {
status = "warning";
} else {
status = "success";
}
status = "success";
} else {
status = "failed";
}

View File

@@ -7,6 +7,7 @@ import {
} from "@/components/icons/icons";
import { useState } from "react";
import { Grid } from "react-loader-spinner";
import { searchState } from "../SearchSection";
export type StatusOptions = "in-progress" | "failed" | "warning" | "success";
@@ -31,26 +32,13 @@ export const ResponseSection = ({
let icon = null;
if (status === "in-progress") {
icon = (
<div className="m-auto">
<Grid
height="12"
width="12"
color="#3b82f6"
ariaLabel="grid-loading"
radius="12.5"
wrapperStyle={{}}
wrapperClass=""
visible={true}
/>
</div>
);
icon = <></>;
}
if (status === "failed") {
icon = <AlertIcon size={16} className="text-red-500" />;
}
if (status === "success") {
icon = <CheckmarkIcon size={16} className="text-green-600" />;
icon = <></>;
}
if (status === "warning") {
icon = <TriangleAlertIcon size={16} className="text-yellow-600" />;

View File

@@ -52,5 +52,5 @@ export const CUSTOM_ANALYTICS_ENABLED = process.env.CUSTOM_ANALYTICS_SECRET_KEY
? true
: false;
export const DISABLE_AGENTIC_SEARCH =
process.env.DISABLE_AGENTIC_SEARCH?.toLowerCase() === "true";
export const DISABLE_LLM_DOC_RELEVANCE =
process.env.DISABLE_LLM_DOC_RELEVANCE?.toLowerCase() === "true";

View File

@@ -158,7 +158,6 @@ export interface SearchRequestOverrides {
}
export interface ValidQuestionResponse {
answerable: boolean | null;
reasoning: string | null;
error: string | null;
}

View File

@@ -61,8 +61,7 @@ export const searchRequestStreamed = async ({
filters: filters,
enable_auto_detect_filters: false,
},
llm_doc_eval: true,
skip_gen_ai_answer_generation: true,
evaluation_type: agentic ? "agentic" : "basic",
}),
headers: {
"Content-Type": "application/json",

View File

@@ -1,66 +0,0 @@
import {
AnswerPiecePacket,
ErrorMessagePacket,
ValidQuestionResponse,
} from "./interfaces";
import { processRawChunkString } from "./streamingUtils";
export interface QuestionValidationArgs {
query: string;
update: (update: Partial<ValidQuestionResponse>) => void;
}
export const questionValidationStreamed = async <T>({
query,
update,
}: QuestionValidationArgs) => {
const response = await fetch("/api/query/stream-query-validation", {
method: "POST",
body: JSON.stringify({
query,
}),
headers: {
"Content-Type": "application/json",
},
});
const reader = response.body?.getReader();
const decoder = new TextDecoder("utf-8");
let reasoning = "";
let previousPartialChunk: string | null = null;
while (true) {
const rawChunk = await reader?.read();
if (!rawChunk) {
throw new Error("Unable to process chunk");
}
const { done, value } = rawChunk;
if (done) {
break;
}
const [completedChunks, partialChunk] = processRawChunkString<
AnswerPiecePacket | ValidQuestionResponse | ErrorMessagePacket
>(decoder.decode(value, { stream: true }), previousPartialChunk);
if (!completedChunks.length && !partialChunk) {
break;
}
previousPartialChunk = partialChunk as string | null;
completedChunks.forEach((chunk) => {
if (Object.hasOwn(chunk, "answer_piece")) {
reasoning += (chunk as AnswerPiecePacket).answer_piece;
update({
reasoning,
});
}
if (Object.hasOwn(chunk, "answerable")) {
update({ answerable: (chunk as ValidQuestionResponse).answerable });
}
if (Object.hasOwn(chunk, "error")) {
update({ error: (chunk as ErrorMessagePacket).error });
}
});
}
};