From 0257017d9bf8e7f334dc6194691a94e86abd31e5 Mon Sep 17 00:00:00 2001 From: Cole Goldsmith Date: Mon, 29 Sep 2025 10:28:32 -0500 Subject: [PATCH 1/4] update accent colors, add first pass at color and icon selector for filters --- frontend/components/filter-icon-popover.tsx | 181 +++++++++++++ frontend/components/knowledge-filter-list.tsx | 230 +++++++---------- .../components/knowledge-filter-panel.tsx | 241 ++++++++---------- frontend/components/ui/input.tsx | 19 +- frontend/src/app/globals.css | 60 ++--- frontend/src/app/knowledge/page.tsx | 25 +- .../src/contexts/knowledge-filter-context.tsx | 36 +++ frontend/tailwind.config.ts | 51 ++-- 8 files changed, 490 insertions(+), 353 deletions(-) create mode 100644 frontend/components/filter-icon-popover.tsx diff --git a/frontend/components/filter-icon-popover.tsx b/frontend/components/filter-icon-popover.tsx new file mode 100644 index 00000000..4bc181e7 --- /dev/null +++ b/frontend/components/filter-icon-popover.tsx @@ -0,0 +1,181 @@ +"use client"; + +import React, { type SVGProps } from "react"; +import { Button } from "@/components/ui/button"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { + Filter as FilterIcon, + Star, + Book, + FileText, + Folder, + Globe, + Calendar, + User, + Users, + Tag, + Briefcase, + Building2, + Cog, + Database, + Cpu, + Bot, + MessageSquare, + Search, + Shield, + Lock, + Key, + Link, + Mail, + Phone, + Check, +} from "lucide-react"; +import { filterAccentClasses } from "./knowledge-filter-panel"; + +const ICON_MAP = { + Filter: FilterIcon, + Star, + Book, + FileText, + Folder, + Globe, + Calendar, + User, + Users, + Tag, + Briefcase, + Building2, + Cog, + Database, + Cpu, + Bot, + MessageSquare, + Search, + Shield, + Lock, + Key, + Link, + Mail, + Phone, +} as const; + +export type IconKey = keyof typeof ICON_MAP; + +function iconKeyToComponent( + key: string +): React.ComponentType> { + return ( + (ICON_MAP as Record>>)[ + key + ] || FilterIcon + ); +} + +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", + "": "bg-muted-foreground", +}; + +export interface FilterIconPopoverProps { + color: FilterColor; + iconKey: IconKey | string; + 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) => ( + + ))} +
+
+ Icon +
+
+ {(Object.keys(ICON_MAP) as IconKey[]).map((k) => { + const OptIcon = ICON_MAP[k]; + const active = iconKey === k; + return ( + + ); + })} +
+
+
+
+ ); +} diff --git a/frontend/components/knowledge-filter-list.tsx b/frontend/components/knowledge-filter-list.tsx index 2758d17f..f277518f 100644 --- a/frontend/components/knowledge-filter-list.tsx +++ b/frontend/components/knowledge-filter-list.tsx @@ -2,24 +2,73 @@ 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 { + Filter as FilterIcon, + Loader2, + Plus, + X, + Star, + Book, + FileText, + Folder, + Globe, + Calendar, + User, + Users, + Tag, + Briefcase, + Building2, + Cog, + Database, + Cpu, + Bot, + MessageSquare, + Search, + Shield, + Lock, + Key, + Link, + Mail, + Phone, +} 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 { - Dialog, - DialogContent, - DialogDescription, - DialogHeader, - DialogTitle, -} from "@/components/ui/dialog"; +import { useKnowledgeFilter } from "@/src/contexts/knowledge-filter-context"; +import type { SVGProps } from "react"; + +const ICON_MAP = { + Filter: FilterIcon, + Star, + Book, + FileText, + Folder, + Globe, + Calendar, + User, + Users, + Tag, + Briefcase, + Building2, + Cog, + Database, + Cpu, + Bot, + MessageSquare, + Search, + Shield, + Lock, + Key, + Link, + Mail, + Phone, +} as const; + +function iconKeyToComponent(key: string): React.ComponentType> { + return (ICON_MAP as Record>>)[key] || FilterIcon; +} interface ParsedQueryData { query: string; @@ -30,6 +79,8 @@ interface ParsedQueryData { }; limit: number; scoreThreshold: number; + color?: "zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red"; + icon?: string; } interface KnowledgeFilterListProps { @@ -42,10 +93,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 +102,12 @@ export function KnowledgeFilterList({ const filters = data || []; - const createFilterMutation = useCreateFilter(); - const handleFilterSelect = (filter: KnowledgeFilter) => { 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 +116,7 @@ export function KnowledgeFilterList({ return ( <> -
+
Knowledge Filters @@ -136,7 +139,7 @@ export function KnowledgeFilterList({
) : filters.length === 0 ? ( -
+
{searchQuery ? "No filters found" : "No saved filters"}
) : ( @@ -152,9 +155,33 @@ export function KnowledgeFilterList({ >
-
- -
+ {(() => { + const parsed = parseQueryData(filter.query_data); + const color = (parsed.color || "zinc") as + | "zinc" + | "pink" + | "purple" + | "indigo" + | "emerald" + | "amber" + | "red"; + const Icon = iconKeyToComponent(parsed.icon || "Filter"); + const colorMap = { + zinc: "bg-zinc-500/20 text-zinc-500", + pink: "bg-pink-500/20 text-pink-500", + purple: "bg-purple-500/20 text-purple-500", + indigo: "bg-indigo-500/20 text-indigo-500", + emerald: "bg-emerald-500/20 text-emerald-500", + amber: "bg-amber-500/20 text-amber-500", + red: "bg-red-500/20 text-red-500", + } as const; + const colorClasses = colorMap[color]; + return ( +
+ +
+ ); + })()}
{filter.name}
@@ -200,72 +227,7 @@ export function KnowledgeFilterList({ )) )}
- {/* 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" - /> -
-
- -