'use client'; import { ResizableHandle, ResizablePanel, ResizablePanelGroup, } from "@/components/ui/resizable"; import { Separator } from "@/components/ui/separator"; import { useNonEmptyQueryParam } from "@/hooks/useNonEmptyQueryParam"; import { GetSourceResponse, pathQueryParamName, repoQueryParamName, ZoektFileMatch, ZoektSearchResponse } from "@/lib/types"; 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 { 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 { CodePreview, CodePreviewFile } from "./codePreview"; import { SearchResults } from "./searchResults"; import { useRouter } from "next/navigation"; 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: async (): Promise => { console.log("Fetching search results"); const result = await fetch(`/api/search?query=${searchQuery}&numResults=${numResults}`) .then(response => response.json()); console.log("Done"); return result; }, enabled: searchQuery.length > 0, }); const { fileMatches, searchDurationMs } = useMemo((): { fileMatches: ZoektFileMatch[], searchDurationMs: number } => { if (!searchResponse) { return { fileMatches: [], searchDurationMs: 0, }; } return { fileMatches: searchResponse.result.FileMatches ?? [], searchDurationMs: Math.round(searchResponse.result.Stats.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?: ZoektFileMatch; onClose: () => void; selectedMatchIndex: number; onSelectedMatchIndexChange: (index: number) => void; } const CodePreviewWrapper = ({ fileMatch, onClose, selectedMatchIndex, onSelectedMatchIndexChange, }: CodePreviewWrapperProps) => { const { data: file } = useQuery({ queryKey: ["source", fileMatch?.FileName, fileMatch?.Repo], queryFn: async (): Promise => { if (!fileMatch) { return undefined; } const url = createPathWithQueryParams( `/api/source`, [pathQueryParamName, fileMatch.FileName], [repoQueryParamName, fileMatch.Repo] ); return fetch(url) .then(response => response.json()) .then((body: GetSourceResponse) => { if (body.encoding !== "base64") { throw new Error("Expected base64 encoding"); } const content = atob(body.content); const link = getCodeHostFilePreviewLink(fileMatch.Repo, fileMatch.FileName) return { content, filepath: fileMatch.FileName, matches: fileMatch.Matches, link: link, }; }); }, enabled: fileMatch !== undefined, }); return ( ) }