Enhance document explorer

This commit is contained in:
Weves 2023-10-31 08:40:39 -07:00 committed by Chris Weaver
parent 31bfd015ae
commit 23ee45c033
14 changed files with 624 additions and 227 deletions

View File

@ -19,6 +19,7 @@ from danswer.document_index.factory import get_default_document_index
from danswer.document_index.vespa.index import VespaIndex
from danswer.search.access_filters import build_access_filters_for_user
from danswer.search.danswer_helper import recommend_search_flow
from danswer.search.models import BaseFilters
from danswer.search.models import IndexFilters
from danswer.search.search_runner import chunks_to_search_docs
from danswer.search.search_runner import danswer_search
@ -47,6 +48,7 @@ router = APIRouter()
class AdminSearchRequest(BaseModel):
query: str
filters: BaseFilters
class AdminSearchResponse(BaseModel):
@ -64,9 +66,9 @@ def admin_search(
user_acl_filters = build_access_filters_for_user(user, db_session)
final_filters = IndexFilters(
source_type=None,
document_set=None,
time_cutoff=None,
source_type=question.filters.source_type,
document_set=question.filters.document_set,
time_cutoff=question.filters.time_cutoff,
access_control_list=user_acl_filters,
)
document_index = get_default_document_index()

View File

@ -0,0 +1,214 @@
"use client";
import { adminSearch } from "./lib";
import { MagnifyingGlass } from "@phosphor-icons/react";
import { useState, useEffect } from "react";
import { DanswerDocument } from "@/lib/search/interfaces";
import { getSourceIcon } from "@/components/source";
import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay";
import { CustomCheckbox } from "@/components/CustomCheckbox";
import { updateHiddenStatus } from "../lib";
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
import { getErrorMsg } from "@/lib/fetchUtils";
import { ScoreSection } from "../ScoreEditor";
import { useRouter } from "next/navigation";
import { HorizontalFilters } from "@/components/search/filtering/Filters";
import { useFilters } from "@/lib/hooks";
import { buildFilters } from "@/lib/search/utils";
import { DocumentUpdatedAtBadge } from "@/components/search/DocumentUpdatedAtBadge";
import { Connector, DocumentSet } from "@/lib/types";
const DocumentDisplay = ({
document,
refresh,
setPopup,
}: {
document: DanswerDocument;
refresh: () => void;
setPopup: (popupSpec: PopupSpec | null) => void;
}) => {
return (
<div
key={document.document_id}
className="text-sm border-b border-gray-800 mb-3"
>
<div className="flex relative">
<a
className={
"rounded-lg flex font-bold " +
(document.link ? "" : "pointer-events-none")
}
href={document.link}
target="_blank"
rel="noopener noreferrer"
>
{getSourceIcon(document.source_type, 22)}
<p className="truncate break-all ml-2 my-auto text-base">
{document.semantic_identifier || document.document_id}
</p>
</a>
</div>
<div className="flex flex-wrap gap-x-2 mt-1 text-xs">
<div className="px-1 py-0.5 bg-gray-700 rounded flex">
<p className="mr-1 my-auto">Boost:</p>
<ScoreSection
documentId={document.document_id}
initialScore={document.boost}
setPopup={setPopup}
refresh={refresh}
consistentWidth={false}
/>
</div>
<div
onClick={async () => {
const response = await updateHiddenStatus(
document.document_id,
!document.hidden
);
if (response.ok) {
refresh();
} else {
setPopup({
type: "error",
message: `Failed to update document - ${getErrorMsg(
response
)}}`,
});
}
}}
className="px-1 py-0.5 bg-gray-700 hover:bg-gray-600 rounded flex cursor-pointer select-none"
>
<div className="my-auto">
{document.hidden ? (
<div className="text-red-500">Hidden</div>
) : (
"Visible"
)}
</div>
<div className="ml-1 my-auto">
<CustomCheckbox checked={!document.hidden} />
</div>
</div>
</div>
{document.updated_at && (
<div className="mt-2">
<DocumentUpdatedAtBadge updatedAt={document.updated_at} />
</div>
)}
<p className="pl-1 pt-2 pb-3 text-gray-200 break-words">
{buildDocumentSummaryDisplay(document.match_highlights, document.blurb)}
</p>
</div>
);
};
export function Explorer({
initialSearchValue,
connectors,
documentSets,
}: {
initialSearchValue: string | undefined;
connectors: Connector<any>[];
documentSets: DocumentSet[];
}) {
console.log(connectors);
const router = useRouter();
const { popup, setPopup } = usePopup();
const [query, setQuery] = useState(initialSearchValue || "");
const [timeoutId, setTimeoutId] = useState<number | null>(null);
const [results, setResults] = useState<DanswerDocument[]>([]);
const filterManager = useFilters();
const onSearch = async (query: string) => {
const filters = buildFilters(
filterManager.selectedSources,
filterManager.selectedDocumentSets,
filterManager.timeRange
);
const results = await adminSearch(query, filters);
if (results.ok) {
setResults((await results.json()).documents);
}
setTimeoutId(null);
};
useEffect(() => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
if (query && query.trim() !== "") {
router.replace(
`/admin/documents/explorer?query=${encodeURIComponent(query)}`
);
const newTimeoutId = window.setTimeout(() => onSearch(query), 300);
setTimeoutId(newTimeoutId);
} else {
setResults([]);
}
}, [
query,
filterManager.selectedDocumentSets,
filterManager.selectedSources,
filterManager.timeRange,
]);
return (
<div>
{popup}
<div className="justify-center py-2">
<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
placeholder="Find documents based on title / content..."
value={query}
onChange={(event) => {
setQuery(event.target.value);
}}
onKeyDown={(event) => {
if (event.key === "Enter" && !event.shiftKey) {
onSearch(query);
event.preventDefault();
}
}}
suppressContentEditableWarning={true}
/>
</div>
<div className="mt-4">
<HorizontalFilters
{...filterManager}
availableDocumentSets={documentSets}
existingSources={connectors.map((connector) => connector.source)}
/>
</div>
</div>
{results.length > 0 && (
<div className="mt-3">
{results.map((document) => {
return (
<DocumentDisplay
key={document.document_id}
document={document}
refresh={() => onSearch(query)}
setPopup={setPopup}
/>
);
})}
</div>
)}
{!query && (
<div className="flex text-gray-400 mt-3">
Search for a document above to modify it&apos;s boost or hide it from
searches.
</div>
)}
</div>
);
}

