"use client"; import { Input } from "@/components/ui/input"; import { useEffect, useRef, useState } from "react"; import styles from "./search.module.css"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle, } from "@/components/ui/card"; import { ArrowLeft, ArrowRight, FileDashed, FileMagnifyingGlass, GithubLogo, Lightbulb, LinkSimple, MagnifyingGlass, NoteBlank, NotionLogo, Eye, Trash, ArrowsOutSimple, DotsThreeVertical, Waveform, Plus, } from "@phosphor-icons/react"; import { Button } from "@/components/ui/button"; import Link from "next/link"; import { getIconFromFilename } from "../common/iconUtils"; import { formatDateTime, useIsMobileWidth } from "../common/utils"; import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar"; import { AppSidebar } from "../components/appSidebar/appSidebar"; import { Separator } from "@/components/ui/separator"; import { KhojLogoType } from "../components/logo/khojLogo"; import { InlineLoading } from "../components/loading/loading"; import { AlertDialog, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogCancel, AlertDialogAction, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { useToast } from "@/components/ui/use-toast"; import { Scroll } from "lucide-react"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { uploadDataForIndexing } from "../common/chatFunctions"; import { CommandDialog } from "@/components/ui/command"; import { Progress } from "@/components/ui/progress"; interface AdditionalData { file: string; source: string; compiled: string; heading: string; } interface SearchResult { type: string; additional: AdditionalData; entry: string; score: number; "corpus-id": string; } interface FileObject { file_name: string; raw_text: string; updated_at: string; } function getNoteTypeIcon(source: string) { if (source === "notion") { return ; } if (source === "github") { return ; } return ; } const naturalLanguageSearchQueryExamples = [ "What does the paper say about climate change?", "Making a cappuccino at home", "Benefits of eating mangoes", "How to plan a wedding on a budget", "Appointment with Dr. Makinde on 12th August", "Class notes lecture 3 on quantum mechanics", "Painting concepts for acrylics", "Abstract from the paper attention is all you need", "Climbing Everest without oxygen", "Solving a rubik's cube in 30 seconds", "Facts about the planet Mars", "How to make a website using React", "Fish at the bottom of the ocean", "Fish farming Kenya 2021", "How to make a cake without an oven", "Installing a solar panel at home", ]; interface NoteResultProps { note: SearchResult; setFocusSearchResult: (note: SearchResult) => void; } function Note(props: NoteResultProps) { const note = props.note; const isFileNameURL = (note.additional.file || "").startsWith("http"); const fileName = isFileNameURL ? note.additional.heading : note.additional.file.split("/").pop(); const fileIcon = getIconFromFilename(fileName || ".txt", "h-4 w-4 inline mr-2"); return ( {getNoteTypeIcon(note.additional.source)} {fileName}
{note.entry}
{isFileNameURL ? ( {note.additional.file} ) : (
{fileIcon} {note.additional.file}
)}
); } function focusNote(note: SearchResult) { const isFileNameURL = (note.additional.file || "").startsWith("http"); const fileName = isFileNameURL ? note.additional.heading : note.additional.file.split("/").pop(); const fileIcon = getIconFromFilename(fileName || ".txt", "h-4 w-4 inline mr-2"); return ( {fileName} {isFileNameURL ? ( {note.additional.file} ) : (
{fileIcon} {note.additional.file}
)}
{note.entry}
); } const UploadFiles: React.FC<{ onClose: () => void; setUploadedFiles: (files: string[]) => void; }> = ({ onClose, setUploadedFiles }) => { const [isDragAndDropping, setIsDragAndDropping] = useState(false); const [warning, setWarning] = useState(null); const [error, setError] = useState(null); const [uploading, setUploading] = useState(false); const [progressValue, setProgressValue] = useState(0); const fileInputRef = useRef(null); useEffect(() => { if (!uploading) { setProgressValue(0); } if (uploading) { const interval = setInterval(() => { setProgressValue((prev) => { const increment = Math.floor(Math.random() * 5) + 1; // Generates a random number between 1 and 5 const nextValue = prev + increment; return nextValue < 100 ? nextValue : 100; // Ensures progress does not exceed 100 }); }, 800); return () => clearInterval(interval); } }, [uploading]); function handleDragOver(event: React.DragEvent) { event.preventDefault(); setIsDragAndDropping(true); } function handleDragLeave(event: React.DragEvent) { event.preventDefault(); setIsDragAndDropping(false); } function handleDragAndDropFiles(event: React.DragEvent) { event.preventDefault(); setIsDragAndDropping(false); if (!event.dataTransfer.files) return; uploadFiles(event.dataTransfer.files); } function openFileInput() { if (fileInputRef && fileInputRef.current) { fileInputRef.current.click(); } } function handleFileChange(event: React.ChangeEvent) { if (!event.target.files) return; uploadFiles(event.target.files); } function uploadFiles(files: FileList) { uploadDataForIndexing(files, setWarning, setUploading, setError, setUploadedFiles); } return ( Build Knowledge Base Adding files to your Khoj knowledge base allows your AI to search through your own documents. This helps you get personalized answers, grounded in your own data.
{uploading && ( )}
{warning && (
{warning}
)} {error && (
{error}
)}
{isDragAndDropping ? (
Drop files to upload
) : (
Drag and drop files here
)}
); }; export default function Search() { const [searchQuery, setSearchQuery] = useState(""); const [searchResults, setSearchResults] = useState(null); const [searchResultsLoading, setSearchResultsLoading] = useState(false); const [focusSearchResult, setFocusSearchResult] = useState(null); const [files, setFiles] = useState([]); const [error, setError] = useState(null); const [fileObjectsLoading, setFileObjectsLoading] = useState(true); const searchTimeoutRef = useRef(null); const [selectedFile, setSelectedFile] = useState(null); const [selectedFileFullText, setSelectedFileFullText] = useState(null); const [isDeleting, setIsDeleting] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); const [selectedFiles, setSelectedFiles] = useState([]); const [filteredFiles, setFilteredFiles] = useState([]); const { toast } = useToast(); const isMobileWidth = useIsMobileWidth(); function search() { if (searchResultsLoading || !searchQuery.trim()) return; setSearchResultsLoading(true); const apiUrl = `/api/search?q=${encodeURIComponent(searchQuery)}&client=web`; fetch(apiUrl, { method: "GET", headers: { "Content-Type": "application/json", }, }) .then((response) => response.json()) .then((data) => { setSearchResults(data); setSearchResultsLoading(false); }) .catch((error) => { console.error("Error:", error); }); } const deleteSelected = async () => { let filesToDelete = selectedFiles.length > 0 ? selectedFiles : filteredFiles; if (filesToDelete.length === 0) { return; } try { const response = await fetch("/api/content/files", { method: "DELETE", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ files: filesToDelete }), }); if (!response.ok) throw new Error("Failed to delete files"); // Update the syncedFiles state setUploadedFiles((prevFiles) => prevFiles.filter((file) => !filesToDelete.includes(file)), ); // Reset selectedFiles setSelectedFiles([]); } catch (error) { console.error("Error deleting files:", error); } }; const fetchFiles = async () => { try { const response = await fetch("/api/content/all"); if (!response.ok) throw new Error("Failed to fetch files"); const filesList = await response.json(); if (Array.isArray(filesList)) { setFiles(filesList.toSorted()); } } catch (error) { setError("Failed to load files"); console.error("Error fetching files:", error); } finally { setFileObjectsLoading(false); } }; const fetchSpecificFile = async (fileName: string) => { try { const response = await fetch(`/api/content/file?file_name=${fileName}`); if (!response.ok) throw new Error("Failed to fetch file"); const file = await response.json(); setSelectedFileFullText(file.raw_text); } catch (error) { setError("Failed to load file"); console.error("Error fetching file:", error); } }; useEffect(() => { if (!searchQuery.trim()) { setSearchResults(null); return; } setFocusSearchResult(null); if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } if (searchQuery.trim()) { searchTimeoutRef.current = setTimeout(() => { search(); }, 750); // 1000 milliseconds = 1 second } return () => { if (searchTimeoutRef.current) { clearTimeout(searchTimeoutRef.current); } }; }, [searchQuery]); useEffect(() => { if (selectedFile) { fetchSpecificFile(selectedFile); } }, [selectedFile]); useEffect(() => { fetchFiles(); }, []); useEffect(() => { if (uploadedFiles.length > 0) { fetchFiles(); } }, [uploadedFiles]); const handleDelete = async (fileName: string) => { setIsDeleting(true); try { const response = await fetch(`/api/content/file?filename=${fileName}`, { method: "DELETE", }); if (!response.ok) throw new Error("Failed to delete file"); toast({ title: "File deleted", description: `File ${fileName} has been deleted`, variant: "default", }); // Refresh files list fetchFiles(); } catch (error) { toast({ title: "Error deleting file", description: `Failed to delete file ${fileName}`, variant: "destructive", }); } finally { setIsDeleting(false); } }; return (
{isMobileWidth ? ( ) : (

Search

)}
setSearchQuery(e.currentTarget.value)} onKeyDown={(e) => e.key === "Enter" && search()} type="search" placeholder="Search Documents" />
{}} setUploadedFiles={setUploadedFiles} /> {searchResultsLoading && (
)} {focusSearchResult && (
{focusNote(focusSearchResult)}
)} {!focusSearchResult && !searchResultsLoading && searchResults && searchResults.length > 0 && (
{searchResults.map((result, index) => { return ( ); })}
)} {searchResults === null && (
{fileObjectsLoading && (
)} {error &&
{error}
}
{files.map((file, index) => (
{ setSelectedFileFullText( null, ); setSelectedFile( file.file_name, ); }} > {file.file_name .split("/") .pop()}
{file.file_name .split("/") .pop()}

{!selectedFileFullText && ( )} {selectedFileFullText}

Delete File Are you sure you want to delete this file? Cancel handleDelete( file.file_name, ) } > {isDeleting ? "Deleting..." : "Delete"}

{file.raw_text.slice(0, 100)}...

{formatDateTime(file.updated_at)}
))}
)} {searchResults && searchResults.length === 0 && ( No documents found
To use search, upload your docs to your account.
Learn More
)}
); }