Merge pull request #665 from langflow-ai/fix/filter_search
This commit is contained in:
commit
1df76aa51e
5 changed files with 721 additions and 672 deletions
36
frontend/app/api/queries/useGetAllFiltersQuery.ts
Normal file
36
frontend/app/api/queries/useGetAllFiltersQuery.ts
Normal 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,
|
||||
);
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,165 +1,158 @@
|
|||
"use client";
|
||||
|
||||
import { Plus } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
type KnowledgeFilter,
|
||||
useGetFiltersSearchQuery,
|
||||
} from "@/app/api/queries/useGetFiltersSearchQuery";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { useGetAllFiltersQuery } from "@/app/api/queries/useGetAllFiltersQuery";
|
||||
import type { KnowledgeFilter } from "@/app/api/queries/useGetFiltersSearchQuery";
|
||||
import { useKnowledgeFilter } from "@/contexts/knowledge-filter-context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
type FilterColor,
|
||||
type IconKey,
|
||||
iconKeyToComponent,
|
||||
type FilterColor,
|
||||
type IconKey,
|
||||
iconKeyToComponent,
|
||||
} from "./filter-icon-popover";
|
||||
import { filterAccentClasses } from "./knowledge-filter-panel";
|
||||
|
||||
interface ParsedQueryData {
|
||||
query: string;
|
||||
filters: {
|
||||
data_sources: string[];
|
||||
document_types: string[];
|
||||
owners: string[];
|
||||
};
|
||||
limit: number;
|
||||
scoreThreshold: number;
|
||||
color: FilterColor;
|
||||
icon: IconKey;
|
||||
query: string;
|
||||
filters: {
|
||||
data_sources: string[];
|
||||
document_types: string[];
|
||||
owners: string[];
|
||||
};
|
||||
limit: number;
|
||||
scoreThreshold: number;
|
||||
color: FilterColor;
|
||||
icon: IconKey;
|
||||
}
|
||||
|
||||
interface KnowledgeFilterListProps {
|
||||
selectedFilter: KnowledgeFilter | null;
|
||||
onFilterSelect: (filter: KnowledgeFilter | null) => void;
|
||||
selectedFilter: KnowledgeFilter | null;
|
||||
onFilterSelect: (filter: KnowledgeFilter | null) => void;
|
||||
}
|
||||
|
||||
export function KnowledgeFilterList({
|
||||
selectedFilter,
|
||||
onFilterSelect,
|
||||
selectedFilter,
|
||||
onFilterSelect,
|
||||
}: KnowledgeFilterListProps) {
|
||||
const [searchQuery] = useState("");
|
||||
const { startCreateMode } = useKnowledgeFilter();
|
||||
const { startCreateMode } = useKnowledgeFilter();
|
||||
|
||||
const { data, isFetching: loading } = useGetFiltersSearchQuery(
|
||||
searchQuery,
|
||||
20,
|
||||
);
|
||||
const { data, isFetching: loading } = useGetAllFiltersQuery();
|
||||
|
||||
const filters = data || [];
|
||||
const filters = data || [];
|
||||
|
||||
const handleFilterSelect = (filter: KnowledgeFilter) => {
|
||||
if (filter.id === selectedFilter?.id) {
|
||||
onFilterSelect(null);
|
||||
return;
|
||||
}
|
||||
onFilterSelect(filter);
|
||||
};
|
||||
const handleFilterSelect = (filter: KnowledgeFilter) => {
|
||||
if (filter.id === selectedFilter?.id) {
|
||||
onFilterSelect(null);
|
||||
return;
|
||||
}
|
||||
onFilterSelect(filter);
|
||||
};
|
||||
|
||||
const handleCreateNew = () => {
|
||||
startCreateMode();
|
||||
};
|
||||
const handleCreateNew = () => {
|
||||
startCreateMode();
|
||||
};
|
||||
|
||||
const parseQueryData = (queryData: string): ParsedQueryData => {
|
||||
return JSON.parse(queryData) as ParsedQueryData;
|
||||
};
|
||||
const parseQueryData = (queryData: string): ParsedQueryData => {
|
||||
return JSON.parse(queryData) as ParsedQueryData;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 min-h-0 flex flex-col">
|
||||
<div className="px-3 flex-1 min-h-0 flex flex-col">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="flex items-center justify-between mb-3 mr-2 ml-4">
|
||||
<h3 className="text-xs font-medium text-muted-foreground">
|
||||
Knowledge Filters
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 hover:bg-accent rounded"
|
||||
onClick={handleCreateNew}
|
||||
title="Create New Filter"
|
||||
>
|
||||
<Plus className="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto scrollbar-hide space-y-1">
|
||||
{loading ? (
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-2">
|
||||
Loading...
|
||||
</div>
|
||||
) : filters.length === 0 ? (
|
||||
<div className="text-[13px] text-muted-foreground pb-2 pt-3 ml-4">
|
||||
{searchQuery ? "No filters found" : "No saved filters"}
|
||||
</div>
|
||||
) : (
|
||||
filters.map((filter) => (
|
||||
<div
|
||||
key={filter.id}
|
||||
onClick={() => handleFilterSelect(filter)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors",
|
||||
selectedFilter?.id === filter.id &&
|
||||
"active bg-accent text-accent-foreground",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const parsed = parseQueryData(
|
||||
filter.query_data,
|
||||
) as ParsedQueryData;
|
||||
const Icon = iconKeyToComponent(parsed.icon);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center w-5 h-5 rounded transition-colors",
|
||||
filterAccentClasses[parsed.color],
|
||||
parsed.color === "zinc" &&
|
||||
"group-hover:bg-background group-[.active]:bg-background",
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-3 w-3" />}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div className="text-sm font-medium truncate group-hover:text-accent-foreground">
|
||||
{filter.name}
|
||||
</div>
|
||||
</div>
|
||||
{filter.description && (
|
||||
<div className="text-xs text-muted-foreground line-clamp-2">
|
||||
{filter.description}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{new Date(filter.created_at).toLocaleDateString(
|
||||
undefined,
|
||||
{
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm group-hover:bg-background group-[.active]:bg-background transition-colors">
|
||||
{(() => {
|
||||
const dataSources = parseQueryData(filter.query_data)
|
||||
.filters.data_sources;
|
||||
if (dataSources[0] === "*") return "All sources";
|
||||
const count = dataSources.length;
|
||||
return `${count} ${
|
||||
count === 1 ? "source" : "sources"
|
||||
}`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Create flow moved to panel create mode */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return (
|
||||
<div className="flex-1 min-h-0 flex flex-col">
|
||||
<div className="px-3 flex-1 min-h-0 flex flex-col">
|
||||
<div className="flex-shrink-0">
|
||||
<div className="flex items-center justify-between mb-3 mr-2 ml-4">
|
||||
<h3 className="text-xs font-medium text-muted-foreground">
|
||||
Knowledge Filters
|
||||
</h3>
|
||||
<button
|
||||
type="button"
|
||||
className="p-1 hover:bg-accent rounded"
|
||||
onClick={handleCreateNew}
|
||||
title="Create New Filter"
|
||||
>
|
||||
<Plus className="h-4 w-4 text-muted-foreground" />
|
||||
</button>
|
||||
</div>
|
||||
<div className="overflow-y-auto scrollbar-hide space-y-1">
|
||||
{loading ? (
|
||||
<div className="text-[13px] text-muted-foreground p-2 ml-2">
|
||||
Loading...
|
||||
</div>
|
||||
) : filters.length === 0 ? (
|
||||
<div className="text-[13px] text-muted-foreground pb-2 pt-3 ml-4">
|
||||
No saved filters
|
||||
</div>
|
||||
) : (
|
||||
filters.map((filter) => (
|
||||
<div
|
||||
key={filter.id}
|
||||
onClick={() => handleFilterSelect(filter)}
|
||||
className={cn(
|
||||
"flex items-center gap-3 px-3 py-2 w-full rounded-lg hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors",
|
||||
selectedFilter?.id === filter.id &&
|
||||
"active bg-accent text-accent-foreground",
|
||||
)}
|
||||
>
|
||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
{(() => {
|
||||
const parsed = parseQueryData(
|
||||
filter.query_data,
|
||||
) as ParsedQueryData;
|
||||
const Icon = iconKeyToComponent(parsed.icon);
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"flex items-center justify-center w-5 h-5 rounded transition-colors",
|
||||
filterAccentClasses[parsed.color],
|
||||
parsed.color === "zinc" &&
|
||||
"group-hover:bg-background group-[.active]:bg-background",
|
||||
)}
|
||||
>
|
||||
{Icon && <Icon className="h-3 w-3" />}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
<div className="text-sm font-medium truncate group-hover:text-accent-foreground">
|
||||
{filter.name}
|
||||
</div>
|
||||
</div>
|
||||
{filter.description && (
|
||||
<div className="text-xs text-muted-foreground line-clamp-2">
|
||||
{filter.description}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="text-xs text-muted-foreground">
|
||||
{new Date(filter.created_at).toLocaleDateString(
|
||||
undefined,
|
||||
{
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "numeric",
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
<span className="text-xs bg-muted text-muted-foreground px-1 py-0.5 rounded-sm group-hover:bg-background group-[.active]:bg-background transition-colors">
|
||||
{(() => {
|
||||
const dataSources = parseQueryData(filter.query_data)
|
||||
.filters.data_sources;
|
||||
if (dataSources[0] === "*") return "All sources";
|
||||
const count = dataSources.length;
|
||||
return `${count} ${
|
||||
count === 1 ? "source" : "sources"
|
||||
}`;
|
||||
})()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{/* Create flow moved to panel create mode */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
10
frontend/package-lock.json
generated
10
frontend/package-lock.json
generated
|
|
@ -30,6 +30,7 @@
|
|||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"motion": "^12.23.12",
|
||||
"next": "15.5.7",
|
||||
|
|
@ -3429,6 +3430,15 @@
|
|||
"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": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"dotenv": "^17.2.3",
|
||||
"fuse.js": "^7.1.0",
|
||||
"lucide-react": "^0.525.0",
|
||||
"motion": "^12.23.12",
|
||||
"next": "15.5.7",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue