'use client'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { Separator } from "@/components/ui/separator"; import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; import { getCodeHostFilePreviewLink } from "@/lib/utils"; import { SymbolIcon } from "@radix-ui/react-icons"; import { useQuery } from "@tanstack/react-query"; import Image from "next/image"; import { useMemo, useState } from "react"; import logoDark from "../../../public/sb_logo_dark.png"; import logoLight from "../../../public/sb_logo_light.png"; import { SearchBar } from "../searchBar"; import { SettingsDropdown } from "../settingsDropdown"; import { CodePreviewPanel, CodePreviewFile } from "./codePreviewPanel"; import { SearchResultsPanel } from "./searchResultsPanel"; import { useRouter } from "next/navigation"; import { fetchFileSource, search } from "../api/(client)/client"; import { SearchResultFile } from "@/lib/schemas"; export default function SearchPage() { const router = useRouter(); const searchQuery = useNonEmptyQueryParam("query") ?? ""; const numResults = useNonEmptyQueryParam("numResults") ?? "100"; const [selectedMatchIndex, setSelectedMatchIndex] = useState(0); const [selectedFile, setSelectedFile] = useState(undefined); const { data: searchResponse, isLoading } = useQuery({ queryKey: ["search", searchQuery, numResults], queryFn: () => search({ query: searchQuery, numResults: parseInt(numResults), }), enabled: searchQuery.length > 0, }); 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]); return (
{/* TopBar */}
{ router.push("/"); }} > {"Sourcebot {"Sourcebot
{isLoading && ( )}

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

{/* Search Results & Code Preview */} { setSelectedFile(fileMatch); 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); return { content: decodedSource, filepath: fileMatch.FileName, matches: fileMatch.ChunkMatches, link: link, language: fileMatch.Language, }; }); }, enabled: fileMatch !== undefined, }); return ( ) }