Knowledge Base Search Component¶
Overview¶
The KnowledgeBaseSearch component provides an advanced search interface for the company knowledge base. It enables users to quickly find relevant documents, templates, and knowledge resources using natural language queries and advanced filtering options.
Features¶
- Natural Language Search: Supports conversational queries for intuitive searching
- Semantic Search: Finds conceptually related content beyond keyword matching
- Filtering Options: Filter by document type, date, author, and tags
- Instant Results: Real-time results as users type
- Result Previews: Document previews without opening files
- Search History: Tracks and suggests previous searches
- Relevance Ranking: Orders results by relevance to query
Implementation¶
The component is built using React with custom search algorithms and integrates with the BidScript backend for content indexing.
import React, { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Search, X, FileText, Clock, Tag } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useDebounce } from "@/hooks/useDebounce";
import { useSession } from "next-auth/react";
import apiClient from "@/lib/apiClient";
Props¶
| Prop | Type | Description |
|---|---|---|
onResultSelect |
function | Callback when a search result is selected |
placeholder |
string | Custom placeholder text for search input |
autoFocus |
boolean | Whether to focus the search input on mount |
showFilters |
boolean | Whether to display advanced filtering options |
initialQuery |
string | Initial search query |
maxResults |
number | Maximum number of results to display |
State Management¶
The component manages several state variables:
// Search state
const [query, setQuery] = useState<string>("");
const [debouncedQuery, setDebouncedQuery] = useState<string>("");
const [results, setResults] = useState<SearchResult[]>([]);
const [isSearching, setIsSearching] = useState<boolean>(false);
const [selectedResult, setSelectedResult] = useState<string | null>(null);
// Filter state
const [filters, setFilters] = useState<SearchFilters>({
documentTypes: [],
dateRange: null,
authors: [],
tags: [],
});
const [showFilterPanel, setShowFilterPanel] = useState<boolean>(false);
// History state
const [searchHistory, setSearchHistory] = useState<string[]>([]);
const [showHistory, setShowHistory] = useState<boolean>(false);
Search Implementation¶
The component implements an efficient search process with debouncing:
// Debounce search query to prevent excessive API calls
const debouncedQuery = useDebounce(query, 300);
// Effect to trigger search when query changes
useEffect(() => {
if (debouncedQuery.trim() === "") {
setResults([]);
setIsSearching(false);
return;
}
performSearch();
}, [debouncedQuery, filters]);
// Search function
const performSearch = async () => {
setIsSearching(true);
try {
const response = await apiClient.post("/api/knowledgebase/search", {
query: debouncedQuery,
filters,
maxResults: maxResults || 20,
});
setResults(response.data.results);
// Add to search history if not already present
if (
debouncedQuery.trim() !== "" &&
!searchHistory.includes(debouncedQuery)
) {
setSearchHistory((prev) => [debouncedQuery, ...prev].slice(0, 10));
}
} catch (error) {
console.error("Search error:", error);
setResults([]);
} finally {
setIsSearching(false);
}
};
Search Result Rendering¶
The component presents search results with highlighting and previews:
<div className="search-results-container">
<ScrollArea className="h-[400px]">
{results.length === 0 && debouncedQuery !== "" && !isSearching ? (
<div className="no-results p-4 text-center text-gray-500">
No results found for "{debouncedQuery}"
</div>
) : (
<div className="results-list">
{results.map((result) => (
<motion.div
key={result.id}
initial={{ opacity: 0, y: 5 }}
animate={{ opacity: 1, y: 0 }}
className={`result-item p-3 border-b hover:bg-gray-50 cursor-pointer ${
selectedResult === result.id ? "bg-blue-50" : ""
}`}
onClick={() => handleResultSelect(result)}
>
<div className="flex items-start">
<div className="icon mr-3 mt-1">
<FileText className="h-5 w-5 text-gray-400" />
</div>
<div className="content flex-1">
<div className="title font-medium">{result.title}</div>
<div className="preview text-sm text-gray-600">
{highlightMatch(result.preview, debouncedQuery)}
</div>
<div className="meta flex items-center text-xs text-gray-500 mt-1">
<span className="type mr-2">{result.type}</span>
<span className="date mr-2">
{formatDate(result.lastModified)}
</span>
{result.tags.length > 0 && (
<div className="tags flex items-center">
<Tag className="h-3 w-3 mr-1" />
{result.tags.slice(0, 3).join(", ")}
{result.tags.length > 3 && "..."}
</div>
)}
</div>
</div>
</div>
</motion.div>
))}
</div>
)}
</ScrollArea>
</div>
Filter Panel¶
The component includes an advanced filter panel:
{
showFilterPanel && (
<motion.div
initial={{ opacity: 0, height: 0 }}
animate={{ opacity: 1, height: "auto" }}
exit={{ opacity: 0, height: 0 }}
className="filter-panel border-t mt-2 pt-2"
>
<h4 className="text-sm font-medium mb-2">Filter Results</h4>
<div className="filter-section mb-2">
<label className="text-xs text-gray-500 block mb-1">
Document Type
</label>
<div className="flex flex-wrap gap-1">
{availableDocumentTypes.map((type) => (
<Button
key={type}
variant={
filters.documentTypes.includes(type) ? "default" : "outline"
}
size="sm"
className="text-xs py-1 h-auto"
onClick={() => toggleDocumentTypeFilter(type)}
>
{type}
</Button>
))}
</div>
</div>
<div className="filter-section mb-2">
<label className="text-xs text-gray-500 block mb-1">Date Range</label>
<div className="flex gap-2">
<DatePicker
selected={filters.dateRange?.from}
onChange={(date) => setDateRangeFilter("from", date)}
placeholderText="From"
className="text-sm p-1 border rounded w-full"
/>
<DatePicker
selected={filters.dateRange?.to}
onChange={(date) => setDateRangeFilter("to", date)}
placeholderText="To"
className="text-sm p-1 border rounded w-full"
/>
</div>
</div>
<div className="filter-section mb-2">
<label className="text-xs text-gray-500 block mb-1">Tags</label>
<div className="flex flex-wrap gap-1">
{popularTags.map((tag) => (
<Button
key={tag}
variant={filters.tags.includes(tag) ? "default" : "outline"}
size="sm"
className="text-xs py-1 h-auto"
onClick={() => toggleTagFilter(tag)}
>
{tag}
</Button>
))}
</div>
</div>
</motion.div>
);
}
Usage Example¶
import { KnowledgeBaseSearch } from "@/components/KnowledgeBaseSearch";
const KnowledgeBasePage = () => {
const handleDocumentSelect = (document) => {
// Handle document selection
console.log("Selected document:", document);
};
return (
<div className="knowledge-base-page">
<h1>Knowledge Base</h1>
<div className="search-container my-4">
<KnowledgeBaseSearch
onResultSelect={handleDocumentSelect}
placeholder="Search knowledge base..."
autoFocus
showFilters={true}
maxResults={50}
/>
</div>
{/* Rest of knowledge base page */}
</div>
);
};
Dependencies¶
import { useState, useEffect, useRef } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { Search, X, FileText, Clock, Tag } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ScrollArea } from "@/components/ui/scroll-area";
import { useDebounce } from "@/hooks/useDebounce";
import DatePicker from "react-datepicker";
Notes¶
- Implements semantic search for more intelligent results
- Optimised for performance with large knowledge bases
- Maintains search history for quick access to previous queries
- Highlights matching terms in result previews
- Supports complex filtering combinations
- Provides keyboard navigation for results