View File

@ -1,4 +1,6 @@
export const adminSearch = async (query: string) => {
import { Filters } from "@/lib/search/interfaces";
export const adminSearch = async (query: string, filters: Filters) => {
const response = await fetch("/api/admin/search", {
method: "POST",
headers: {
@ -6,6 +8,7 @@ export const adminSearch = async (query: string) => {
},
body: JSON.stringify({
query,
filters,
}),
});
return response;

View File

@ -1,193 +1,27 @@
"use client";
import { AdminPageTitle } from "@/components/admin/Title";
import { ZoomInIcon } from "@/components/icons/icons";
import { adminSearch } from "./lib";
import { MagnifyingGlass } from "@phosphor-icons/react";
import { useState, useEffect } from "react";
import { DanswerDocument } from "@/lib/search/interfaces";
import { FiZap } from "react-icons/fi";
import { getSourceIcon } from "@/components/source";
import { buildDocumentSummaryDisplay } from "@/components/search/DocumentDisplay";
import { CustomCheckbox } from "@/components/CustomCheckbox";
import { updateHiddenStatus } from "../lib";
import { PopupSpec, usePopup } from "@/components/admin/connectors/Popup";
import { getErrorMsg } from "@/lib/fetchUtils";
import { ScoreSection } from "../ScoreEditor";
import { useRouter } from "next/navigation";
import { Explorer } from "./Explorer";
import { fetchValidFilterInfo } from "@/lib/search/utilsSS";
const DocumentDisplay = ({
document,
refresh,
setPopup,
}: {
document: DanswerDocument;
refresh: () => void;
setPopup: (popupSpec: PopupSpec | null) => void;
}) => {
return (
<div
key={document.document_id}
className="text-sm border-b border-gray-800 mb-3"
>
<div className="flex relative">
<a
className={
"rounded-lg flex font-bold " +
(document.link ? "" : "pointer-events-none")
}
href={document.link}
target="_blank"
rel="noopener noreferrer"
>
{getSourceIcon(document.source_type, 22)}
<p className="truncate break-all ml-2 my-auto text-base">
{document.semantic_identifier || document.document_id}
</p>
</a>
</div>
<div className="flex flex-wrap gap-x-2 mt-1 text-xs">
<div className="px-1 py-0.5 bg-gray-700 rounded flex">
<p className="mr-1 my-auto">Boost:</p>
<ScoreSection
documentId={document.document_id}
initialScore={document.boost}
setPopup={setPopup}
refresh={refresh}
consistentWidth={false}
/>
</div>
<div
onClick={async () => {
const response = await updateHiddenStatus(
document.document_id,
!document.hidden
);
if (response.ok) {
refresh();
} else {
setPopup({
type: "error",
message: `Failed to update document - ${getErrorMsg(
response
)}}`,
});
}
}}
className="px-1 py-0.5 bg-gray-700 hover:bg-gray-600 rounded flex cursor-pointer select-none"
>
<div className="my-auto">
{document.hidden ? (
<div className="text-red-500">Hidden</div>
) : (
"Visible"
)}
</div>
<div className="ml-1 my-auto">
<CustomCheckbox checked={!document.hidden} />
</div>
</div>
</div>
<p className="pl-1 pt-2 pb-3 text-gray-200 break-words">
{buildDocumentSummaryDisplay(document.match_highlights, document.blurb)}
</p>
</div>
);
};
const Main = ({
initialSearchValue,
}: {
initialSearchValue: string | undefined;
}) => {
const router = useRouter();
const { popup, setPopup } = usePopup();
const [query, setQuery] = useState(initialSearchValue || "");
const [timeoutId, setTimeoutId] = useState<number | null>(null);
const [results, setResults] = useState<DanswerDocument[]>([]);
const onSearch = async (query: string) => {
const results = await adminSearch(query);
if (results.ok) {
setResults((await results.json()).documents);
}
setTimeoutId(null);
};
useEffect(() => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
if (query && query.trim() !== "") {
router.replace(
`/admin/documents/explorer?query=${encodeURIComponent(query)}`
);
const timeoutId = window.setTimeout(() => onSearch(query), 300);
setTimeoutId(timeoutId);
} else {
setResults([]);
}
}, [query]);
return (
<div>
{popup}
<div className="flex justify-center py-2">
<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
placeholder="Find documents based on title / content..."
value={query}
onChange={(e) => {
setQuery(e.target.value);
}}
suppressContentEditableWarning={true}
/>
</div>
</div>
{results.length > 0 && (
<div className="mt-3">
{results.map((document) => {
return (
<DocumentDisplay
key={document.document_id}
document={document}
refresh={() => onSearch(query)}
setPopup={setPopup}
/>
);
})}
</div>
)}
{!query && (
<div className="flex">
<FiZap className="my-auto mr-1 text-blue-400" /> Search for a document
above to modify it&apos;s boost or hide it from searches.
</div>
)}
</div>
);
};
const Page = ({
const Page = async ({
searchParams,
}: {
searchParams: { [key: string]: string };
}) => {
const { connectors, documentSets } = await fetchValidFilterInfo();
return (
<div className="mx-auto container">
<div className="border-solid border-gray-600 border-b pb-2 mb-3 flex">
<ZoomInIcon size={32} />
<h1 className="text-3xl font-bold pl-2">Document Explorer</h1>
</div>
<AdminPageTitle
icon={<ZoomInIcon size={32} />}
title="Document Explorer"
/>
<Main initialSearchValue={searchParams.query} />
<Explorer
initialSearchValue={searchParams.query}
connectors={connectors}
documentSets={documentSets}
/>
</div>
);
};

View File

@ -0,0 +1,22 @@
import { HealthCheckBanner } from "../health/healthcheck";
import { Divider } from "@tremor/react";
export function AdminPageTitle({
icon,
title,
}: {
icon: JSX.Element;
title: string | JSX.Element;
}) {
return (
<div className="dark">
<div className="mb-4">
<HealthCheckBanner />
</div>
<h1 className="text-3xl font-bold flex gap-x-2 mb-2">
{icon} {title}
</h1>
<Divider />
</div>
);
}

View File

@ -4,6 +4,7 @@ import { getSourceIcon } from "../source";
import { useState } from "react";
import { PopupSpec } from "../admin/connectors/Popup";
import { timeAgo } from "@/lib/time";
import { DocumentUpdatedAtBadge } from "./DocumentUpdatedAtBadge";
export const buildDocumentSummaryDisplay = (
matchHighlights: string[],
@ -167,25 +168,7 @@ export const DocumentDisplay = ({
</div>
</div>
{document.updated_at && (
<div className="flex flex-wrap gap-x-2 mt-1">
<div
className={`
text-xs
text-gray-200
bg-gray-800
rounded-full
px-1
py-0.5
w-fit
my-auto
select-none
mr-2`}
>
<div className="mr-1 my-auto flex">
{"Updated " + timeAgo(document.updated_at)}
</div>
</div>
</div>
<DocumentUpdatedAtBadge updatedAt={document.updated_at} />
)}
<p className="pl-1 pt-2 pb-3 text-gray-200 break-words">
{buildDocumentSummaryDisplay(document.match_highlights, document.blurb)}

View File

@ -0,0 +1,25 @@
import { timeAgo } from "@/lib/time";
export function DocumentUpdatedAtBadge({ updatedAt }: { updatedAt: string }) {
return (
<div className="flex flex-wrap gap-x-2 mt-1">
<div
className={`
text-xs
text-gray-200
bg-gray-800
rounded-full
px-1
py-0.5
w-fit
my-auto
select-none
mr-2`}
>
<div className="mr-1 my-auto flex">
{"Updated " + timeAgo(updatedAt)}
</div>
</div>
</div>
);
}

View File

@ -3,7 +3,7 @@
import { useRef, useState } from "react";
import { SearchBar } from "./SearchBar";
import { SearchResultsDisplay } from "./SearchResultsDisplay";
import { SourceSelector } from "./Filters";
import { SourceSelector } from "./filtering/Filters";
import { Connector, DocumentSet } from "@/lib/types";
import { SearchTypeSelector } from "./SearchTypeSelector";
import {
@ -23,7 +23,7 @@ import { SearchHelper } from "./SearchHelper";
import { CancellationToken, cancellable } from "@/lib/search/cancellable";
import { NEXT_PUBLIC_DISABLE_STREAMING } from "@/lib/constants";
import { searchRequest } from "@/lib/search/qa";
import { useObjectState, useTimeRange } from "@/lib/hooks";
import { useFilters, useObjectState, useTimeRange } from "@/lib/hooks";
import { questionValidationStreamed } from "@/lib/search/streamingQuestionValidation";
const SEARCH_DEFAULT_OVERRIDES_START: SearchDefaultOverrides = {
@ -60,11 +60,7 @@ export const SearchSection: React.FC<SearchSectionProps> = ({
useObjectState<ValidQuestionResponse>(VALID_QUESTION_RESPONSE_DEFAULT);
// Filters
const [timeRange, setTimeRange] = useTimeRange();
const [sources, setSources] = useState<Source[]>([]);
const [selectedDocumentSets, setSelectedDocumentSets] = useState<string[]>(
[]
);
const filterManager = useFilters();
// Search Type
const [selectedSearchType, setSelectedSearchType] =
@ -140,9 +136,9 @@ export const SearchSection: React.FC<SearchSectionProps> = ({
: searchRequestStreamed;
const searchFnArgs = {
query,
sources,
documentSets: selectedDocumentSets,
timeRange,
sources: filterManager.selectedSources,
documentSets: filterManager.selectedDocumentSets,
timeRange: filterManager.timeRange,
updateCurrentAnswer: cancellable({
cancellationToken: lastSearchCancellationToken.current,
fn: updateCurrentAnswer,
@ -193,12 +189,7 @@ export const SearchSection: React.FC<SearchSectionProps> = ({
<div className="absolute left-0 hidden 2xl:block w-64">
{(connectors.length > 0 || documentSets.length > 0) && (
<SourceSelector
timeRange={timeRange}
setTimeRange={setTimeRange}
selectedSources={sources}
setSelectedSources={setSources}
selectedDocumentSets={selectedDocumentSets}
setSelectedDocumentSets={setSelectedDocumentSets}
{...filterManager}
availableDocumentSets={documentSets}
existingSources={connectors.map((connector) => connector.source)}
/>

View File

@ -0,0 +1,105 @@
import { FiCheck, FiChevronDown } from "react-icons/fi";
import { CustomDropdown } from "../../Dropdown";
interface Option {
key: string;
display: string | JSX.Element;
}
export function FilterDropdown({
options,
selected,
handleSelect,
icon,
defaultDisplay,
}: {
options: Option[];
selected: string[];
handleSelect: (option: Option) => void;
icon: JSX.Element;
defaultDisplay: string | JSX.Element;
}) {
return (
<div className="w-64">
<CustomDropdown
dropdown={
<div
className={`
border
border-gray-800
rounded-lg
flex
flex-col
w-64
max-h-96
overflow-y-auto
overscroll-contain`}
>
{options.map((option, ind) => {
const isSelected = selected.includes(option.key);
return (
<div
key={option.key}
className={`
flex
px-3
text-sm
text-gray-200
py-2.5
select-none
cursor-pointer
${
ind === options.length - 1
? ""
: "border-b border-gray-800"
}
${
isSelected
? "bg-dark-tremor-background-muted"
: "hover:bg-dark-tremor-background-muted "
}
`}
onClick={(event) => {
handleSelect(option);
event.preventDefault();
event.stopPropagation();
}}
>
{option.display}
{isSelected && (
<div className="ml-auto mr-1">
<FiCheck />
</div>
)}
</div>
);
})}
</div>
}
>
<div
className={`
flex
w-64
text-sm
text-gray-400
px-3
py-1.5
rounded-lg
border
border-gray-800
cursor-pointer
hover:bg-dark-tremor-background-muted`}
>
{icon}
{selected.length === 0 ? (
defaultDisplay
) : (
<p className="text-gray-200 line-clamp-1">{selected.join(", ")}</p>
)}
<FiChevronDown className="my-auto ml-auto" />
</div>
</CustomDropdown>
</div>
);
}

View File

@ -1,12 +1,13 @@
import React from "react";
import { getSourceIcon } from "../source";
import { getSourceIcon } from "../../source";
import { DocumentSet, ValidSources } from "@/lib/types";
import { Source } from "@/lib/search/interfaces";
import { InfoIcon, defaultTailwindCSS } from "../icons/icons";
import { HoverPopup } from "../HoverPopup";
import { FiFilter } from "react-icons/fi";
import { DateRangeSelector } from "./DateRangeSelector";
import { InfoIcon, defaultTailwindCSS } from "../../icons/icons";
import { HoverPopup } from "../../HoverPopup";
import { FiBook, FiBookmark, FiFilter, FiMap, FiX } from "react-icons/fi";
import { DateRangeSelector } from "../DateRangeSelector";
import { DateRangePickerValue } from "@tremor/react";
import { FilterDropdown } from "./FilterDropdown";
const sources: Source[] = [
{ displayName: "Google Drive", internalName: "google_drive" },
@ -168,3 +169,167 @@ export function SourceSelector({
</div>
);
}
function SelectedBubble({
children,
onClick,
}: {
children: string | JSX.Element;
onClick: () => void;
}) {
return (
<div
className={
"flex cursor-pointer items-center text-white border border-gray-800 " +
"py-1 my-1.5 rounded-lg px-2 w-fit bg-dark-tremor-background-muted hover:bg-gray-800"
}
onClick={onClick}
>
{children}
<FiX className="ml-2 text-gray-400" size={14} />
</div>
);
}
export function HorizontalFilters({
timeRange,
setTimeRange,
selectedSources,
setSelectedSources,
selectedDocumentSets,
setSelectedDocumentSets,
availableDocumentSets,
existingSources,
}: SourceSelectorProps) {
const handleSourceSelect = (source: Source) => {
setSelectedSources((prev: Source[]) => {
const prevSourceNames = prev.map((source) => source.internalName);
if (prevSourceNames.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 availableSources = sources.filter((source) =>
existingSources.includes(source.internalName)
);
return (
<div className="dark">
<div className="flex gap-x-3">
<div className="w-64">
<DateRangeSelector value={timeRange} onValueChange={setTimeRange} />
</div>
<FilterDropdown
options={availableSources.map((source) => {
return {
key: source.displayName,
display: (
<>
{" "}
{getSourceIcon(source.internalName, 16)}
<span className="ml-2 text-sm text-gray-200">
{source.displayName}
</span>
</>
),
};
})}
selected={selectedSources.map((source) => source.displayName)}
handleSelect={(option) =>
handleSourceSelect(
sources.find((source) => source.displayName === option.key)!
)
}
icon={
<div className="my-auto mr-2 text-gray-500 w-[16px] h-[16px]">
<FiMap size={16} />
</div>
}
defaultDisplay="All Sources"
/>
<FilterDropdown
options={availableDocumentSets.map((documentSet) => {
return {
key: documentSet.name,
display: (
<>
<div className="text-gray-500 my-auto">
<FiBookmark />
</div>
<span className="ml-2 text-sm text-gray-200">
{documentSet.name}
</span>
</>
),
};
})}
selected={selectedDocumentSets}
handleSelect={(option) => handleDocumentSetSelect(option.key)}
icon={
<div className="my-auto mr-2 text-gray-500 w-[16px] h-[16px]">
<FiBook size={16} />
</div>
}
defaultDisplay="All Document Sets"
/>
</div>
<div className="flex border-b border-gray-800 pb-4 mt-2 h-12">
<div className="flex flex-wrap gap-x-2">
{timeRange && timeRange.selectValue && (
<SelectedBubble onClick={() => setTimeRange(null)}>
<div className="text-sm flex text-gray-400">
{timeRange.selectValue}
</div>
</SelectedBubble>
)}
{existingSources.length > 0 &&
selectedSources.map((source) => (
<SelectedBubble
key={source.internalName}
onClick={() => handleSourceSelect(source)}
>
<>
{getSourceIcon(source.internalName, 16)}
<span className="ml-2 text-sm text-gray-400">
{source.displayName}
</span>
</>
</SelectedBubble>
))}
{selectedDocumentSets.length > 0 &&
selectedDocumentSets.map((documentSetName) => (
<SelectedBubble
key={documentSetName}
onClick={() => handleDocumentSetSelect(documentSetName)}
>
<>
<div className="text-gray-500">
<FiBookmark />
</div>
<span className="ml-2 text-sm text-gray-400">
{documentSetName}
</span>
</>
</SelectedBubble>
))}
</div>
</div>
</div>
);
}

View File

@ -7,6 +7,7 @@ import useSWR, { mutate, useSWRConfig } from "swr";
import { fetcher } from "./fetcher";
import { useState } from "react";
import { DateRangePickerValue } from "@tremor/react";
import { Source } from "./search/interfaces";
const CREDENTIAL_URL = "/api/manage/admin/credential";
@ -73,3 +74,20 @@ export const useConnectorCredentialIndexingStatus = (
export const useTimeRange = (initialValue?: DateRangePickerValue) => {
return useState<DateRangePickerValue | null>(null);
};
export function useFilters() {
const [timeRange, setTimeRange] = useTimeRange();
const [selectedSources, setSelectedSources] = useState<Source[]>([]);
const [selectedDocumentSets, setSelectedDocumentSets] = useState<string[]>(
[]
);
return {
timeRange,
setTimeRange,
selectedSources,
setSelectedSources,
selectedDocumentSets,
setSelectedDocumentSets,
};
}

View File

@ -59,6 +59,12 @@ export interface SearchDefaultOverrides {
offset: number;
}
export interface Filters {
source_type: string[] | null;
document_set: string[] | null;
time_cutoff: Date | null;
}
export interface SearchRequestArgs {
query: string;
sources: Source[];

View File

@ -1,11 +1,11 @@
import { Source } from "./interfaces";
import { Filters, Source } from "./interfaces";
import { DateRangePickerValue } from "@tremor/react";
export const buildFilters = (
sources: Source[],
documentSets: string[],
timeRange: DateRangePickerValue | null
) => {
): Filters => {
const filters = {
source_type:
sources.length > 0 ? sources.map((source) => source.internalName) : null,

View File

@ -0,0 +1,29 @@
import { Connector, DocumentSet } from "../types";
import { fetchSS } from "../utilsSS";
export async function fetchValidFilterInfo() {
const [connectorsResponse, documentSetResponse] = await Promise.all([
fetchSS("/manage/connector"),
fetchSS("/manage/document-set"),
]);
let connectors = [] as Connector<any>[];
if (connectorsResponse.ok) {
connectors = (await connectorsResponse.json()) as Connector<any>[];
} else {
console.log(
`Failed to fetch connectors - ${connectorsResponse.status} - ${connectorsResponse.statusText}`
);
}
let documentSets = [] as DocumentSet[];
if (documentSetResponse.ok) {
documentSets = (await documentSetResponse.json()) as DocumentSet[];
} else {
console.log(
`Failed to fetch document sets - ${documentSetResponse.status} - ${documentSetResponse.statusText}`
);
}
return { connectors, documentSets };
}