From f306159a5aee3e31f46518b87bafcc2a3c552b0c Mon Sep 17 00:00:00 2001 From: Yash-1511 Date: Sun, 5 Jan 2025 17:30:00 +0530 Subject: [PATCH 1/2] feat: add autocomplete suggestions feature in search page --- src/interface/web/app/search/page.tsx | 98 +++++++++++++++++++++------ src/khoj/routers/api.py | 16 +++++ 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/src/interface/web/app/search/page.tsx b/src/interface/web/app/search/page.tsx index b700e5c11..1c099a7ae 100644 --- a/src/interface/web/app/search/page.tsx +++ b/src/interface/web/app/search/page.tsx @@ -173,7 +173,10 @@ export default function Search() { const [searchResultsLoading, setSearchResultsLoading] = useState(false); const [focusSearchResult, setFocusSearchResult] = useState(null); const [exampleQuery, setExampleQuery] = useState(""); + const [fileSuggestions, setFileSuggestions] = useState([]); + const [showSuggestions, setShowSuggestions] = useState(false); const searchTimeoutRef = useRef(null); + const suggestionsTimeoutRef = useRef(null); const isMobileWidth = useIsMobileWidth(); @@ -205,29 +208,62 @@ export default function Search() { }); } - useEffect(() => { - if (!searchQuery.trim()) { + function getFileSuggestions(query: string) { + // Get suggestions only if query starts with "file:" + if (!query.toLowerCase().startsWith("file:")) { + setFileSuggestions([]); + setShowSuggestions(false); return; } - setFocusSearchResult(null); + const filePrefix = query.substring(5).trim(); // Remove "file:" prefix + const apiUrl = `/api/file-suggestions?q=${encodeURIComponent(filePrefix)}`; + + fetch(apiUrl, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then((response) => response.json()) + .then((data) => { + setFileSuggestions(data); + setShowSuggestions(true); + }) + .catch((error) => { + console.error("Error:", error); + }); + } + function handleSearchInputChange(value: string) { + setSearchQuery(value); + + // Clear previous timeouts if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } + if (suggestionsTimeoutRef.current) { + clearTimeout(suggestionsTimeoutRef.current); + } - if (searchQuery.trim()) { + // Get file suggestions immediately + suggestionsTimeoutRef.current = setTimeout(() => { + getFileSuggestions(value); + }, 100); + + // Debounce search + if (value.trim()) { searchTimeoutRef.current = setTimeout(() => { search(); - }, 750); // 1000 milliseconds = 1 second + }, 750); } + } - return () => { - if (searchTimeoutRef.current) { - clearTimeout(searchTimeoutRef.current); - } - }; - }, [searchQuery]); + function applySuggestion(suggestion: string) { + setSearchQuery(`file:${suggestion}`); + setShowSuggestions(false); + search(); + } return ( @@ -247,14 +283,38 @@ export default function Search() {
- setSearchQuery(e.currentTarget.value)} - onKeyDown={(e) => e.key === "Enter" && search()} - type="search" - placeholder="Search Documents" - /> +
+ handleSearchInputChange(e.currentTarget.value)} + onKeyDown={(e) => { + if (e.key === "Enter") { + if (showSuggestions && fileSuggestions.length > 0) { + applySuggestion(fileSuggestions[0]); + } else { + search(); + } + } + }} + type="search" + placeholder="Search Documents (type 'file:' for file suggestions)" + value={searchQuery} + /> + {showSuggestions && fileSuggestions.length > 0 && ( +
+ {fileSuggestions.map((suggestion, index) => ( +
applySuggestion(suggestion)} + > + {suggestion} +
+ ))} +
+ )} +