Compare commits

...

4 Commits

Author SHA1 Message Date
pablodanswer
afdb597dbb stashed 2024-12-11 10:20:18 -08:00
pablodanswer
b570f41bb8 update file context displays 2024-12-11 09:30:34 -08:00
pablodanswer
2781898433 improve file context displays 2024-12-11 09:30:34 -08:00
pablodanswer
0c1d6175a7 seed 2024-12-10 20:39:10 -08:00
14 changed files with 378 additions and 62 deletions

View File

@@ -475,18 +475,27 @@ def stream_chat_message_objects(
files = load_all_chat_files(
history_msgs, new_msg_req.file_descriptors, db_session
)
latest_query_files = [
file
for file in files
if file.file_id in [f["id"] for f in new_msg_req.file_descriptors]
]
file_ids = {f["id"] for f in new_msg_req.file_descriptors}
latest_query_files = [file for file in files if file.file_id in file_ids]
latest_query_file_descriptors = list(
{
file.file_id: file.to_file_descriptor() for file in latest_query_files
}.values()
)
print("fiel ids")
print(file_ids)
print(len(file_ids))
print("FILE DESCRIPTORS")
print(latest_query_file_descriptors)
print(len(latest_query_file_descriptors))
if user_message:
attach_files_to_chat_message(
chat_message=user_message,
files=[
new_file.to_file_descriptor() for new_file in latest_query_files
],
files=latest_query_file_descriptors,
db_session=db_session,
commit=False,
)

View File

@@ -39,7 +39,6 @@ from danswer.key_value_store.interface import KvKeyNotFoundError
from danswer.natural_language_processing.search_nlp_models import EmbeddingModel
from danswer.natural_language_processing.search_nlp_models import warm_up_bi_encoder
from danswer.natural_language_processing.search_nlp_models import warm_up_cross_encoder
from danswer.seeding.load_docs import seed_initial_documents
from danswer.seeding.load_yamls import load_chat_yamls
from danswer.server.manage.llm.models import LLMProviderUpsertRequest
from danswer.server.settings.store import load_settings
@@ -151,7 +150,7 @@ def setup_danswer(
# update multipass indexing setting based on GPU availability
update_default_multipass_indexing(db_session)
seed_initial_documents(db_session, tenant_id, cohere_enabled)
# seed_initial_documents(db_session, tenant_id, cohere_enabled)
def translate_saved_search_settings(db_session: Session) -> None:

1
passwords.txt Normal file
View File

@@ -0,0 +1 @@
i

View File

@@ -59,7 +59,7 @@ import { useDocumentSelection } from "./useDocumentSelection";
import { LlmOverride, useFilters, useLlmOverride } from "@/lib/hooks";
import { computeAvailableFilters } from "@/lib/filters";
import { ChatState, FeedbackType, RegenerationState } from "./types";
import { ChatFilters } from "./documentSidebar/ChatFilters";
import { ChatFilters } from "./filters/ChatFilters";
import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader";
import { FeedbackModal } from "./modal/FeedbackModal";
import { ShareChatSessionModal } from "./modal/ShareChatSessionModal";
@@ -478,7 +478,7 @@ export function ChatPage({
latestMessageId !== undefined ? latestMessageId : null
);
}
clearSelectedDocuments();
setChatSessionSharedStatus(chatSession.shared_status);
// go to bottom. If initial load, then do a scroll,
@@ -790,7 +790,23 @@ export function ChatPage({
toggleDocumentSelection,
clearSelectedDocuments,
selectedDocumentTokens,
selectedUploadedFiles,
toggleFileSelection,
] = useDocumentSelection();
console.log(
"the length of selected uploaded files",
selectedUploadedFiles.length
);
// selectedDocuments,
// toggleDocumentSelection,
// clearDocuments,
// totalTokens,
// selectedFiles,
// toggleFileSelection,
const [selectedFiles, setSelectedFiles] = useState<FileDescriptor[]>([]);
// just choose a conservative default, this will be updated in the
// background on initial load / on persona change
const [maxTokens, setMaxTokens] = useState<number>(4096);
@@ -1204,11 +1220,12 @@ export function ChatPage({
getLastSuccessfulMessageId(currMessageHistory) || systemMessage;
const stack = new CurrentMessageFIFO();
updateCurrentMessageFIFO(stack, {
signal: controller.signal, // Add this line
message: currMessage,
alternateAssistantId: currentAssistantId,
fileDescriptors: currentMessageFiles,
fileDescriptors: selectedUploadedFiles,
parentMessageId:
regenerationRequest?.parentMessage.messageId ||
lastSuccessfulMessageId,
@@ -1893,10 +1910,6 @@ export function ChatPage({
setSharedChatSession(chatSession);
};
const [documentSelection, setDocumentSelection] = useState(false);
// const toggleDocumentSelectionAspects = () => {
// setDocumentSelection((documentSelection) => !documentSelection);
// setShowDocSidebar(false);
// };
const toggleDocumentSidebar = () => {
if (!documentSidebarToggled) {
@@ -1990,10 +2003,12 @@ export function ChatPage({
/>
)}
{retrievalEnabled && documentSidebarToggled && settings?.isMobile && (
{documentSidebarToggled && settings?.isMobile && (
<div className="md:hidden">
<Modal noPadding noScroll>
<ChatFilters
toggleFileSelection={toggleFileSelection}
toggledFiles={selectedUploadedFiles}
setPresentingDocument={setPresentingDocument}
modal={true}
filterManager={filterManager}
@@ -2006,6 +2021,7 @@ export function ChatPage({
setDocumentSidebarToggled(false);
}}
selectedMessage={aiMessage}
selectedFiles={selectedFiles}
selectedDocuments={selectedDocuments}
toggleDocumentSelection={toggleDocumentSelection}
clearSelectedDocuments={clearSelectedDocuments}
@@ -2125,7 +2141,7 @@ export function ChatPage({
</div>
</div>
</div>
{!settings?.isMobile && retrievalEnabled && (
{!settings?.isMobile && (
<div
style={{ transition: "width 0.30s ease-out" }}
className={`
@@ -2133,7 +2149,6 @@ export function ChatPage({
fixed
right-0
z-[1000]
bg-background
h-screen
transition-all
@@ -2150,6 +2165,9 @@ export function ChatPage({
`}
>
<ChatFilters
toggledFiles={selectedUploadedFiles}
selectedFiles={selectedFiles}
toggleFileSelection={toggleFileSelection}
setPresentingDocument={setPresentingDocument}
modal={false}
filterManager={filterManager}
@@ -2341,6 +2359,11 @@ export function ChatPage({
const messageMap = currentMessageMap(
completeMessageDetail
);
const files = message.files.filter(
(file) =>
file.type != "document" &&
file.type != "plain_text"
);
const messageReactComponentKey = `${i}-${currentSessionId()}`;
const parentMessage = message.parentMessageId
? messageMap.get(message.parentMessageId)
@@ -2363,7 +2386,7 @@ export function ChatPage({
<HumanMessage
stopGenerating={stopGenerating}
content={message.message}
files={message.files}
files={files}
messageId={message.messageId}
onEdit={(editedContent) => {
const parentMessageId =
@@ -2446,6 +2469,7 @@ export function ChatPage({
}
>
<AIMessage
userFiles={previousMessage?.files}
setPresentingDocument={
setPresentingDocument
}
@@ -2505,6 +2529,9 @@ export function ChatPage({
setSelectedMessageForDocDisplay(
message.messageId
);
setSelectedFiles(
parentMessage?.files || []
);
}}
docs={message.documents}
currentPersona={liveAssistant}
@@ -2800,11 +2827,7 @@ export function ChatPage({
transition-all
duration-300
ease-in-out
${
documentSidebarToggled && retrievalEnabled
? "w-[400px]"
: "w-[0px]"
}
${documentSidebarToggled ? "w-[400px]" : "w-[0px]"}
`}
></div>
)}

View File

@@ -8,6 +8,8 @@ import { MetadataBadge } from "@/components/MetadataBadge";
import { WebResultIcon } from "@/components/WebResultIcon";
import { Dispatch, SetStateAction } from "react";
import { ValidSources } from "@/lib/types";
import { FileDescriptor } from "../interfaces";
import { truncateString } from "@/lib/utils";
interface DocumentDisplayProps {
closeSidebar: () => void;
@@ -128,3 +130,63 @@ export function ChatDocumentDisplay({
</div>
);
}
export function ChatFileDisplay({
closeSidebar,
file,
modal,
isSelected,
handleSelect,
tokenLimitReached,
setPresentingDocument,
}: {
closeSidebar: () => void;
file: FileDescriptor;
modal?: boolean;
isSelected: boolean;
handleSelect: (file: FileDescriptor) => void;
tokenLimitReached: boolean;
setPresentingDocument?: (document: DanswerDocument) => void;
}) {
const handleViewFile = () => {
if (setPresentingDocument) {
setPresentingDocument({
document_id: file.id,
source_type: ValidSources.File,
semantic_identifier: file.name,
} as DanswerDocument);
}
};
return (
<div className={`opacity-100 ${modal ? "w-[90vw]" : "w-full"}`}>
<div
className={`flex relative flex-col gap-0.5 rounded-xl mx-2 my-1 ${
isSelected ? "bg-gray-200" : "hover:bg-background-125"
}`}
>
<button
onClick={handleViewFile}
className="cursor-pointer text-left flex flex-col px-2 py-1.5"
>
<div className="line-clamp-1 mb-1 flex h-6 items-center gap-2 text-xs">
<SourceIcon sourceType={ValidSources.File} iconSize={18} />
<div className="line-clamp-1 text-text-900 text-sm font-semibold">
{truncateString(file.name || file.id, modal ? 30 : 40)}
</div>
</div>
<div className="line-clamp-2 text-sm font-normal leading-snug text-gray-600">
{file.type}
</div>
<div className="absolute top-2 right-2">
<DocumentSelector
isSelected={isSelected}
handleSelect={() => handleSelect(file)}
isDisabled={tokenLimitReached && !isSelected}
/>
</div>
</button>
</div>
</div>
);
}

View File

@@ -1,8 +1,8 @@
import { DanswerDocument } from "@/lib/search/interfaces";
import { ChatDocumentDisplay } from "./ChatDocumentDisplay";
import { ChatDocumentDisplay, ChatFileDisplay } from "./ChatDocumentDisplay";
import { usePopup } from "@/components/admin/connectors/Popup";
import { removeDuplicateDocs } from "@/lib/documentUtils";
import { Message } from "../interfaces";
import { FileDescriptor, Message } from "../interfaces";
import {
Dispatch,
ForwardedRef,
@@ -15,6 +15,7 @@ import { FilterManager } from "@/lib/hooks";
import { CCPairBasicInfo, DocumentSet, Tag } from "@/lib/types";
import { SourceSelector } from "../shared_chat_search/SearchFilters";
import { XIcon } from "@/components/icons/icons";
import FileSourceCard from "@/components/chat_search/sources/FileSource";
interface ChatFiltersProps {
filterManager: FilterManager;
@@ -22,6 +23,7 @@ interface ChatFiltersProps {
selectedMessage: Message | null;
selectedDocuments: DanswerDocument[] | null;
toggleDocumentSelection: (document: DanswerDocument) => void;
toggleFileSelection: (file: FileDescriptor) => void;
clearSelectedDocuments: () => void;
selectedDocumentTokens: number;
maxTokens: number;
@@ -33,6 +35,8 @@ interface ChatFiltersProps {
documentSets: DocumentSet[];
showFilters: boolean;
setPresentingDocument: Dispatch<SetStateAction<DanswerDocument | null>>;
selectedFiles: FileDescriptor[];
toggledFiles: FileDescriptor[];
}
export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
@@ -44,6 +48,7 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
selectedDocuments,
filterManager,
toggleDocumentSelection,
toggleFileSelection,
clearSelectedDocuments,
selectedDocumentTokens,
maxTokens,
@@ -52,6 +57,8 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
ccPairs,
tags,
setPresentingDocument,
selectedFiles,
toggledFiles,
documentSets,
showFilters,
},
@@ -72,8 +79,12 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
return () => clearTimeout(timer);
}, [selectedDocuments]);
const selectedDocumentIds =
selectedDocuments?.map((document) => document.document_id) || [];
const selectedDocumentIds = (
selectedDocuments?.map((document) => document.document_id) || []
).concat(toggledFiles?.map((file) => file.id) || []);
console.log("SELECTED DOCUMENT IDS", selectedDocumentIds);
console.log("toggled files", toggledFiles);
const currentDocuments = selectedMessage?.documents || null;
const dedupedDocuments = removeDuplicateDocs(currentDocuments || []);
@@ -132,38 +143,49 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
/>
) : (
<>
{dedupedDocuments.length > 0 ? (
dedupedDocuments.map((document, ind) => (
<div
key={document.document_id}
className={`${
ind === dedupedDocuments.length - 1
? ""
: "border-b border-border-light w-full"
}`}
>
<ChatDocumentDisplay
setPresentingDocument={setPresentingDocument}
{dedupedDocuments.length > 0
? dedupedDocuments.map((document, ind) => (
<div
key={document.document_id}
className={`${
ind === dedupedDocuments.length - 1
? ""
: "border-b border-border-light w-full"
}`}
>
<ChatDocumentDisplay
setPresentingDocument={setPresentingDocument}
closeSidebar={closeSidebar}
modal={modal}
document={document}
isSelected={selectedDocumentIds.includes(
document.document_id
)}
handleSelect={(documentId) => {
toggleDocumentSelection(
dedupedDocuments.find(
(doc) => doc.document_id === documentId
)!
);
}}
tokenLimitReached={tokenLimitReached}
/>
</div>
))
: selectedFiles.map((file) => (
<ChatFileDisplay
file={file}
key={file.id}
closeSidebar={closeSidebar}
modal={modal}
document={document}
isSelected={selectedDocumentIds.includes(
document.document_id
)}
handleSelect={(documentId) => {
toggleDocumentSelection(
dedupedDocuments.find(
(doc) => doc.document_id === documentId
)!
);
}}
isSelected={selectedDocumentIds.includes(file.id)}
handleSelect={(d: FileDescriptor) =>
toggleFileSelection(d)
}
tokenLimitReached={tokenLimitReached}
setPresentingDocument={setPresentingDocument}
/>
</div>
))
) : (
<div className="mx-3" />
)}
))}
</>
)}
</div>
@@ -184,7 +206,10 @@ export const ChatFilters = forwardRef<HTMLDivElement, ChatFiltersProps>(
delayedSelectedDocumentCount > 0
? delayedSelectedDocumentCount
: ""
} Source${delayedSelectedDocumentCount > 1 ? "s" : ""}`}
}
${selectedFiles ? "Document" : "Source"}${
delayedSelectedDocumentCount > 1 ? "s" : ""
}`}
</button>
</div>
)}

View File

@@ -6,6 +6,7 @@ import {
FiChevronLeft,
FiTool,
FiGlobe,
FiBook,
} from "react-icons/fi";
import { FeedbackType } from "../types";
import React, {
@@ -72,6 +73,8 @@ import CsvContent from "../../../components/tools/CSVContent";
import SourceCard, {
SeeMoreBlock,
} from "@/components/chat_search/sources/SourceCard";
import { FileSeeMoreBlock } from "@/components/chat_search/sources/FileSource";
import FileSourceCard from "@/components/chat_search/sources/FileSource";
const TOOLS_WITH_CUSTOM_HANDLING = [
SEARCH_TOOL_NAME,
@@ -167,6 +170,7 @@ export const AIMessage = ({
overriddenModel,
selectedMessageForDocDisplay,
continueGenerating,
userFiles,
shared,
isActive,
toggleDocumentSelection,
@@ -193,6 +197,7 @@ export const AIMessage = ({
setPresentingDocument,
index,
}: {
userFiles?: FileDescriptor[];
index?: number;
selectedMessageForDocDisplay?: number | null;
shared?: boolean;
@@ -391,7 +396,8 @@ export const AIMessage = ({
<div className="max-w-message-max break-words">
<div className="w-full ml-4">
<div className="max-w-message-max break-words">
{!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME ? (
{!userFiles &&
(!toolCall || toolCall.tool_name === SEARCH_TOOL_NAME) ? (
<>
{query !== undefined &&
handleShowRetrieved !== undefined &&
@@ -459,6 +465,54 @@ export const AIMessage = ({
/>
)}
{userFiles && userFiles.length > 0 && (
<div>
<div className="flex mb-auto">
<FiBook
className="my-auto flex-none mr-2"
size={14}
/>
<div className="my-auto cursor-default">
<span className="mobile:hidden">
Used context from the following files:
</span>
<span className="desktop:hidden">Files used:</span>
</div>
</div>
<div className=" -mx-8 w-full mb-4 flex relative">
<div className="w-full">
<div className="px-8 flex gap-x-2">
{!settings?.isMobile &&
userFiles.length > 0 &&
userFiles
.slice(0, 2)
.map((doc, ind) => (
<FileSourceCard
file={doc}
key={ind}
setPresentingDocument={
setPresentingDocument
}
/>
))}
<FileSeeMoreBlock
documentSelectionToggled={
(documentSelectionToggled &&
selectedMessageForDocDisplay ===
messageId) ||
false
}
toggleDocumentSelection={
toggleDocumentSelection
}
uniqueSources={uniqueSources}
/>
</div>
</div>
</div>
</div>
)}
{docs && docs.length > 0 && (
<div className="mt-2 -mx-8 w-full mb-4 flex relative">
<div className="w-full">

View File

@@ -1,5 +1,6 @@
import { DanswerDocument } from "@/lib/search/interfaces";
import { useState } from "react";
import { FileDescriptor } from "./interfaces";
interface DocumentInfo {
num_chunks: number;
@@ -22,15 +23,28 @@ export function useDocumentSelection(): [
(document: DanswerDocument) => void,
() => void,
number,
FileDescriptor[],
(file: FileDescriptor) => void,
] {
const [selectedDocuments, setSelectedDocuments] = useState<DanswerDocument[]>(
[]
);
const [totalTokens, setTotalTokens] = useState(0);
const [selectedFiles, setSelectedFiles] = useState<FileDescriptor[]>([]);
const selectedDocumentIds = selectedDocuments.map(
(document) => document.document_id
);
const documentIdToLength = new Map<string, number>();
function toggleFileSelection(file: FileDescriptor) {
const isAdding = !selectedFiles.includes(file);
console.log("is adding", isAdding);
console.log("selected files", selectedFiles);
if (!isAdding) {
setSelectedFiles(selectedFiles.filter((f) => f.id !== file.id));
} else {
setSelectedFiles([...selectedFiles, file]);
}
}
function toggleDocumentSelection(document: DanswerDocument) {
const documentId = document.document_id;
@@ -57,6 +71,7 @@ export function useDocumentSelection(): [
function clearDocuments() {
setSelectedDocuments([]);
setSelectedFiles([]);
setTotalTokens(0);
}
@@ -65,5 +80,7 @@ export function useDocumentSelection(): [
toggleDocumentSelection,
clearDocuments,
totalTokens,
selectedFiles,
toggleFileSelection,
];
}

View File

@@ -0,0 +1,33 @@
import React from "react";
interface SwitchProps {
checked: boolean;
onChange: () => void;
label: string;
}
export const Switch: React.FC<SwitchProps> = ({ checked, onChange, label }) => {
return (
<label className="flex items-center cursor-pointer">
<div className="relative">
<input
type="checkbox"
className="sr-only"
checked={checked}
onChange={onChange}
/>
<div
className={`block w-14 h-8 rounded-full ${
checked ? "bg-blue-600" : "bg-gray-600"
}`}
></div>
<div
className={`dot absolute left-1 top-1 bg-white w-6 h-6 rounded-full transition ${
checked ? "transform translate-x-6" : ""
}`}
></div>
</div>
<div className="ml-3 text-gray-700 font-medium">{label}</div>
</label>
);
};

View File

@@ -54,7 +54,9 @@ export default function TextView({
const fileId = presentingDocument.document_id.split("__")[1];
try {
const response = await fetch(
`/api/chat/file/${encodeURIComponent(fileId)}`,
`/api/chat/file/${encodeURIComponent(
fileId || presentingDocument.document_id
)}`,
{
method: "GET",
}

View File

@@ -0,0 +1,86 @@
import { WebResultIcon } from "@/components/WebResultIcon";
import { SourceIcon } from "@/components/SourceIcon";
import { DanswerDocument } from "@/lib/search/interfaces";
import { truncateString } from "@/lib/utils";
import { SetStateAction } from "react";
import { Dispatch } from "react";
import { ValidSources } from "@/lib/types";
import { FileDescriptor } from "@/app/chat/interfaces";
export default function FileSourceCard({
file,
setPresentingDocument,
}: {
file: FileDescriptor;
setPresentingDocument?: (document: DanswerDocument) => void;
}) {
return (
<div
key={file.id}
onClick={() => {
if (setPresentingDocument) {
setPresentingDocument({
document_id: file.id,
source_type: ValidSources.File,
semantic_identifier: file.name,
} as DanswerDocument);
}
}}
className="cursor-pointer text-left overflow-hidden flex flex-col gap-0.5 rounded-sm px-3 py-2.5 hover:bg-background-125 bg-background-100 w-[200px]"
>
<div className="line-clamp-1 font-semibold text-ellipsis text-text-900 flex h-6 items-center gap-2 text-sm">
<SourceIcon sourceType={ValidSources.File} iconSize={18} />
<p>{truncateString(file.name || file.id || "", 12)}</p>
</div>
<div className="line-clamp-2 text-sm font-semibold"></div>
<div className="line-clamp-2 text-sm font-normal leading-snug text-text-700">
{file.type}
</div>
</div>
);
}
interface SeeMoreBlockProps {
documentSelectionToggled: boolean;
toggleDocumentSelection?: () => void;
uniqueSources: DanswerDocument["source_type"][];
}
export function FileSeeMoreBlock({
documentSelectionToggled,
toggleDocumentSelection,
uniqueSources,
}: SeeMoreBlockProps) {
return (
<div
onClick={toggleDocumentSelection}
className={`
${documentSelectionToggled ? "border-border-100 border" : ""}
cursor-pointer w-[150px] rounded-sm flex-none transition-all duration-500 hover:bg-background-125 bg-text-100 px-3 py-2.5
`}
>
<div className="line-clamp-1 font-semibold text-ellipsis text-text-900 flex h-6 items-center justify-between text-sm">
<p className="mr-0 flex-shrink-0">
{documentSelectionToggled ? "Hide sources" : "See context"}
</p>
<div className="flex -space-x-3 flex-shrink-0 overflow-hidden">
{uniqueSources.map((sourceType, ind) => (
<div
key={ind}
className="inline-block bg-background-100 rounded-full p-0.5"
style={{ zIndex: uniqueSources.length - ind }}
>
<div className="bg-background-100 rounded-full">
<SourceIcon sourceType={sourceType} iconSize={20} />
</div>
</div>
))}
</div>
</div>
<div className="line-clamp-2 text-sm font-semibold"></div>
<div className="line-clamp-2 text-sm font-normal leading-snug text-text-700">
See more
</div>
</div>
);
}

View File

@@ -12,7 +12,7 @@ import { useContext, useEffect, useState } from "react";
import { DateRangePickerValue } from "@/app/ee/admin/performance/DateRangeSelector";
import { SourceMetadata } from "./search/interfaces";
import { destructureValue } from "./llm/utils";
import { ChatSession } from "@/app/chat/interfaces";
import { ChatSession, FileDescriptor } from "@/app/chat/interfaces";
import { UsersResponse } from "./users/interfaces";
import { Credential } from "./connectors/credentials";
import { SettingsContext } from "@/components/settings/SettingsProvider";
@@ -116,9 +116,12 @@ export interface FilterManager {
setSelectedDocumentSets: React.Dispatch<React.SetStateAction<string[]>>;
selectedTags: Tag[];
setSelectedTags: React.Dispatch<React.SetStateAction<Tag[]>>;
fileDescriptors: FileDescriptor[];
setFileDescriptors: React.Dispatch<React.SetStateAction<FileDescriptor[]>>;
}
export function useFilters(): FilterManager {
const [fileDescriptors, setFileDescriptors] = useState<FileDescriptor[]>([]);
const [timeRange, setTimeRange] = useTimeRange();
const [selectedSources, setSelectedSources] = useState<SourceMetadata[]>([]);
const [selectedDocumentSets, setSelectedDocumentSets] = useState<string[]>(
@@ -135,6 +138,8 @@ export function useFilters(): FilterManager {
setSelectedDocumentSets,
selectedTags,
setSelectedTags,
fileDescriptors,
setFileDescriptors,
};
}