Merge pull request #171 from langflow-ai/feat/filters-sweep
Feat/filters sweep
This commit is contained in:
commit
331f737c00
12 changed files with 210 additions and 222 deletions
|
|
@ -133,7 +133,7 @@ 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 text-primary",
|
"flex items-center justify-center h-6 w-6 rounded-sm transition-colors text-white",
|
||||||
colorSwatchClasses[c]
|
colorSwatchClasses[c]
|
||||||
)}
|
)}
|
||||||
aria-label={c}
|
aria-label={c}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ export function KnowledgeFilterList({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="flex flex-col gap-1 px-3 !mb-12 mt-0 h-full overflow-y-auto">
|
<div className="flex flex-col gap-2 px-3 !mb-12 mt-0 h-full overflow-y-auto">
|
||||||
<div className="flex items-center w-full justify-between pl-3">
|
<div className="flex items-center w-full justify-between pl-3">
|
||||||
<div className="text-sm font-medium text-muted-foreground">
|
<div className="text-sm font-medium text-muted-foreground">
|
||||||
Knowledge Filters
|
Knowledge Filters
|
||||||
|
|
@ -76,7 +76,7 @@ export function KnowledgeFilterList({
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleCreateNew}
|
onClick={handleCreateNew}
|
||||||
title="Create New Filter"
|
title="Create New Filter"
|
||||||
className="h-8 px-3 text-muted-foreground"
|
className="!h-8 w-8 px-0 text-muted-foreground"
|
||||||
>
|
>
|
||||||
<Plus className="h-3 w-3" />
|
<Plus className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -106,12 +106,14 @@ 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) as ParsedQueryData;
|
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 flex-shrink-0 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"
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,15 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import { X, Save, RefreshCw } from "lucide-react";
|
import { X, RefreshCw } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardFooter,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
|
@ -61,6 +67,8 @@ export function KnowledgeFilterPanel() {
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
const [color, setColor] = useState<FilterColor>("zinc");
|
const [color, setColor] = useState<FilterColor>("zinc");
|
||||||
const [iconKey, setIconKey] = useState<IconKey>("filter");
|
const [iconKey, setIconKey] = useState<IconKey>("filter");
|
||||||
|
const [nameError, setNameError] = useState<string | null>(null);
|
||||||
|
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Filter configuration states (mirror search page exactly)
|
// Filter configuration states (mirror search page exactly)
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
|
|
@ -146,27 +154,12 @@ export function KnowledgeFilterPanel() {
|
||||||
// Don't render if panel is closed or we don't have any data
|
// Don't render if panel is closed or we don't have any data
|
||||||
if (!isPanelOpen || !parsedFilterData) return null;
|
if (!isPanelOpen || !parsedFilterData) return null;
|
||||||
|
|
||||||
const selectAllFilters = () => {
|
|
||||||
// Use wildcards instead of listing all specific items
|
|
||||||
setSelectedFilters({
|
|
||||||
data_sources: ["*"],
|
|
||||||
document_types: ["*"],
|
|
||||||
owners: ["*"],
|
|
||||||
connector_types: ["*"],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearAllFilters = () => {
|
|
||||||
setSelectedFilters({
|
|
||||||
data_sources: [],
|
|
||||||
document_types: [],
|
|
||||||
owners: [],
|
|
||||||
connector_types: [],
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSaveConfiguration = async () => {
|
const handleSaveConfiguration = async () => {
|
||||||
if (!name.trim()) return;
|
if (!name.trim()) {
|
||||||
|
setNameError("Name is required");
|
||||||
|
nameInputRef.current?.focus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
const filterData = {
|
const filterData = {
|
||||||
query,
|
query,
|
||||||
filters: selectedFilters,
|
filters: selectedFilters,
|
||||||
|
|
@ -238,8 +231,8 @@ export function KnowledgeFilterPanel() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="fixed right-0 top-14 bottom-0 w-80 bg-background border-l border-border/40 z-40 overflow-y-auto">
|
<div className="fixed right-0 top-14 bottom-0 w-80 bg-background border-l z-40 overflow-y-auto">
|
||||||
<Card className="h-full rounded-none border-0 shadow-lg">
|
<Card className="h-full rounded-none border-0 shadow-lg flex flex-col">
|
||||||
<CardHeader className="pb-3">
|
<CardHeader className="pb-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<CardTitle className="text-lg flex items-center gap-2">
|
<CardTitle className="text-lg flex items-center gap-2">
|
||||||
|
|
@ -248,7 +241,10 @@ export function KnowledgeFilterPanel() {
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={closePanelOnly}
|
onClick={() => {
|
||||||
|
setSelectedFilter(null);
|
||||||
|
closePanelOnly();
|
||||||
|
}}
|
||||||
className="h-8 w-8 p-0"
|
className="h-8 w-8 p-0"
|
||||||
>
|
>
|
||||||
<X className="h-4 w-4" />
|
<X className="h-4 w-4" />
|
||||||
|
|
@ -260,7 +256,10 @@ export function KnowledgeFilterPanel() {
|
||||||
{/* Filter Name and Description */}
|
{/* Filter Name and Description */}
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="filter-name">Filter name</Label>
|
<Label htmlFor="filter-name" className="gap-1">
|
||||||
|
Filter name
|
||||||
|
<span className="text-destructive">*</span>
|
||||||
|
</Label>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<FilterIconPopover
|
<FilterIconPopover
|
||||||
color={color}
|
color={color}
|
||||||
|
|
@ -271,8 +270,17 @@ export function KnowledgeFilterPanel() {
|
||||||
<Input
|
<Input
|
||||||
id="filter-name"
|
id="filter-name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={(e) => setName(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const v = e.target.value;
|
||||||
|
setName(v);
|
||||||
|
if (nameError && v.trim()) {
|
||||||
|
setNameError(null);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
placeholder="Filter name"
|
placeholder="Filter name"
|
||||||
|
ref={nameInputRef}
|
||||||
|
aria-invalid={!!nameError}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -282,13 +290,19 @@ export function KnowledgeFilterPanel() {
|
||||||
{formatDate(selectedFilter.created_at)}
|
{formatDate(selectedFilter.created_at)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{createMode && (
|
||||||
|
<div className="space-y-2 text-xs text-right text-muted-foreground">
|
||||||
|
<span className="text-placeholder-foreground">Created</span>{" "}
|
||||||
|
{formatDate(new Date().toISOString())}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="filter-description">Description</Label>
|
<Label htmlFor="filter-description">Description</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="filter-description"
|
id="filter-description"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
placeholder="Optional description"
|
placeholder="Provide a brief description of your knowledge filter..."
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -301,17 +315,17 @@ export function KnowledgeFilterPanel() {
|
||||||
</Label>
|
</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
id="search-query"
|
id="search-query"
|
||||||
placeholder="e.g., 'financial reports from Q4'"
|
placeholder="Enter your search query..."
|
||||||
value={query}
|
value={query}
|
||||||
|
className="font-mono placeholder:font-mono"
|
||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
rows={3}
|
rows={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Filter Dropdowns */}
|
{/* Filter Dropdowns */}
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm font-medium">Data Sources</Label>
|
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={(availableFacets.data_sources || []).map((bucket) => ({
|
options={(availableFacets.data_sources || []).map((bucket) => ({
|
||||||
value: bucket.key,
|
value: bucket.key,
|
||||||
|
|
@ -322,13 +336,12 @@ export function KnowledgeFilterPanel() {
|
||||||
onValueChange={(values) =>
|
onValueChange={(values) =>
|
||||||
handleFilterChange("data_sources", values)
|
handleFilterChange("data_sources", values)
|
||||||
}
|
}
|
||||||
placeholder="Select data sources..."
|
placeholder="Select sources..."
|
||||||
allOptionLabel="All Data Sources"
|
allOptionLabel="All sources"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm font-medium">Document Types</Label>
|
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={(availableFacets.document_types || []).map(
|
options={(availableFacets.document_types || []).map(
|
||||||
(bucket) => ({
|
(bucket) => ({
|
||||||
|
|
@ -341,13 +354,12 @@ export function KnowledgeFilterPanel() {
|
||||||
onValueChange={(values) =>
|
onValueChange={(values) =>
|
||||||
handleFilterChange("document_types", values)
|
handleFilterChange("document_types", values)
|
||||||
}
|
}
|
||||||
placeholder="Select document types..."
|
placeholder="Select types..."
|
||||||
allOptionLabel="All Document Types"
|
allOptionLabel="All types"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm font-medium">Owners</Label>
|
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={(availableFacets.owners || []).map((bucket) => ({
|
options={(availableFacets.owners || []).map((bucket) => ({
|
||||||
value: bucket.key,
|
value: bucket.key,
|
||||||
|
|
@ -362,7 +374,6 @@ export function KnowledgeFilterPanel() {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-sm font-medium">Sources</Label>
|
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
options={(availableFacets.connector_types || []).map(
|
options={(availableFacets.connector_types || []).map(
|
||||||
(bucket) => ({
|
(bucket) => ({
|
||||||
|
|
@ -375,33 +386,13 @@ export function KnowledgeFilterPanel() {
|
||||||
onValueChange={(values) =>
|
onValueChange={(values) =>
|
||||||
handleFilterChange("connector_types", values)
|
handleFilterChange("connector_types", values)
|
||||||
}
|
}
|
||||||
placeholder="Select sources..."
|
placeholder="Select connectors..."
|
||||||
allOptionLabel="All Sources"
|
allOptionLabel="All connectors"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* All/None buttons */}
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={selectAllFilters}
|
|
||||||
className="h-auto px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 border-border/50"
|
|
||||||
>
|
|
||||||
All
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="sm"
|
|
||||||
onClick={clearAllFilters}
|
|
||||||
className="h-auto px-3 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 border-border/50"
|
|
||||||
>
|
|
||||||
None
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Result Limit Control - exactly like search page */}
|
{/* Result Limit Control - exactly like search page */}
|
||||||
<div className="space-y-4 pt-4 border-t border-border/50">
|
<div className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Label className="text-sm font-medium text-nowrap">
|
<Label className="text-sm font-medium text-nowrap">
|
||||||
|
|
@ -462,39 +453,45 @@ export function KnowledgeFilterPanel() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Save Configuration Button */}
|
|
||||||
<div className="flex flex-col gap-3 pt-4 border-t border-border/50">
|
|
||||||
<Button
|
|
||||||
onClick={handleSaveConfiguration}
|
|
||||||
disabled={isSaving}
|
|
||||||
className="w-full"
|
|
||||||
size="sm"
|
|
||||||
>
|
|
||||||
{isSaving ? (
|
|
||||||
<>
|
|
||||||
<RefreshCw className="h-3 w-3 mr-2 animate-spin" />
|
|
||||||
Saving...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Save className="h-3 w-3 mr-2" />
|
|
||||||
Save Configuration
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
{!createMode && (
|
|
||||||
<Button
|
|
||||||
variant="destructive"
|
|
||||||
className="w-full"
|
|
||||||
onClick={handleDeleteFilter}
|
|
||||||
>
|
|
||||||
Delete Filter
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
<CardFooter className="mt-auto align-bottom justify-end gap-2">
|
||||||
|
{/* Save Configuration Button */}
|
||||||
|
{createMode && (
|
||||||
|
<Button
|
||||||
|
onClick={closePanelOnly}
|
||||||
|
disabled={isSaving}
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{!createMode && (
|
||||||
|
<Button
|
||||||
|
variant="destructive"
|
||||||
|
size="sm"
|
||||||
|
onClick={handleDeleteFilter}
|
||||||
|
disabled={isSaving}
|
||||||
|
>
|
||||||
|
Delete Filter
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={handleSaveConfiguration}
|
||||||
|
disabled={isSaving}
|
||||||
|
size="sm"
|
||||||
|
className="relative"
|
||||||
|
>
|
||||||
|
{isSaving && (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="h-3 w-3 animate-spin" />
|
||||||
|
Saving...
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!isSaving && (createMode ? "Create Filter" : "Update Filter")}
|
||||||
|
</Button>
|
||||||
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ const buttonVariants = cva(
|
||||||
"border border-input hover:bg-muted hover:text-accent-foreground disabled:bg-muted disabled:!border-none",
|
"border border-input hover:bg-muted hover:text-accent-foreground disabled:bg-muted disabled:!border-none",
|
||||||
primary:
|
primary:
|
||||||
"border bg-background text-secondary-foreground hover:bg-muted hover:shadow-sm",
|
"border bg-background text-secondary-foreground hover:bg-muted hover:shadow-sm",
|
||||||
warning: "bg-warning text-secondary hover:bg-warning/90",
|
warning: "bg-warning text-warning-foreground hover:bg-warning/90",
|
||||||
secondary:
|
secondary:
|
||||||
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
|
"border border-muted bg-muted text-secondary-foreground hover:bg-secondary-foreground/5",
|
||||||
ghost:
|
ghost:
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,39 @@
|
||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react";
|
||||||
import { ChevronDown, Check } from "lucide-react"
|
import { ChevronDown, Check } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import {
|
||||||
Command,
|
Command,
|
||||||
CommandEmpty,
|
CommandEmpty,
|
||||||
CommandGroup,
|
CommandGroup,
|
||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
} from "@/components/ui/command"
|
} from "@/components/ui/command";
|
||||||
import {
|
import {
|
||||||
Popover,
|
Popover,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
} from "@/components/ui/popover"
|
} from "@/components/ui/popover";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
|
||||||
interface Option {
|
interface Option {
|
||||||
value: string
|
value: string;
|
||||||
label: string
|
label: string;
|
||||||
count?: number
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface MultiSelectProps {
|
interface MultiSelectProps {
|
||||||
options: Option[]
|
options: Option[];
|
||||||
value: string[]
|
value: string[];
|
||||||
onValueChange: (value: string[]) => void
|
onValueChange: (value: string[]) => void;
|
||||||
placeholder?: string
|
placeholder?: string;
|
||||||
className?: string
|
className?: string;
|
||||||
maxSelection?: number
|
maxSelection?: number;
|
||||||
searchPlaceholder?: string
|
searchPlaceholder?: string;
|
||||||
showAllOption?: boolean
|
showAllOption?: boolean;
|
||||||
allOptionLabel?: string
|
allOptionLabel?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function MultiSelect({
|
export function MultiSelect({
|
||||||
|
|
@ -43,60 +43,61 @@ export function MultiSelect({
|
||||||
placeholder = "Select items...",
|
placeholder = "Select items...",
|
||||||
className,
|
className,
|
||||||
maxSelection,
|
maxSelection,
|
||||||
searchPlaceholder = "Search...",
|
searchPlaceholder = "Search options...",
|
||||||
showAllOption = true,
|
showAllOption = true,
|
||||||
allOptionLabel = "All"
|
allOptionLabel = "All",
|
||||||
}: MultiSelectProps) {
|
}: MultiSelectProps) {
|
||||||
const [open, setOpen] = React.useState(false)
|
const [open, setOpen] = React.useState(false);
|
||||||
const [searchValue, setSearchValue] = React.useState("")
|
const [searchValue, setSearchValue] = React.useState("");
|
||||||
|
|
||||||
const isAllSelected = value.includes("*")
|
const isAllSelected = value.includes("*");
|
||||||
|
|
||||||
const filteredOptions = options.filter(option =>
|
const filteredOptions = options.filter((option) =>
|
||||||
option.label.toLowerCase().includes(searchValue.toLowerCase())
|
option.label.toLowerCase().includes(searchValue.toLowerCase())
|
||||||
)
|
);
|
||||||
|
|
||||||
const handleSelect = (optionValue: string) => {
|
const handleSelect = (optionValue: string) => {
|
||||||
if (optionValue === "*") {
|
if (optionValue === "*") {
|
||||||
// Toggle "All" selection
|
// Toggle "All" selection
|
||||||
if (isAllSelected) {
|
if (isAllSelected) {
|
||||||
onValueChange([])
|
onValueChange([]);
|
||||||
} else {
|
} else {
|
||||||
onValueChange(["*"])
|
onValueChange(["*"]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let newValue: string[]
|
let newValue: string[];
|
||||||
if (value.includes(optionValue)) {
|
if (value.includes(optionValue)) {
|
||||||
// Remove the item
|
// Remove the item
|
||||||
newValue = value.filter(v => v !== optionValue && v !== "*")
|
newValue = value.filter((v) => v !== optionValue && v !== "*");
|
||||||
} else {
|
} else {
|
||||||
// Add the item and remove "All" if present
|
// Add the item and remove "All" if present
|
||||||
newValue = [...value.filter(v => v !== "*"), optionValue]
|
newValue = [...value.filter((v) => v !== "*"), optionValue];
|
||||||
|
|
||||||
// Check max selection limit
|
// Check max selection limit
|
||||||
if (maxSelection && newValue.length > maxSelection) {
|
if (maxSelection && newValue.length > maxSelection) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onValueChange(newValue)
|
onValueChange(newValue);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const getDisplayText = () => {
|
const getDisplayText = () => {
|
||||||
if (isAllSelected) {
|
if (isAllSelected) {
|
||||||
return allOptionLabel
|
return allOptionLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.length === 0) {
|
if (value.length === 0) {
|
||||||
return placeholder
|
return placeholder;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the noun from placeholder (e.g., "Select data sources..." -> "data sources")
|
// Extract the noun from placeholder (e.g., "Select data sources..." -> "data sources")
|
||||||
const noun = placeholder.toLowerCase().replace('select ', '').replace('...', '')
|
const noun = placeholder
|
||||||
return `${value.length} ${noun}`
|
.toLowerCase()
|
||||||
}
|
.replace("select ", "")
|
||||||
|
.replace("...", "");
|
||||||
|
return `${value.length} ${noun}`;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
|
|
@ -106,17 +107,15 @@ export function MultiSelect({
|
||||||
role="combobox"
|
role="combobox"
|
||||||
aria-expanded={open}
|
aria-expanded={open}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full justify-between min-h-[40px] h-auto text-left",
|
"w-full justify-between h-8 py-0 text-left",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="text-foreground text-sm">
|
<span className="text-foreground text-sm">{getDisplayText()}</span>
|
||||||
{getDisplayText()}
|
|
||||||
</span>
|
|
||||||
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
<ChevronDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-full p-0" align="start">
|
<PopoverContent className="p-0" align="start">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput
|
<CommandInput
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
|
|
@ -132,16 +131,13 @@ export function MultiSelect({
|
||||||
onSelect={() => handleSelect("*")}
|
onSelect={() => handleSelect("*")}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
>
|
>
|
||||||
|
<span className="flex-1">{allOptionLabel}</span>
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"mr-2 h-4 w-4",
|
"mr-2 h-4 w-4",
|
||||||
isAllSelected ? "opacity-100" : "opacity-0"
|
isAllSelected ? "opacity-100" : "opacity-0"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<span className="flex-1">{allOptionLabel}</span>
|
|
||||||
<span className="text-xs text-blue-500 bg-blue-500/10 px-1.5 py-0.5 rounded ml-2">
|
|
||||||
*
|
|
||||||
</span>
|
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
)}
|
)}
|
||||||
{filteredOptions.map((option) => (
|
{filteredOptions.map((option) => (
|
||||||
|
|
@ -149,20 +145,19 @@ export function MultiSelect({
|
||||||
key={option.value}
|
key={option.value}
|
||||||
onSelect={() => handleSelect(option.value)}
|
onSelect={() => handleSelect(option.value)}
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
disabled={isAllSelected}
|
|
||||||
>
|
>
|
||||||
<Check
|
|
||||||
className={cn(
|
|
||||||
"mr-2 h-4 w-4",
|
|
||||||
value.includes(option.value) ? "opacity-100" : "opacity-0"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<span className="flex-1">{option.label}</span>
|
<span className="flex-1">{option.label}</span>
|
||||||
{option.count !== undefined && (
|
{option.count !== undefined && (
|
||||||
<span className="text-xs text-muted-foreground bg-muted/50 px-1.5 py-0.5 rounded ml-2">
|
<span className="text-xs text-muted-foreground bg-muted/50 px-1.5 py-0.5 rounded ml-2">
|
||||||
{option.count}
|
{option.count}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
<Check
|
||||||
|
className={cn(
|
||||||
|
"mr-2 h-4 w-4",
|
||||||
|
value.includes(option.value) ? "opacity-100" : "opacity-0"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
))}
|
))}
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
@ -170,5 +165,5 @@ export function MultiSelect({
|
||||||
</Command>
|
</Command>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,10 @@ const Slider = React.forwardRef<
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<SliderPrimitive.Track className="relative h-2 w-full grow overflow-hidden rounded-full bg-muted/40">
|
<SliderPrimitive.Track className="relative h-[5px] w-full grow overflow-hidden rounded-full bg-muted">
|
||||||
<SliderPrimitive.Range className="absolute h-full bg-primary" />
|
<SliderPrimitive.Range className="absolute h-full bg-muted-foreground" />
|
||||||
</SliderPrimitive.Track>
|
</SliderPrimitive.Track>
|
||||||
<SliderPrimitive.Thumb className="block h-5 w-5 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
<SliderPrimitive.Thumb className="block h-4 w-4 rounded-full border-2 border-primary bg-background ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50" />
|
||||||
</SliderPrimitive.Root>
|
</SliderPrimitive.Root>
|
||||||
))
|
))
|
||||||
Slider.displayName = SliderPrimitive.Root.displayName
|
Slider.displayName = SliderPrimitive.Root.displayName
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
|
||||||
<textarea
|
<textarea
|
||||||
data-slot="textarea"
|
data-slot="textarea"
|
||||||
className={cn(
|
className={cn(
|
||||||
"primary-input placeholder:font-mono placeholder:text-placeholder-foreground min-h-fit",
|
"primary-input placeholder:text-placeholder-foreground min-h-fit",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|
|
||||||
9
frontend/package-lock.json
generated
9
frontend/package-lock.json
generated
|
|
@ -28,7 +28,6 @@
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tanstack/react-query": "^5.86.0",
|
"@tanstack/react-query": "^5.86.0",
|
||||||
"ag-grid-community": "^34.2.0",
|
"ag-grid-community": "^34.2.0",
|
||||||
|
|
@ -2318,14 +2317,6 @@
|
||||||
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
|
"tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20 || >= 4.0.0-beta.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/line-clamp": {
|
|
||||||
"version": "0.4.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/line-clamp/-/line-clamp-0.4.4.tgz",
|
|
||||||
"integrity": "sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==",
|
|
||||||
"peerDependencies": {
|
|
||||||
"tailwindcss": ">=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@tailwindcss/typography": {
|
"node_modules/@tailwindcss/typography": {
|
||||||
"version": "0.5.16",
|
"version": "0.5.16",
|
||||||
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
"resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,6 @@
|
||||||
"@radix-ui/react-tabs": "^1.1.13",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-tooltip": "^1.2.8",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tailwindcss/forms": "^0.5.10",
|
"@tailwindcss/forms": "^0.5.10",
|
||||||
"@tailwindcss/line-clamp": "^0.4.4",
|
|
||||||
"@tailwindcss/typography": "^0.5.16",
|
"@tailwindcss/typography": "^0.5.16",
|
||||||
"@tanstack/react-query": "^5.86.0",
|
"@tanstack/react-query": "^5.86.0",
|
||||||
"ag-grid-community": "^34.2.0",
|
"ag-grid-community": "^34.2.0",
|
||||||
|
|
|
||||||
|
|
@ -25,27 +25,28 @@
|
||||||
--muted-foreground: 240 4% 46%;
|
--muted-foreground: 240 4% 46%;
|
||||||
--accent: 240 5% 96%;
|
--accent: 240 5% 96%;
|
||||||
--accent-foreground: 0 0% 0%;
|
--accent-foreground: 0 0% 0%;
|
||||||
--destructive: 0 72% 51%;
|
|
||||||
--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%;
|
||||||
--placeholder-foreground: 240 5% 65%;
|
--placeholder-foreground: 240 5% 65%;
|
||||||
|
|
||||||
--accent-amber: 48, 96%, 89%; /* amber-100 #fef3c7 */
|
--accent-amber: 48 96% 89%; /* amber-100 #fef3c7 */
|
||||||
--accent-amber-foreground: 26, 90%, 37%; /* amber-700 #b45309 */
|
--accent-amber-foreground: 26 90% 37%; /* amber-700 #b45309 */
|
||||||
--accent-emerald: 149, 80%, 90%; /* emerald-100 #d1fae5 */
|
--accent-emerald: 149 80% 90%; /* emerald-100 #d1fae5 */
|
||||||
--accent-emerald-foreground: 161, 94%, 30%; /* emerald-600 #059669 */
|
--accent-emerald-foreground: 161 94% 30%; /* emerald-600 #059669 */
|
||||||
--accent-red: 0, 93%, 94%; /* red-100 #fee2e2 */
|
--accent-red: 0 93% 94%; /* red-100 #fee2e2 */
|
||||||
--accent-red-foreground: 0, 72%, 51%; /* red-600 #dc2626 */
|
--accent-red-foreground: 0 72% 51%; /* red-600 #dc2626 */
|
||||||
--accent-indigo: 226, 100%, 94%; /* indigo-100 #e0e7ff */
|
--accent-indigo: 226 100% 94%; /* indigo-100 #e0e7ff */
|
||||||
--accent-indigo-foreground: 243, 75%, 59%; /* indigo-600 #4f46e5 */
|
--accent-indigo-foreground: 243 75% 59%; /* indigo-600 #4f46e5 */
|
||||||
--accent-pink: 326, 78%, 95%; /* pink-100 #fce7f3 */
|
--accent-pink: 326 78% 95%; /* pink-100 #fce7f3 */
|
||||||
--accent-pink-foreground: 333, 71%, 51%; /* pink-600 #db2777 */
|
--accent-pink-foreground: 333 71% 51%; /* pink-600 #db2777 */
|
||||||
--accent-purple: 269, 100%, 95%; /* purple-100 #f3e8ff */
|
--accent-purple: 269 100% 95%; /* purple-100 #f3e8ff */
|
||||||
--accent-purple-foreground: 271, 81%, 56%; /* purple-600 #7c3aed */
|
--accent-purple-foreground: 271 81% 56%; /* purple-600 #7c3aed */
|
||||||
|
|
||||||
|
--destructive: 0 72% 51%; /* red-600 #dc2626 */
|
||||||
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
--warning: 26 90% 37%; /* amber-700 #b45309 */
|
||||||
|
--warning-foreground: 0 0% 100%;
|
||||||
|
|
||||||
/* Component Colors */
|
/* Component Colors */
|
||||||
--component-icon: #d8598a;
|
--component-icon: #d8598a;
|
||||||
|
|
@ -71,27 +72,28 @@
|
||||||
--muted-foreground: 240 5% 65%;
|
--muted-foreground: 240 5% 65%;
|
||||||
--accent: 240 4% 16%;
|
--accent: 240 4% 16%;
|
||||||
--accent-foreground: 0 0% 100%;
|
--accent-foreground: 0 0% 100%;
|
||||||
--destructive: 0 84% 60%;
|
|
||||||
--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%;
|
||||||
--placeholder-foreground: 240 4% 46%;
|
--placeholder-foreground: 240 4% 46%;
|
||||||
|
|
||||||
--accent-amber: 22, 78%, 26%; /* amber-900 #78350f */
|
--accent-amber: 22 78% 26%; /* amber-900 #78350f */
|
||||||
--accent-amber-foreground: 46, 97%, 65%; /* amber-300 #fcd34d */
|
--accent-amber-foreground: 46 97% 65%; /* amber-300 #fcd34d */
|
||||||
--accent-emerald: 164, 86%, 16%; /* emerald-900 #064e3b */
|
--accent-emerald: 164 86% 16%; /* emerald-900 #064e3b */
|
||||||
--accent-emerald-foreground: 158, 64%, 52%; /* emerald-400 #34d399 */
|
--accent-emerald-foreground: 158 64% 52%; /* emerald-400 #34d399 */
|
||||||
--accent-red: 0, 63%, 31%; /* red-900 #7f1d1d */
|
--accent-red: 0 63% 31%; /* red-900 #7f1d1d */
|
||||||
--accent-red-foreground: 0, 91%, 71%; /* red-400 #f87171 */
|
--accent-red-foreground: 0 91% 71%; /* red-400 #f87171 */
|
||||||
--accent-indigo: 242, 47%, 34%; /* indigo-900 #312e81 */
|
--accent-indigo: 242 47% 34%; /* indigo-900 #312e81 */
|
||||||
--accent-indigo-foreground: 234, 89%, 74%; /* indigo-400 #818cf8 */
|
--accent-indigo-foreground: 234 89% 74%; /* indigo-400 #818cf8 */
|
||||||
--accent-pink: 336, 69%, 30%; /* pink-900 #831843 */
|
--accent-pink: 336 69% 30%; /* pink-900 #831843 */
|
||||||
--accent-pink-foreground: 329, 86%, 70%; /* pink-400 #f472b6 */
|
--accent-pink-foreground: 329 86% 70%; /* pink-400 #f472b6 */
|
||||||
--accent-purple: 274, 66%, 32%; /* purple-900 #4c1d95 */
|
--accent-purple: 274 66% 32%; /* purple-900 #4c1d95 */
|
||||||
--accent-purple-foreground: 270, 95%, 75%; /* purple-400 #a78bfa */
|
--accent-purple-foreground: 270 95% 75%; /* purple-400 #a78bfa */
|
||||||
|
|
||||||
|
--destructive: 0 84% 60%; /* red-500 #ef4444 */
|
||||||
|
--destructive-foreground: 0 0% 100%;
|
||||||
|
--warning: 46 97% 65%; /* amber-300 #fcd34d */
|
||||||
|
--warning-foreground: 0 0% 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ function SearchPage() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convert TaskFiles to File format and merge with backend results
|
// Convert TaskFiles to File format and merge with backend results
|
||||||
const taskFilesAsFiles: File[] = taskFiles.map(taskFile => {
|
const taskFilesAsFiles: File[] = taskFiles.map((taskFile) => {
|
||||||
return {
|
return {
|
||||||
filename: taskFile.filename,
|
filename: taskFile.filename,
|
||||||
mimetype: taskFile.mimetype,
|
mimetype: taskFile.mimetype,
|
||||||
|
|
@ -77,11 +77,11 @@ function SearchPage() {
|
||||||
|
|
||||||
const backendFiles = data as File[];
|
const backendFiles = data as File[];
|
||||||
|
|
||||||
const filteredTaskFiles = taskFilesAsFiles.filter(taskFile => {
|
const filteredTaskFiles = taskFilesAsFiles.filter((taskFile) => {
|
||||||
return (
|
return (
|
||||||
taskFile.status !== "active" &&
|
taskFile.status !== "active" &&
|
||||||
!backendFiles.some(
|
!backendFiles.some(
|
||||||
backendFile => backendFile.filename === taskFile.filename
|
(backendFile) => backendFile.filename === taskFile.filename
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
@ -123,7 +123,7 @@ function SearchPage() {
|
||||||
{
|
{
|
||||||
field: "size",
|
field: "size",
|
||||||
headerName: "Size",
|
headerName: "Size",
|
||||||
valueFormatter: params =>
|
valueFormatter: (params) =>
|
||||||
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
|
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -133,13 +133,13 @@ function SearchPage() {
|
||||||
{
|
{
|
||||||
field: "owner",
|
field: "owner",
|
||||||
headerName: "Owner",
|
headerName: "Owner",
|
||||||
valueFormatter: params =>
|
valueFormatter: (params) =>
|
||||||
params.data?.owner_name || params.data?.owner_email || "—",
|
params.data?.owner_name || params.data?.owner_email || "—",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "chunkCount",
|
field: "chunkCount",
|
||||||
headerName: "Chunks",
|
headerName: "Chunks",
|
||||||
valueFormatter: params => params.data?.chunkCount?.toString() || "-",
|
valueFormatter: (params) => params.data?.chunkCount?.toString() || "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "avgScore",
|
field: "avgScore",
|
||||||
|
|
@ -201,7 +201,7 @@ function SearchPage() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete each file individually since the API expects one filename at a time
|
// Delete each file individually since the API expects one filename at a time
|
||||||
const deletePromises = selectedRows.map(row =>
|
const deletePromises = selectedRows.map((row) =>
|
||||||
deleteDocumentMutation.mutateAsync({ filename: row.filename })
|
deleteDocumentMutation.mutateAsync({ filename: row.filename })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -255,7 +255,7 @@ function SearchPage() {
|
||||||
<div className="primary-input min-h-10 !flex items-center flex-nowrap focus-within:border-foreground transition-colors !p-[0.3rem]">
|
<div className="primary-input min-h-10 !flex items-center flex-nowrap focus-within:border-foreground transition-colors !p-[0.3rem]">
|
||||||
{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 mr-1 rounded max-w-[25%] ${
|
||||||
filterAccentClasses[parsedFilterData?.color || "zinc"]
|
filterAccentClasses[parsedFilterData?.color || "zinc"]
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
|
@ -267,8 +267,12 @@ function SearchPage() {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<Search
|
||||||
|
className="h-4 w-4 ml-1 flex-shrink-0 text-placeholder-foreground"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
/>
|
||||||
<input
|
<input
|
||||||
className="bg-transparent w-full h-full ml-2 focus:outline-none focus-visible:outline-none placeholder:font-mono"
|
className="bg-transparent w-full h-full ml-2 focus:outline-none focus-visible:outline-none font-mono placeholder:font-mono"
|
||||||
name="search-query"
|
name="search-query"
|
||||||
id="search-query"
|
id="search-query"
|
||||||
type="text"
|
type="text"
|
||||||
|
|
@ -318,7 +322,7 @@ function SearchPage() {
|
||||||
rowSelection="multiple"
|
rowSelection="multiple"
|
||||||
rowMultiSelectWithClick={false}
|
rowMultiSelectWithClick={false}
|
||||||
suppressRowClickSelection={true}
|
suppressRowClickSelection={true}
|
||||||
getRowId={params => params.data.filename}
|
getRowId={(params) => params.data.filename}
|
||||||
domLayout="normal"
|
domLayout="normal"
|
||||||
onSelectionChanged={onSelectionChanged}
|
onSelectionChanged={onSelectionChanged}
|
||||||
noRowsOverlayComponent={() => (
|
noRowsOverlayComponent={() => (
|
||||||
|
|
@ -346,7 +350,7 @@ function SearchPage() {
|
||||||
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
||||||
|
|
||||||
Documents to be deleted:
|
Documents to be deleted:
|
||||||
${selectedRows.map(row => `• ${row.filename}`).join("\n")}`}
|
${selectedRows.map((row) => `• ${row.filename}`).join("\n")}`}
|
||||||
confirmText="Delete All"
|
confirmText="Delete All"
|
||||||
onConfirm={handleBulkDelete}
|
onConfirm={handleBulkDelete}
|
||||||
isLoading={deleteDocumentMutation.isPending}
|
isLoading={deleteDocumentMutation.isPending}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import tailwindcssTypography from "@tailwindcss/typography";
|
||||||
import { fontFamily } from "tailwindcss/defaultTheme";
|
import { fontFamily } from "tailwindcss/defaultTheme";
|
||||||
import plugin from "tailwindcss/plugin";
|
import plugin from "tailwindcss/plugin";
|
||||||
import tailwindcssAnimate from "tailwindcss-animate";
|
import tailwindcssAnimate from "tailwindcss-animate";
|
||||||
import tailwindcssLineClamp from "@tailwindcss/line-clamp";
|
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
darkMode: ["class"],
|
darkMode: ["class"],
|
||||||
|
|
@ -167,7 +166,6 @@ const config = {
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
tailwindcssAnimate,
|
tailwindcssAnimate,
|
||||||
tailwindcssLineClamp,
|
|
||||||
tailwindcssForms({
|
tailwindcssForms({
|
||||||
strategy: "class",
|
strategy: "class",
|
||||||
}),
|
}),
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue