'use client'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { Separator } from "@/components/ui/separator"; import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; import { SearchResultFile } from "@/lib/schemas"; import { createPathWithQueryParams, getCodeHostFilePreviewLink } from "@/lib/utils"; import { SymbolIcon } from "@radix-ui/react-icons"; import { useQuery } from "@tanstack/react-query"; import Image from "next/image"; import { useRouter } from "next/navigation"; import { useEffect, useMemo, useState } from "react"; import logoDark from "../../../public/sb_logo_dark.png"; import logoLight from "../../../public/sb_logo_light.png"; import { fetchFileSource, search } from "../api/(client)/client"; import { SearchBar } from "../searchBar"; import { SettingsDropdown } from "../settingsDropdown"; import { CodePreviewFile, CodePreviewPanel } from "./codePreviewPanel"; import { SearchResultsPanel } from "./searchResultsPanel"; import useCaptureEvent from "@/hooks/useCaptureEvent"; const DEFAULT_NUM_RESULTS = 100; export default function SearchPage() { const router = useRouter(); const searchQuery = useNonEmptyQueryParam("query") ?? ""; const _numResults = parseInt(useNonEmptyQueryParam("numResults") ?? `${DEFAULT_NUM_RESULTS}`); const numResults = isNaN(_numResults) ? DEFAULT_NUM_RESULTS : _numResults; const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); const [selectedFile, setSelectedFile] = useState(undefined); const captureEvent = useCaptureEvent(); const { data: searchResponse, isLoading } = useQuery({ queryKey: ["search", searchQuery, numResults], queryFn: () => search({ query: searchQuery, numResults, }), enabled: searchQuery.length > 0, refetchOnWindowFocus: false, }); useEffect(() => { if (!searchResponse) { return; } const fileLanguages = searchResponse.Result.Files?.map(file => file.Language) || []; captureEvent("search_finished", { contentBytesLoaded: searchResponse.Result.ContentBytesLoaded, indexBytesLoaded: searchResponse.Result.IndexBytesLoaded, crashes: searchResponse.Result.Crashes, durationMs: searchResponse.Result.Duration / 1000000, fileCount: searchResponse.Result.FileCount, shardFilesConsidered: searchResponse.Result.ShardFilesConsidered, filesConsidered: searchResponse.Result.FilesConsidered, filesLoaded: searchResponse.Result.FilesLoaded, filesSkipped: searchResponse.Result.FilesSkipped, shardsScanned: searchResponse.Result.ShardsScanned, shardsSkipped: searchResponse.Result.ShardsSkipped, shardsSkippedFilter: searchResponse.Result.ShardsSkippedFilter, matchCount: searchResponse.Result.MatchCount, ngramMatches: searchResponse.Result.NgramMatches, ngramLookups: searchResponse.Result.NgramLookups, wait: searchResponse.Result.Wait, matchTreeConstruction: searchResponse.Result.MatchTreeConstruction, matchTreeSearch: searchResponse.Result.MatchTreeSearch, regexpsConsidered: searchResponse.Result.RegexpsConsidered, flushReason: searchResponse.Result.FlushReason, fileLanguages, }); }, [captureEvent, searchResponse]); const { fileMatches, searchDurationMs } = useMemo((): { fileMatches: SearchResultFile[], searchDurationMs: number } => { if (!searchResponse) { return { fileMatches: [], searchDurationMs: 0, }; } return { fileMatches: searchResponse.Result.Files ?? [], searchDurationMs: Math.round(searchResponse.Result.Duration / 1000000), } }, [searchResponse]); const isMoreResultsButtonVisible = useMemo(() => { return searchResponse && searchResponse.Result.MatchCount > numResults; }, [searchResponse, numResults]); return (
{/* TopBar */}
{ router.push("/"); }} > {"Sourcebot {"Sourcebot

Results for: {fileMatches.length} files in {searchDurationMs} ms

{isMoreResultsButtonVisible && (
{ const url = createPathWithQueryParams('/search', ["query", searchQuery], ["numResults", `${numResults * 2}`], ) router.push(url); }} > More results
)}
{/* Search Results & Code Preview */} {isLoading ? (

Searching...

) : ( { setSelectedFile(fileMatch); }} onMatchIndexChanged={(matchIndex) => { setSelectedMatchIndex(matchIndex); }} /> )}
); } interface CodePreviewWrapperProps { fileMatch?: SearchResultFile; onClose: () => void; selectedMatchIndex: number; onSelectedMatchIndexChange: (index: number) => void; } const CodePreviewWrapper = ({ fileMatch, onClose, selectedMatchIndex, onSelectedMatchIndexChange, }: CodePreviewWrapperProps) => { const { data: file } = useQuery({ queryKey: ["source", fileMatch?.FileName, fileMatch?.Repository], queryFn: async (): Promise => { if (!fileMatch) { return undefined; } return fetchFileSource(fileMatch.FileName, fileMatch.Repository) .then(({ source }) => { // @todo : refector this to use the templates provided by zoekt. const link = getCodeHostFilePreviewLink(fileMatch.Repository, fileMatch.FileName) const decodedSource = atob(source); // Filter out filename matches const filteredMatches = fileMatch.ChunkMatches.filter((match) => { return !match.FileName; }); return { content: decodedSource, filepath: fileMatch.FileName, matches: filteredMatches, link: link, language: fileMatch.Language, }; }); }, enabled: fileMatch !== undefined, }); return ( ) }