diff --git a/frontend/components/knowledge-filter-dropdown.tsx b/frontend/components/knowledge-filter-dropdown.tsx index d2c2fb27..afaf7053 100644 --- a/frontend/components/knowledge-filter-dropdown.tsx +++ b/frontend/components/knowledge-filter-dropdown.tsx @@ -4,7 +4,7 @@ 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 { Badge } from "@/components/ui/badge" + import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" import { ChevronDown, Filter, Search, X, Loader2, Plus, Save } from "lucide-react" diff --git a/frontend/components/knowledge-filter-panel.tsx b/frontend/components/knowledge-filter-panel.tsx index b0fbb8e0..9a850f73 100644 --- a/frontend/components/knowledge-filter-panel.tsx +++ b/frontend/components/knowledge-filter-panel.tsx @@ -1,7 +1,7 @@ "use client" import { useState, useEffect } from 'react' -import { Filter, X, Edit3, Save, Settings, ChevronDown, ChevronUp, RefreshCw } from 'lucide-react' +import { X, Edit3, Save, Settings, ChevronDown, ChevronUp, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import { Input } from '@/components/ui/input' @@ -11,16 +11,6 @@ import { Checkbox } from '@/components/ui/checkbox' import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible' import { useKnowledgeFilter } from '@/contexts/knowledge-filter-context' -interface ParsedQueryData { - query: string - filters: { - data_sources: string[] - document_types: string[] - owners: string[] - } - limit: number - scoreThreshold: number -} interface FacetBucket { key: string @@ -143,14 +133,7 @@ export function KnowledgeFilterPanel() { })) } - const handleFilterChange = (facetType: keyof typeof selectedFilters, value: string, checked: boolean) => { - setSelectedFilters(prev => ({ - ...prev, - [facetType]: checked - ? [...prev[facetType], value] - : prev[facetType].filter(item => item !== value) - })) - } + const selectAllFilters = () => { // Use wildcards instead of listing all specific items @@ -276,7 +259,6 @@ export function KnowledgeFilterPanel() { if (!buckets || buckets.length === 0) return null const isAllSelected = selectedFilters[facetType].includes("*") // Wildcard - const hasSpecificSelections = selectedFilters[facetType].some(item => item !== "*") const handleAllToggle = (checked: boolean) => { if (checked) { diff --git a/frontend/src/app/chat/page.tsx b/frontend/src/app/chat/page.tsx index b0d4f5c8..0fef0fca 100644 --- a/frontend/src/app/chat/page.tsx +++ b/frontend/src/app/chat/page.tsx @@ -236,12 +236,6 @@ function ChatPage() { inputRef.current?.focus() }, []) - // Update input when global filter query changes - useEffect(() => { - if (parsedFilterData?.query) { - setInput(parsedFilterData.query) - } - }, [parsedFilterData]) const handleSSEStream = async (userMessage: Message) => { const apiEndpoint = endpoint === "chat" ? "/api/chat" : "/api/langflow" @@ -252,17 +246,21 @@ function ChatPage() { stream: true, ...(parsedFilterData?.filters && (() => { const filters = parsedFilterData.filters - const processed: SelectedFilters = {} - if (!filters.data_sources.includes("*")) { - processed.data_sources = filters.data_sources + const processed: SelectedFilters = { + data_sources: [], + document_types: [], + owners: [] } - if (!filters.document_types.includes("*")) { - processed.document_types = filters.document_types - } - if (!filters.owners.includes("*")) { - processed.owners = filters.owners - } - return Object.keys(processed).length > 0 ? { filters: processed } : {} + // Only copy non-wildcard arrays + processed.data_sources = filters.data_sources.includes("*") ? [] : filters.data_sources + processed.document_types = filters.document_types.includes("*") ? [] : filters.document_types + processed.owners = filters.owners.includes("*") ? [] : filters.owners + + // Only include filters if any array has values + const hasFilters = processed.data_sources.length > 0 || + processed.document_types.length > 0 || + processed.owners.length > 0 + return hasFilters ? { filters: processed } : {} })()), limit: parsedFilterData?.limit ?? 10, scoreThreshold: parsedFilterData?.scoreThreshold ?? 0 @@ -725,17 +723,21 @@ function ChatPage() { prompt: userMessage.content, ...(parsedFilterData?.filters && (() => { const filters = parsedFilterData.filters - const processed: SelectedFilters = {} - if (!filters.data_sources.includes("*")) { - processed.data_sources = filters.data_sources + const processed: SelectedFilters = { + data_sources: [], + document_types: [], + owners: [] } - if (!filters.document_types.includes("*")) { - processed.document_types = filters.document_types - } - if (!filters.owners.includes("*")) { - processed.owners = filters.owners - } - return Object.keys(processed).length > 0 ? { filters: processed } : {} + // Only copy non-wildcard arrays + processed.data_sources = filters.data_sources.includes("*") ? [] : filters.data_sources + processed.document_types = filters.document_types.includes("*") ? [] : filters.document_types + processed.owners = filters.owners.includes("*") ? [] : filters.owners + + // Only include filters if any array has values + const hasFilters = processed.data_sources.length > 0 || + processed.document_types.length > 0 || + processed.owners.length > 0 + return hasFilters ? { filters: processed } : {} })()), limit: parsedFilterData?.limit ?? 10, scoreThreshold: parsedFilterData?.scoreThreshold ?? 0 diff --git a/frontend/src/app/knowledge-sources/page.tsx b/frontend/src/app/knowledge-sources/page.tsx index 5091a4d2..30f8ce38 100644 --- a/frontend/src/app/knowledge-sources/page.tsx +++ b/frontend/src/app/knowledge-sources/page.tsx @@ -7,7 +7,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/com import { Badge } from "@/components/ui/badge" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" -import { Upload, FolderOpen, Loader2, PlugZap, CheckCircle, XCircle, RefreshCw, Download, AlertCircle, Database } from "lucide-react" +import { Upload, FolderOpen, Loader2, PlugZap, RefreshCw, Download } from "lucide-react" import { ProtectedRoute } from "@/components/protected-route" import { useTask } from "@/contexts/task-context" import { useAuth } from "@/contexts/auth-context" @@ -42,7 +42,7 @@ interface Connection { function KnowledgeSourcesPage() { const { isAuthenticated } = useAuth() - const { addTask, refreshTasks, tasks } = useTask() + const { addTask, tasks } = useTask() const searchParams = useSearchParams() // File upload state @@ -319,8 +319,8 @@ function KnowledgeSourcesPage() { const result = await response.json() if (response.ok) { const aggs = result.aggregations || {} - const toBuckets = (agg: any): FacetBucket[] => - (agg?.buckets || []).map((b: any) => ({ key: String(b.key), count: b.doc_count })) + const toBuckets = (agg: { buckets?: Array<{ key: string | number; doc_count: number }> }): FacetBucket[] => + (agg?.buckets || []).map(b => ({ key: String(b.key), count: b.doc_count })) const dataSourceBuckets = toBuckets(aggs.data_sources) setFacetStats({ data_sources: dataSourceBuckets.slice(0, 10), diff --git a/frontend/src/app/search/page.tsx b/frontend/src/app/search/page.tsx index 67e0d7aa..865944e1 100644 --- a/frontend/src/app/search/page.tsx +++ b/frontend/src/app/search/page.tsx @@ -1,7 +1,7 @@ "use client" -import { useState, useEffect, useCallback } from "react" -import { useSearchParams } from "next/navigation" +import { useState, useEffect, useCallback, useRef } from "react" + import { Button } from "@/components/ui/button" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Input } from "@/components/ui/input" @@ -26,12 +26,13 @@ interface SearchResponse { } function SearchPage() { - const searchParams = useSearchParams() + const { selectedFilter, parsedFilterData } = useKnowledgeFilter() const [query, setQuery] = useState("") const [loading, setLoading] = useState(false) const [results, setResults] = useState([]) const [searchPerformed, setSearchPerformed] = useState(false) + const prevFilterDataRef = useRef("") const handleSearch = useCallback(async (e?: React.FormEvent) => { if (e) e.preventDefault() @@ -42,7 +43,18 @@ function SearchPage() { try { // Build search payload with global filter data - const searchPayload: any = { + interface SearchPayload { + query: string; + limit: number; + scoreThreshold: number; + filters?: { + data_sources?: string[]; + document_types?: string[]; + owners?: string[]; + }; + } + + const searchPayload: SearchPayload = { query, limit: parsedFilterData?.limit || 10, scoreThreshold: parsedFilterData?.scoreThreshold || 0 @@ -59,7 +71,7 @@ function SearchPage() { !filters.owners.includes("*") if (hasSpecificFilters) { - const processedFilters: any = {} + const processedFilters: SearchPayload['filters'] = {} // Only add filter arrays that don't contain wildcards if (!filters.data_sources.includes("*")) { @@ -114,12 +126,32 @@ function SearchPage() { } }, [parsedFilterData]) - // Auto-refresh search when filter changes (if search was already performed) + // Auto-refresh search when filter changes (but only if search was already performed) useEffect(() => { - if (searchPerformed && query.trim()) { + if (!parsedFilterData) return + + // Create a stable string representation of the filter data for comparison + const currentFilterString = JSON.stringify({ + filters: parsedFilterData.filters, + limit: parsedFilterData.limit, + scoreThreshold: parsedFilterData.scoreThreshold + }) + + // Only trigger search if filter data actually changed and we've done a search before + if (prevFilterDataRef.current !== "" && + prevFilterDataRef.current !== currentFilterString && + searchPerformed && + query.trim()) { + + console.log("Filter changed, auto-refreshing search") handleSearch() } - }, [parsedFilterData]) // Only depend on parsedFilterData to avoid infinite loop + + // Update the ref with current filter data + prevFilterDataRef.current = currentFilterString + }, [parsedFilterData, searchPerformed, query, handleSearch]) + + return (