"use client"; import { useState, useEffect, useRef } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent } from "@/components/ui/card"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { ChevronDown, Filter, Search, X, Loader2, Plus, Save, } from "lucide-react"; import { cn } from "@/lib/utils"; interface KnowledgeFilter { id: string; name: string; description: string; query_data: string; owner: string; created_at: string; updated_at: string; } interface ParsedQueryData { query: string; filters: { data_sources: string[]; document_types: string[]; owners: string[]; }; limit: number; scoreThreshold: number; } interface KnowledgeFilterDropdownProps { selectedFilter: KnowledgeFilter | null; onFilterSelect: (filter: KnowledgeFilter | null) => void; } export function KnowledgeFilterDropdown({ selectedFilter, onFilterSelect, }: KnowledgeFilterDropdownProps) { const [isOpen, setIsOpen] = useState(false); const [filters, setFilters] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(""); const [showCreateModal, setShowCreateModal] = useState(false); const [createName, setCreateName] = useState(""); const [createDescription, setCreateDescription] = useState(""); const [creating, setCreating] = useState(false); const dropdownRef = useRef(null); const loadFilters = async (query = "") => { setLoading(true); try { const response = await fetch("/api/knowledge-filter/search", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ query, limit: 20, // Limit for dropdown }), }); const result = await response.json(); if (response.ok && result.success) { setFilters(result.filters); } else { console.error("Failed to load knowledge filters:", result.error); setFilters([]); } } catch (error) { console.error("Error loading knowledge filters:", error); setFilters([]); } finally { setLoading(false); } }; const deleteFilter = async (filterId: string, e: React.MouseEvent) => { e.stopPropagation(); try { const response = await fetch(`/api/knowledge-filter/${filterId}`, { method: "DELETE", }); if (response.ok) { // Remove from local state setFilters((prev) => prev.filter((f) => f.id !== filterId)); // If this was the selected filter, clear selection if (selectedFilter?.id === filterId) { onFilterSelect(null); } } else { console.error("Failed to delete knowledge filter"); } } catch (error) { console.error("Error deleting knowledge filter:", error); } }; const handleFilterSelect = (filter: KnowledgeFilter) => { onFilterSelect(filter); setIsOpen(false); }; const handleClearFilter = () => { onFilterSelect(null); setIsOpen(false); }; const handleCreateNew = () => { setIsOpen(false); setShowCreateModal(true); }; const handleCreateFilter = async () => { if (!createName.trim()) return; setCreating(true); try { // Create a basic filter with wildcards (match everything by default) const defaultFilterData = { query: "", filters: { data_sources: ["*"], document_types: ["*"], owners: ["*"], }, limit: 10, scoreThreshold: 0, }; const response = await fetch("/api/knowledge-filter", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ name: createName.trim(), description: createDescription.trim(), queryData: JSON.stringify(defaultFilterData), }), }); const result = await response.json(); if (response.ok && result.success) { // Create the new filter object const newFilter: KnowledgeFilter = { id: result.filter.id, name: createName.trim(), description: createDescription.trim(), query_data: JSON.stringify(defaultFilterData), owner: result.filter.owner, created_at: result.filter.created_at, updated_at: result.filter.updated_at, }; // Add to local filters list setFilters((prev) => [newFilter, ...prev]); // Select the new filter onFilterSelect(newFilter); // Close modal and reset form setShowCreateModal(false); setCreateName(""); setCreateDescription(""); } else { console.error("Failed to create knowledge filter:", result.error); } } catch (error) { console.error("Error creating knowledge filter:", error); } finally { setCreating(false); } }; const handleCancelCreate = () => { setShowCreateModal(false); setCreateName(""); setCreateDescription(""); }; const getFilterSummary = (filter: KnowledgeFilter): string => { try { const parsed = JSON.parse(filter.query_data) as ParsedQueryData; const parts = []; if (parsed.query) parts.push(`"${parsed.query}"`); if (parsed.filters.data_sources.length > 0) parts.push(`${parsed.filters.data_sources.length} sources`); if (parsed.filters.document_types.length > 0) parts.push(`${parsed.filters.document_types.length} types`); if (parsed.filters.owners.length > 0) parts.push(`${parsed.filters.owners.length} owners`); return parts.join(" • ") || "No filters"; } catch { return "Invalid filter"; } }; useEffect(() => { if (isOpen) { loadFilters(); } }, [isOpen]); useEffect(() => { const timeoutId = setTimeout(() => { if (isOpen) { loadFilters(searchQuery); } }, 300); return () => clearTimeout(timeoutId); }, [searchQuery, isOpen]); // Close dropdown when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); return (
{isOpen && ( {/* Search Header */}
setSearchQuery(e.target.value)} className="pl-9 h-8 text-sm" />
{/* Filter List */}
{/* Clear filter option */}
All Knowledge
No filters applied
{loading ? (
Loading...
) : filters.length === 0 ? (
{searchQuery ? "No filters found" : "No saved filters"}
) : ( filters.map((filter) => (
handleFilterSelect(filter)} className={cn( "flex items-center gap-3 p-3 hover:bg-accent hover:text-accent-foreground cursor-pointer group transition-colors", selectedFilter?.id === filter.id && "bg-accent text-accent-foreground", )} >
{filter.name}
{getFilterSummary(filter)}
)) )}
{/* Create New Filter Option */}
Create New Filter
Save current search as filter
{/* Selected Filter Details */} {selectedFilter && (
Selected: {selectedFilter.name}
{selectedFilter.description && (
{selectedFilter.description}
)}
)}
)} {/* Create Filter Modal */} {showCreateModal && (

Create New Knowledge Filter

setCreateName(e.target.value)} className="mt-1" />