Merge pull request #665 from langflow-ai/fix/filter_search

This commit is contained in:
Edwin Jose 2025-12-12 17:04:21 -05:00 committed by GitHub
commit 1df76aa51e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 721 additions and 672 deletions

View file

@ -0,0 +1,36 @@
import {
type UseQueryOptions,
useQuery,
useQueryClient,
} from "@tanstack/react-query";
import type { KnowledgeFilter } from "./useGetFiltersSearchQuery";
export const useGetAllFiltersQuery = (
options?: Omit<UseQueryOptions<KnowledgeFilter[]>, "queryKey" | "queryFn">,
) => {
const queryClient = useQueryClient();
async function getAllFilters(): Promise<KnowledgeFilter[]> {
const response = await fetch("/api/knowledge-filter/search", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "", limit: 1000 }), // Fetch all filters
});
const json = await response.json();
if (!response.ok || !json.success) {
// ensure we always return a KnowledgeFilter[] to satisfy the return type
return [];
}
return (json.filters || []) as KnowledgeFilter[];
}
return useQuery<KnowledgeFilter[]>(
{
queryKey: ["knowledge-filters", "all"],
queryFn: getAllFilters,
...options,
},
queryClient,
);
};

View file

@ -1,3 +1,4 @@
import Fuse from "fuse.js";
import { ArrowRight, Check, Funnel, Loader2, Plus } from "lucide-react"; import { ArrowRight, Check, Funnel, Loader2, Plus } from "lucide-react";
import { AnimatePresence, motion } from "motion/react"; import { AnimatePresence, motion } from "motion/react";
import { import {
@ -19,7 +20,7 @@ import {
} from "@/components/ui/popover"; } from "@/components/ui/popover";
import { useFileDrag } from "@/hooks/use-file-drag"; import { useFileDrag } from "@/hooks/use-file-drag";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { useGetFiltersSearchQuery } from "../../api/queries/useGetFiltersSearchQuery"; import { useGetAllFiltersQuery } from "../../api/queries/useGetAllFiltersQuery";
import type { KnowledgeFilterData } from "../_types/types"; import type { KnowledgeFilterData } from "../_types/types";
import { FilePreview } from "./file-preview"; import { FilePreview } from "./file-preview";
import { SelectedKnowledgeFilter } from "./selected-knowledge-filter"; import { SelectedKnowledgeFilter } from "./selected-knowledge-filter";
@ -80,19 +81,27 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
y: number; y: number;
} | null>(null); } | null>(null);
// Fetch filters using the query hook // Fetch all filters once when dropdown opens
const { data: availableFilters = [] } = useGetFiltersSearchQuery( const { data: allFilters = [] } = useGetAllFiltersQuery({
filterSearchTerm, enabled: isFilterDropdownOpen,
20, });
{ enabled: isFilterDropdownOpen },
);
// Filter available filters based on search term // Use fuse.js for fuzzy search on client side
const filteredFilters = useMemo(() => { const filteredFilters = useMemo(() => {
return availableFilters.filter((filter) => if (!filterSearchTerm) {
filter.name.toLowerCase().includes(filterSearchTerm.toLowerCase()), return allFilters.slice(0, 20); // Return first 20 when no search term
); }
}, [availableFilters, filterSearchTerm]);
const fuse = new Fuse(allFilters, {
keys: ["name", "description"],
threshold: 0.3, // 0.0 = perfect match, 1.0 = match anything
includeScore: true,
minMatchCharLength: 1,
});
const results = fuse.search(filterSearchTerm);
return results.map((result) => result.item).slice(0, 20);
}, [allFilters, filterSearchTerm]);
const { getRootProps, getInputProps } = useDropzone({ const { getRootProps, getInputProps } = useDropzone({
accept: { accept: {
@ -534,7 +543,7 @@ export const ChatInput = forwardRef<ChatInputHandle, ChatInputProps>(
Searching: @{filterSearchTerm} Searching: @{filterSearchTerm}
</div> </div>
)} )}
{availableFilters.length === 0 ? ( {allFilters.length === 0 ? (
<div className="px-2 py-3 text-sm text-muted-foreground"> <div className="px-2 py-3 text-sm text-muted-foreground">
No knowledge filters available No knowledge filters available
</div> </div>

View file

@ -1,13 +1,10 @@
"use client"; "use client";
import { Plus } from "lucide-react"; import { Plus } from "lucide-react";
import { useState } from "react"; import { useGetAllFiltersQuery } from "@/app/api/queries/useGetAllFiltersQuery";
import { import type { KnowledgeFilter } from "@/app/api/queries/useGetFiltersSearchQuery";
type KnowledgeFilter,
useGetFiltersSearchQuery,
} from "@/app/api/queries/useGetFiltersSearchQuery";
import { cn } from "@/lib/utils";
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context"; import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
import { cn } from "@/lib/utils";
import { import {
type FilterColor, type FilterColor,
type IconKey, type IconKey,
@ -37,13 +34,9 @@ export function KnowledgeFilterList({
selectedFilter, selectedFilter,
onFilterSelect, onFilterSelect,
}: KnowledgeFilterListProps) { }: KnowledgeFilterListProps) {
const [searchQuery] = useState("");
const { startCreateMode } = useKnowledgeFilter(); const { startCreateMode } = useKnowledgeFilter();
const { data, isFetching: loading } = useGetFiltersSearchQuery( const { data, isFetching: loading } = useGetAllFiltersQuery();
searchQuery,
20,
);
const filters = data || []; const filters = data || [];
@ -87,7 +80,7 @@ export function KnowledgeFilterList({
</div> </div>
) : filters.length === 0 ? ( ) : filters.length === 0 ? (
<div className="text-[13px] text-muted-foreground pb-2 pt-3 ml-4"> <div className="text-[13px] text-muted-foreground pb-2 pt-3 ml-4">
{searchQuery ? "No filters found" : "No saved filters"} No saved filters
</div> </div>
) : ( ) : (
filters.map((filter) => ( filters.map((filter) => (

View file

@ -30,6 +30,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"fuse.js": "^7.1.0",
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"motion": "^12.23.12", "motion": "^12.23.12",
"next": "15.5.7", "next": "15.5.7",
@ -3429,6 +3430,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/fuse.js": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
"integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=10"
}
},
"node_modules/get-nonce": { "node_modules/get-nonce": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",

View file

@ -34,6 +34,7 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.1.1", "cmdk": "^1.1.1",
"dotenv": "^17.2.3", "dotenv": "^17.2.3",
"fuse.js": "^7.1.0",
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"motion": "^12.23.12", "motion": "^12.23.12",
"next": "15.5.7", "next": "15.5.7",