make filter icon required
This commit is contained in:
parent
ef98a5a826
commit
60cb732ce2
7 changed files with 48 additions and 61 deletions
|
|
@ -7,7 +7,6 @@ import {
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover";
|
} from "@/components/ui/popover";
|
||||||
import {
|
import {
|
||||||
File,
|
|
||||||
Book,
|
Book,
|
||||||
Scroll,
|
Scroll,
|
||||||
Library,
|
Library,
|
||||||
|
|
@ -32,13 +31,13 @@ import {
|
||||||
ShoppingCart,
|
ShoppingCart,
|
||||||
ShoppingBag,
|
ShoppingBag,
|
||||||
Check,
|
Check,
|
||||||
Plus,
|
Filter,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { filterAccentClasses } from "./knowledge-filter-panel";
|
import { filterAccentClasses } from "./knowledge-filter-panel";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
const ICON_MAP = {
|
const ICON_MAP = {
|
||||||
file: File,
|
filter: Filter,
|
||||||
book: Book,
|
book: Book,
|
||||||
scroll: Scroll,
|
scroll: Scroll,
|
||||||
library: Library,
|
library: Library,
|
||||||
|
|
@ -87,21 +86,20 @@ const COLORS = [
|
||||||
export type FilterColor = (typeof COLORS)[number];
|
export type FilterColor = (typeof COLORS)[number];
|
||||||
|
|
||||||
const colorSwatchClasses = {
|
const colorSwatchClasses = {
|
||||||
zinc: "bg-muted-foreground text-accent-foreground",
|
zinc: "bg-muted-foreground",
|
||||||
pink: "bg-accent-pink-foreground text-accent-pink",
|
pink: "bg-accent-pink-foreground",
|
||||||
purple: "bg-accent-purple-foreground text-accent-purple",
|
purple: "bg-accent-purple-foreground",
|
||||||
indigo: "bg-accent-indigo-foreground text-accent-indigo",
|
indigo: "bg-accent-indigo-foreground",
|
||||||
emerald: "bg-accent-emerald-foreground text-accent-emerald",
|
emerald: "bg-accent-emerald-foreground",
|
||||||
amber: "bg-accent-amber-foreground text-accent-amber",
|
amber: "bg-accent-amber-foreground",
|
||||||
red: "bg-accent-red-foreground text-accent-red",
|
red: "bg-accent-red-foreground",
|
||||||
"": "bg-muted-foreground text-accent-foreground",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FilterIconPopoverProps {
|
export interface FilterIconPopoverProps {
|
||||||
color: FilterColor;
|
color: FilterColor;
|
||||||
iconKey?: IconKey | undefined;
|
iconKey: IconKey;
|
||||||
onColorChange: (c: FilterColor) => void;
|
onColorChange: (c: FilterColor) => void;
|
||||||
onIconChange: (k: IconKey | undefined) => void;
|
onIconChange: (k: IconKey) => void;
|
||||||
triggerClassName?: string;
|
triggerClassName?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -118,13 +116,12 @@ export function FilterIconPopover({
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<button
|
<button
|
||||||
className={cn(
|
className={cn(
|
||||||
"h-10 w-10 min-w-10 min-h-10 rounded-sm flex items-center justify-center transition-colors",
|
"h-10 w-10 min-w-10 min-h-10 rounded-md flex items-center justify-center transition-colors",
|
||||||
filterAccentClasses[color || ""],
|
filterAccentClasses[color],
|
||||||
triggerClassName
|
triggerClassName
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{Icon && <Icon className="h-5 w-5" />}
|
{Icon && <Icon className="h-5 w-5" />}
|
||||||
{!Icon && <Plus className="h-5 w-5" />}
|
|
||||||
</button>
|
</button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-80" align="start">
|
<PopoverContent className="w-80" align="start">
|
||||||
|
|
@ -136,8 +133,8 @@ export function FilterIconPopover({
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => onColorChange(c)}
|
onClick={() => onColorChange(c)}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center h-6 w-6 rounded-sm transition-colors",
|
"flex items-center justify-center h-6 w-6 rounded-sm transition-colors text-primary",
|
||||||
colorSwatchClasses[c || ""]
|
colorSwatchClasses[c]
|
||||||
)}
|
)}
|
||||||
aria-label={c}
|
aria-label={c}
|
||||||
>
|
>
|
||||||
|
|
@ -153,18 +150,10 @@ export function FilterIconPopover({
|
||||||
<button
|
<button
|
||||||
key={k}
|
key={k}
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => {
|
onClick={() => onIconChange(k as IconKey)}
|
||||||
if (active) {
|
|
||||||
onIconChange(undefined);
|
|
||||||
} else {
|
|
||||||
onIconChange(k as IconKey);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className={
|
className={
|
||||||
"h-8 w-8 inline-flex items-center hover:text-foreground justify-center rounded border " +
|
"h-8 w-8 inline-flex items-center hover:text-foreground justify-center rounded " +
|
||||||
(active
|
(active ? "bg-muted text-primary" : "text-muted-foreground")
|
||||||
? "border-muted-foreground text-foreground"
|
|
||||||
: "border-0 text-muted-foreground")
|
|
||||||
}
|
}
|
||||||
aria-label={k}
|
aria-label={k}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Loader2, Plus, X } from "lucide-react";
|
import { Loader2, Plus } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import {
|
import {
|
||||||
useGetFiltersSearchQuery,
|
useGetFiltersSearchQuery,
|
||||||
|
|
@ -25,8 +25,8 @@ interface ParsedQueryData {
|
||||||
};
|
};
|
||||||
limit: number;
|
limit: number;
|
||||||
scoreThreshold: number;
|
scoreThreshold: number;
|
||||||
color?: FilterColor;
|
color: FilterColor;
|
||||||
icon?: IconKey;
|
icon: IconKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KnowledgeFilterListProps {
|
interface KnowledgeFilterListProps {
|
||||||
|
|
@ -106,13 +106,13 @@ export function KnowledgeFilterList({
|
||||||
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
<div className="flex flex-col gap-1 flex-1 min-w-0">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{(() => {
|
{(() => {
|
||||||
const parsed = parseQueryData(filter.query_data);
|
const parsed = parseQueryData(filter.query_data) as ParsedQueryData;
|
||||||
const Icon = iconKeyToComponent(parsed.icon);
|
const Icon = iconKeyToComponent(parsed.icon);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex items-center justify-center w-5 h-5 rounded transition-colors",
|
"flex items-center justify-center w-5 h-5 rounded transition-colors",
|
||||||
filterAccentClasses[parsed.color || ""],
|
filterAccentClasses[parsed.color],
|
||||||
parsed.color === "zinc" &&
|
parsed.color === "zinc" &&
|
||||||
"group-hover:bg-background group-[.active]:bg-background"
|
"group-hover:bg-background group-[.active]:bg-background"
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,11 @@ import { useDeleteFilter } from "@/app/api/mutations/useDeleteFilter";
|
||||||
import { useUpdateFilter } from "@/app/api/mutations/useUpdateFilter";
|
import { useUpdateFilter } from "@/app/api/mutations/useUpdateFilter";
|
||||||
import { useCreateFilter } from "@/app/api/mutations/useCreateFilter";
|
import { useCreateFilter } from "@/app/api/mutations/useCreateFilter";
|
||||||
import { useGetSearchAggregations } from "@/src/app/api/queries/useGetSearchAggregations";
|
import { useGetSearchAggregations } from "@/src/app/api/queries/useGetSearchAggregations";
|
||||||
import { FilterIconPopover, IconKey } from "@/components/filter-icon-popover";
|
import {
|
||||||
|
FilterColor,
|
||||||
|
FilterIconPopover,
|
||||||
|
IconKey,
|
||||||
|
} from "@/components/filter-icon-popover";
|
||||||
|
|
||||||
interface FacetBucket {
|
interface FacetBucket {
|
||||||
key: string;
|
key: string;
|
||||||
|
|
@ -28,18 +32,14 @@ interface AvailableFacets {
|
||||||
connector_types: FacetBucket[];
|
connector_types: FacetBucket[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const filterAccentClasses: Record<
|
export const filterAccentClasses: Record<FilterColor, string> = {
|
||||||
"zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red" | "",
|
zinc: "bg-muted text-muted-foreground",
|
||||||
string
|
|
||||||
> = {
|
|
||||||
zinc: "bg-accent text-muted-foreground",
|
|
||||||
pink: "bg-accent-pink text-accent-pink-foreground",
|
pink: "bg-accent-pink text-accent-pink-foreground",
|
||||||
purple: "bg-accent-purple text-accent-purple-foreground",
|
purple: "bg-accent-purple text-accent-purple-foreground",
|
||||||
indigo: "bg-accent-indigo text-accent-indigo-foreground",
|
indigo: "bg-accent-indigo text-accent-indigo-foreground",
|
||||||
emerald: "bg-accent-emerald text-accent-emerald-foreground",
|
emerald: "bg-accent-emerald text-accent-emerald-foreground",
|
||||||
amber: "bg-accent-amber text-accent-amber-foreground",
|
amber: "bg-accent-amber text-accent-amber-foreground",
|
||||||
red: "bg-accent-red text-accent-red-foreground",
|
red: "bg-accent-red text-accent-red-foreground",
|
||||||
"": "bg-accent text-muted-foreground",
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function KnowledgeFilterPanel() {
|
export function KnowledgeFilterPanel() {
|
||||||
|
|
@ -59,10 +59,8 @@ export function KnowledgeFilterPanel() {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [description, setDescription] = useState("");
|
const [description, setDescription] = useState("");
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [color, setColor] = useState<
|
const [color, setColor] = useState<FilterColor>("zinc");
|
||||||
"zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red"
|
const [iconKey, setIconKey] = useState<IconKey>("filter");
|
||||||
>("zinc");
|
|
||||||
const [iconKey, setIconKey] = useState<IconKey>();
|
|
||||||
|
|
||||||
// Filter configuration states (mirror search page exactly)
|
// Filter configuration states (mirror search page exactly)
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
|
@ -107,8 +105,8 @@ export function KnowledgeFilterPanel() {
|
||||||
setScoreThreshold(parsedFilterData.scoreThreshold || 0);
|
setScoreThreshold(parsedFilterData.scoreThreshold || 0);
|
||||||
setName(selectedFilter.name);
|
setName(selectedFilter.name);
|
||||||
setDescription(selectedFilter.description || "");
|
setDescription(selectedFilter.description || "");
|
||||||
setColor(parsedFilterData.color || "zinc");
|
setColor(parsedFilterData.color);
|
||||||
setIconKey(parsedFilterData.icon as IconKey);
|
setIconKey(parsedFilterData.icon);
|
||||||
}
|
}
|
||||||
}, [selectedFilter, parsedFilterData]);
|
}, [selectedFilter, parsedFilterData]);
|
||||||
|
|
||||||
|
|
@ -121,8 +119,8 @@ export function KnowledgeFilterPanel() {
|
||||||
setScoreThreshold(parsedFilterData.scoreThreshold || 0);
|
setScoreThreshold(parsedFilterData.scoreThreshold || 0);
|
||||||
setName("");
|
setName("");
|
||||||
setDescription("");
|
setDescription("");
|
||||||
setColor(parsedFilterData.color || "zinc");
|
setColor(parsedFilterData.color);
|
||||||
setIconKey(parsedFilterData.icon as IconKey);
|
setIconKey(parsedFilterData.icon);
|
||||||
}
|
}
|
||||||
}, [createMode, parsedFilterData]);
|
}, [createMode, parsedFilterData]);
|
||||||
|
|
||||||
|
|
@ -268,7 +266,7 @@ export function KnowledgeFilterPanel() {
|
||||||
color={color}
|
color={color}
|
||||||
iconKey={iconKey}
|
iconKey={iconKey}
|
||||||
onColorChange={setColor}
|
onColorChange={setColor}
|
||||||
onIconChange={(k) => setIconKey(k)}
|
onIconChange={setIconKey}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="filter-name"
|
id="filter-name"
|
||||||
|
|
|
||||||
|
|
@ -2116,7 +2116,7 @@ function ChatPage() {
|
||||||
<div className="flex items-center gap-2 px-4 pt-3 pb-1">
|
<div className="flex items-center gap-2 px-4 pt-3 pb-1">
|
||||||
<span
|
<span
|
||||||
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-colors ${
|
className={`inline-flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium transition-colors ${
|
||||||
filterAccentClasses[parsedFilterData?.color || ""]
|
filterAccentClasses[parsedFilterData?.color || "zinc"]
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
@filter:{selectedFilter.name}
|
@filter:{selectedFilter.name}
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@
|
||||||
--accent-foreground: 0 0% 0%;
|
--accent-foreground: 0 0% 0%;
|
||||||
--destructive: 0 72% 51%;
|
--destructive: 0 72% 51%;
|
||||||
--destructive-foreground: 0 0% 100%;
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
--warning: 48, 96%, 89%;
|
||||||
|
--warning-foreground: 26, 90%, 37%;
|
||||||
--border: 240 4.8% 95.9%;
|
--border: 240 4.8% 95.9%;
|
||||||
--input: 240 6% 90%;
|
--input: 240 6% 90%;
|
||||||
--ring: 0 0% 0%;
|
--ring: 0 0% 0%;
|
||||||
|
|
@ -49,10 +51,6 @@
|
||||||
--component-icon: #d8598a;
|
--component-icon: #d8598a;
|
||||||
--flow-icon: #2f67d0;
|
--flow-icon: #2f67d0;
|
||||||
|
|
||||||
/* Warning */
|
|
||||||
--warning: 48 96.6% 76.7%;
|
|
||||||
--warning-foreground: 240 6% 10%;
|
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -75,6 +73,8 @@
|
||||||
--accent-foreground: 0 0% 100%;
|
--accent-foreground: 0 0% 100%;
|
||||||
--destructive: 0 84% 60%;
|
--destructive: 0 84% 60%;
|
||||||
--destructive-foreground: 0 0% 100%;
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
--warning: 22, 78%, 26%;
|
||||||
|
--warning-foreground: 46, 97%, 65%;
|
||||||
--border: 240 3.7% 15.9%;
|
--border: 240 3.7% 15.9%;
|
||||||
--input: 240 5% 34%;
|
--input: 240 5% 34%;
|
||||||
--ring: 0 0% 100%;
|
--ring: 0 0% 100%;
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ function SearchPage() {
|
||||||
{selectedFilter?.name && (
|
{selectedFilter?.name && (
|
||||||
<div
|
<div
|
||||||
className={`flex items-center gap-1 h-full px-1.5 py-0.5 rounded max-w-[300px] ${
|
className={`flex items-center gap-1 h-full px-1.5 py-0.5 rounded max-w-[300px] ${
|
||||||
filterAccentClasses[parsedFilterData?.color || ""]
|
filterAccentClasses[parsedFilterData?.color || "zinc"]
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="truncate">{selectedFilter?.name}</span>
|
<span className="truncate">{selectedFilter?.name}</span>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { FilterColor, IconKey } from "@/components/filter-icon-popover";
|
||||||
import React, {
|
import React, {
|
||||||
createContext,
|
createContext,
|
||||||
type ReactNode,
|
type ReactNode,
|
||||||
|
|
@ -27,9 +28,8 @@ export interface ParsedQueryData {
|
||||||
};
|
};
|
||||||
limit: number;
|
limit: number;
|
||||||
scoreThreshold: number;
|
scoreThreshold: number;
|
||||||
// Optional visual metadata for UI
|
color: FilterColor;
|
||||||
color?: "zinc" | "pink" | "purple" | "indigo" | "emerald" | "amber" | "red";
|
icon: IconKey;
|
||||||
icon?: string; // lucide icon key we store in the UI mapping
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface KnowledgeFilterContextType {
|
interface KnowledgeFilterContextType {
|
||||||
|
|
@ -54,7 +54,7 @@ export function useKnowledgeFilter() {
|
||||||
const context = useContext(KnowledgeFilterContext);
|
const context = useContext(KnowledgeFilterContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"useKnowledgeFilter must be used within a KnowledgeFilterProvider",
|
"useKnowledgeFilter must be used within a KnowledgeFilterProvider"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
|
|
@ -127,7 +127,7 @@ export function KnowledgeFilterProvider({
|
||||||
limit: 10,
|
limit: 10,
|
||||||
scoreThreshold: 0,
|
scoreThreshold: 0,
|
||||||
color: "zinc",
|
color: "zinc",
|
||||||
icon: "Filter",
|
icon: "filter",
|
||||||
});
|
});
|
||||||
setIsPanelOpen(true);
|
setIsPanelOpen(true);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue