diff --git a/frontend/components/filter-icon-popover.tsx b/frontend/components/filter-icon-popover.tsx new file mode 100644 index 00000000..c0ce416b --- /dev/null +++ b/frontend/components/filter-icon-popover.tsx @@ -0,0 +1,169 @@ +"use client"; + +import React, { type SVGProps } from "react"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Book, + Scroll, + Library, + Map, + FileImage, + Layers3, + Database, + Folder, + Archive, + MessagesSquare, + SquareStack, + Ghost, + Gem, + Swords, + Bolt, + Shield, + Hammer, + Globe, + HardDrive, + Upload, + Cable, + ShoppingCart, + ShoppingBag, + Check, + Filter, +} from "lucide-react"; +import { filterAccentClasses } from "./knowledge-filter-panel"; +import { cn } from "@/lib/utils"; + +const ICON_MAP = { + filter: Filter, + book: Book, + scroll: Scroll, + library: Library, + map: Map, + image: FileImage, + layers3: Layers3, + database: Database, + folder: Folder, + archive: Archive, + messagesSquare: MessagesSquare, + squareStack: SquareStack, + ghost: Ghost, + gem: Gem, + swords: Swords, + bolt: Bolt, + shield: Shield, + hammer: Hammer, + globe: Globe, + hardDrive: HardDrive, + upload: Upload, + cable: Cable, + shoppingCart: ShoppingCart, + shoppingBag: ShoppingBag, +} as const; + +export type IconKey = keyof typeof ICON_MAP; + +export function iconKeyToComponent( + key?: string +): React.ComponentType> | undefined { + if (!key) return undefined; + return ( + ICON_MAP as Record>> + )[key]; +} + +const COLORS = [ + "zinc", + "pink", + "purple", + "indigo", + "emerald", + "amber", + "red", +] as const; +export type FilterColor = (typeof COLORS)[number]; + +const colorSwatchClasses = { + zinc: "bg-muted-foreground", + pink: "bg-accent-pink-foreground", + purple: "bg-accent-purple-foreground", + indigo: "bg-accent-indigo-foreground", + emerald: "bg-accent-emerald-foreground", + amber: "bg-accent-amber-foreground", + red: "bg-accent-red-foreground", +}; + +export interface FilterIconPopoverProps { + color: FilterColor; + iconKey: IconKey; + onColorChange: (c: FilterColor) => void; + onIconChange: (k: IconKey) => void; + triggerClassName?: string; +} + +export function FilterIconPopover({ + color, + iconKey, + onColorChange, + onIconChange, + triggerClassName, +}: FilterIconPopoverProps) { + const Icon = iconKeyToComponent(iconKey); + return ( + + + + + +
+
+ {COLORS.map((c) => ( + + ))} +
+
+ {Object.keys(ICON_MAP).map((k: string) => { + const OptIcon = ICON_MAP[k as IconKey]; + const active = iconKey === k; + return ( + + ); + })} +
+
+
+
+ ); +} diff --git a/frontend/components/knowledge-filter-list.tsx b/frontend/components/knowledge-filter-list.tsx index 2758d17f..bac0e5ca 100644 --- a/frontend/components/knowledge-filter-list.tsx +++ b/frontend/components/knowledge-filter-list.tsx @@ -2,24 +2,19 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; - -import { Label } from "@/components/ui/label"; -import { Textarea } from "@/components/ui/textarea"; -import { Filter, Loader2, Plus, Save, X } from "lucide-react"; +import { Loader2, Plus } from "lucide-react"; import { cn } from "@/lib/utils"; import { useGetFiltersSearchQuery, type KnowledgeFilter, } from "@/src/app/api/queries/useGetFiltersSearchQuery"; -import { useCreateFilter } from "@/src/app/api/mutations/useCreateFilter"; +import { useKnowledgeFilter } from "@/src/contexts/knowledge-filter-context"; import { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; + FilterColor, + IconKey, + iconKeyToComponent, +} from "./filter-icon-popover"; +import { filterAccentClasses } from "./knowledge-filter-panel"; interface ParsedQueryData { query: string; @@ -30,6 +25,8 @@ interface ParsedQueryData { }; limit: number; scoreThreshold: number; + color: FilterColor; + icon: IconKey; } interface KnowledgeFilterListProps { @@ -42,10 +39,7 @@ export function KnowledgeFilterList({ onFilterSelect, }: KnowledgeFilterListProps) { const [searchQuery] = useState(""); - const [showCreateModal, setShowCreateModal] = useState(false); - const [createName, setCreateName] = useState(""); - const [createDescription, setCreateDescription] = useState(""); - const [creating, setCreating] = useState(false); + const { startCreateMode } = useKnowledgeFilter(); const { data, isFetching: loading } = useGetFiltersSearchQuery( searchQuery, @@ -54,57 +48,16 @@ export function KnowledgeFilterList({ const filters = data || []; - const createFilterMutation = useCreateFilter(); - const handleFilterSelect = (filter: KnowledgeFilter) => { + if (filter.id === selectedFilter?.id) { + onFilterSelect(null); + return; + } onFilterSelect(filter); }; const handleCreateNew = () => { - 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 result = await createFilterMutation.mutateAsync({ - name: createName.trim(), - description: createDescription.trim(), - queryData: JSON.stringify(defaultFilterData), - }); - - // Select the new filter from API response - onFilterSelect(result.filter); - - // Close modal and reset form - setShowCreateModal(false); - setCreateName(""); - setCreateDescription(""); - } catch (error) { - console.error("Error creating knowledge filter:", error); - } finally { - setCreating(false); - } - }; - - const handleCancelCreate = () => { - setShowCreateModal(false); - setCreateName(""); - setCreateDescription(""); + startCreateMode(); }; const parseQueryData = (queryData: string): ParsedQueryData => { @@ -113,7 +66,7 @@ export function KnowledgeFilterList({ return ( <> -
+
Knowledge Filters @@ -136,7 +89,7 @@ export function KnowledgeFilterList({
) : filters.length === 0 ? ( -
+
{searchQuery ? "No filters found" : "No saved filters"}
) : ( @@ -147,32 +100,45 @@ export function KnowledgeFilterList({ 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 && - "bg-accent text-accent-foreground" + "active bg-accent text-accent-foreground" )} >
-
- -
+ {(() => { + const parsed = parseQueryData(filter.query_data) as ParsedQueryData; + const Icon = iconKeyToComponent(parsed.icon); + return ( +
+ {Icon && } +
+ ); + })()}
{filter.name}
{filter.description && ( -
+
{filter.description}
)}
-
+
{new Date(filter.created_at).toLocaleDateString(undefined, { month: "short", day: "numeric", year: "numeric", })}
- + {(() => { const dataSources = parseQueryData(filter.query_data) .filters.data_sources; @@ -183,89 +149,11 @@ export function KnowledgeFilterList({
- {selectedFilter?.id === filter.id && ( - - )}
)) )}
- {/* Create Filter Dialog */} - - - - Create a new knowledge filter - - Save a reusable filter to quickly scope searches across your - knowledge base. - - -
-
- - setCreateName(e.target.value)} - className="mt-1" - /> -
-
- -