mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 05:39:12 +00:00
Fix text quoting and format web app search page with prettier
This commit is contained in:
@@ -73,12 +73,8 @@ import {
|
|||||||
CommandInput,
|
CommandInput,
|
||||||
CommandItem,
|
CommandItem,
|
||||||
CommandList,
|
CommandList,
|
||||||
} from "@/components/ui/command"
|
} from "@/components/ui/command";
|
||||||
import {
|
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||||
Popover,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTrigger,
|
|
||||||
} from "@/components/ui/popover"
|
|
||||||
|
|
||||||
import { uploadDataForIndexing } from "../common/chatFunctions";
|
import { uploadDataForIndexing } from "../common/chatFunctions";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
@@ -125,11 +121,17 @@ interface FileCardProps {
|
|||||||
selectedFileFullText: string | null;
|
selectedFileFullText: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function FileCard({ file, setSelectedFile, setSelectedFileFullText, handleDownload, handleDelete, isDeleting, selectedFileFullText }: FileCardProps) {
|
function FileCard({
|
||||||
|
file,
|
||||||
|
setSelectedFile,
|
||||||
|
setSelectedFileFullText,
|
||||||
|
handleDownload,
|
||||||
|
handleDelete,
|
||||||
|
isDeleting,
|
||||||
|
selectedFileFullText,
|
||||||
|
}: FileCardProps) {
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card className="animate-fade-in-up bg-secondary h-52">
|
||||||
className="animate-fade-in-up bg-secondary h-52"
|
|
||||||
>
|
|
||||||
<CardHeader className="p-2">
|
<CardHeader className="p-2">
|
||||||
<CardTitle
|
<CardTitle
|
||||||
className="flex items-center gap-2 justify-between"
|
className="flex items-center gap-2 justify-between"
|
||||||
@@ -140,36 +142,23 @@ function FileCard({ file, setSelectedFile, setSelectedFileFullText, handleDownlo
|
|||||||
<div
|
<div
|
||||||
className="text-sm font-medium truncate hover:text-clip hover:whitespace-normal cursor-pointer"
|
className="text-sm font-medium truncate hover:text-clip hover:whitespace-normal cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedFileFullText(
|
setSelectedFileFullText("");
|
||||||
'',
|
setSelectedFile(file.file_name);
|
||||||
);
|
|
||||||
setSelectedFile(
|
|
||||||
file.file_name,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{file.file_name
|
{file.file_name.split("/").pop()}
|
||||||
.split("/")
|
|
||||||
.pop()}
|
|
||||||
</div>
|
</div>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>
|
<DialogTitle>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{file.file_name
|
{file.file_name.split("/").pop()}
|
||||||
.split("/")
|
|
||||||
.pop()}
|
|
||||||
<Button
|
<Button
|
||||||
variant={
|
variant={"ghost"}
|
||||||
"ghost"
|
|
||||||
}
|
|
||||||
title="Download as plaintext"
|
title="Download as plaintext"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleDownload(
|
handleDownload(file.file_name, file.raw_text)
|
||||||
file.file_name,
|
|
||||||
file.raw_text,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Download className="h-4 w-4" />
|
<Download className="h-4 w-4" />
|
||||||
@@ -182,15 +171,11 @@ function FileCard({ file, setSelectedFile, setSelectedFileFullText, handleDownlo
|
|||||||
{!selectedFileFullText && (
|
{!selectedFileFullText && (
|
||||||
<InlineLoading
|
<InlineLoading
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
message={
|
message={"Loading"}
|
||||||
"Loading"
|
|
||||||
}
|
|
||||||
iconClassName="h-5 w-5"
|
iconClassName="h-5 w-5"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{
|
{selectedFileFullText}
|
||||||
selectedFileFullText
|
|
||||||
}
|
|
||||||
</p>
|
</p>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@@ -207,16 +192,12 @@ function FileCard({ file, setSelectedFile, setSelectedFileFullText, handleDownlo
|
|||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
className="flex items-center gap-2 p-1 text-sm"
|
className="flex items-center gap-2 p-1 text-sm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDelete(
|
handleDelete(file.file_name);
|
||||||
file.file_name,
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash className="h-4 w-4" />
|
<Trash className="h-4 w-4" />
|
||||||
<span className="text-xs">
|
<span className="text-xs">
|
||||||
{isDeleting
|
{isDeleting ? "Deleting..." : "Delete"}
|
||||||
? "Deleting..."
|
|
||||||
: "Delete"}
|
|
||||||
</span>
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -237,7 +218,7 @@ function FileCard({ file, setSelectedFile, setSelectedFileFullText, handleDownlo
|
|||||||
</div>
|
</div>
|
||||||
</CardFooter>
|
</CardFooter>
|
||||||
</Card>
|
</Card>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NoteResultProps {
|
interface NoteResultProps {
|
||||||
@@ -397,8 +378,8 @@ const UploadFiles: React.FC<{
|
|||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Build Your Knowledge Base</DialogTitle>
|
<DialogTitle>Build Your Knowledge Base</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
Add context for your Khoj knowledge base.
|
Add context for your Khoj knowledge base. Quickly search and get
|
||||||
Quickly search and get personalized answers from your documents.
|
personalized answers from your documents.
|
||||||
</DialogDescription>
|
</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div
|
<div
|
||||||
@@ -485,8 +466,8 @@ interface FileFilterComboBoxProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function FileFilterComboBox(props: FileFilterComboBoxProps) {
|
function FileFilterComboBox(props: FileFilterComboBoxProps) {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false);
|
||||||
const [value, setValue] = useState(props.explicitFile || "")
|
const [value, setValue] = useState(props.explicitFile || "");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (props.explicitFile) {
|
if (props.explicitFile) {
|
||||||
@@ -504,17 +485,12 @@ function FileFilterComboBox(props: FileFilterComboBoxProps) {
|
|||||||
className={`justify-between" ${props.isMobileWidth ? "w-full" : "w-[200px]"}`}
|
className={`justify-between" ${props.isMobileWidth ? "w-full" : "w-[200px]"}`}
|
||||||
>
|
>
|
||||||
{value
|
{value
|
||||||
? (
|
? props.isMobileWidth
|
||||||
props.isMobileWidth ?
|
? "✔️"
|
||||||
"✔️"
|
: "Selected"
|
||||||
: "Selected"
|
: props.isMobileWidth
|
||||||
)
|
? " "
|
||||||
: (
|
: "Select file"}
|
||||||
props.isMobileWidth ?
|
|
||||||
" "
|
|
||||||
: "Select file"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
<Funnel className="opacity-50" />
|
<Funnel className="opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
@@ -529,16 +505,16 @@ function FileFilterComboBox(props: FileFilterComboBoxProps) {
|
|||||||
key={file}
|
key={file}
|
||||||
value={file}
|
value={file}
|
||||||
onSelect={(currentValue) => {
|
onSelect={(currentValue) => {
|
||||||
setValue(currentValue === value ? "" : currentValue)
|
setValue(currentValue === value ? "" : currentValue);
|
||||||
setOpen(false)
|
setOpen(false);
|
||||||
props.onChooseFile(currentValue)
|
props.onChooseFile(currentValue);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{file}
|
{file}
|
||||||
<Check
|
<Check
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-auto",
|
"ml-auto",
|
||||||
value === file ? "opacity-100" : "opacity-0"
|
value === file ? "opacity-100" : "opacity-0",
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
@@ -548,7 +524,7 @@ function FileFilterComboBox(props: FileFilterComboBoxProps) {
|
|||||||
</Command>
|
</Command>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Search() {
|
export default function Search() {
|
||||||
@@ -576,18 +552,18 @@ export default function Search() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Load all files once on page load
|
// Load all files once on page load
|
||||||
fetch('/api/content/computer', {
|
fetch("/api/content/computer", {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(response => response.json())
|
.then((response) => response.json())
|
||||||
.then(data => {
|
.then((data) => {
|
||||||
setAllFiles(data);
|
setAllFiles(data);
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch((error) => {
|
||||||
console.error('Error loading files:', error);
|
console.error("Error loading files:", error);
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -606,7 +582,6 @@ export default function Search() {
|
|||||||
}
|
}
|
||||||
}, [searchQuery]);
|
}, [searchQuery]);
|
||||||
|
|
||||||
|
|
||||||
function handleSearchInputChange(value: string) {
|
function handleSearchInputChange(value: string) {
|
||||||
setSearchQuery(value);
|
setSearchQuery(value);
|
||||||
|
|
||||||
@@ -673,7 +648,6 @@ export default function Search() {
|
|||||||
if (Array.isArray(filesList)) {
|
if (Array.isArray(filesList)) {
|
||||||
setFiles(filesList.toSorted());
|
setFiles(filesList.toSorted());
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setError("Failed to load files");
|
setError("Failed to load files");
|
||||||
console.error("Error fetching files:", error);
|
console.error("Error fetching files:", error);
|
||||||
@@ -778,7 +752,9 @@ export default function Search() {
|
|||||||
<Input
|
<Input
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
className="border-none pl-4 focus-visible:ring-transparent focus-visible:ring-offset-transparent"
|
className="border-none pl-4 focus-visible:ring-transparent focus-visible:ring-offset-transparent"
|
||||||
onChange={(e) => handleSearchInputChange(e.currentTarget.value)}
|
onChange={(e) =>
|
||||||
|
handleSearchInputChange(e.currentTarget.value)
|
||||||
|
}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
search();
|
search();
|
||||||
@@ -791,7 +767,12 @@ export default function Search() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div id="actions" className="flex gap-2">
|
<div id="actions" className="flex gap-2">
|
||||||
<FileFilterComboBox allFiles={allFiles} onChooseFile={(file) => applySuggestion(file)} isMobileWidth={isMobileWidth} explicitFile={selectedFileFilter} />
|
<FileFilterComboBox
|
||||||
|
allFiles={allFiles}
|
||||||
|
onChooseFile={(file) => applySuggestion(file)}
|
||||||
|
isMobileWidth={isMobileWidth}
|
||||||
|
explicitFile={selectedFileFilter}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="px-2 gap-2 inline-flex rounded-none items-center border-l border-gray-300 hover:text-gray-500"
|
className="px-2 gap-2 inline-flex rounded-none items-center border-l border-gray-300 hover:text-gray-500"
|
||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
@@ -870,34 +851,37 @@ export default function Search() {
|
|||||||
)}
|
)}
|
||||||
{error && <div className="text-red-500">{error}</div>}
|
{error && <div className="text-red-500">{error}</div>}
|
||||||
|
|
||||||
{!searchResults && !fileObjectsLoading && files.length === 0 && (
|
{!searchResults &&
|
||||||
<Card className="flex flex-col items-center justify-center border-none shadow-none">
|
!fileObjectsLoading &&
|
||||||
<CardHeader className="flex flex-col items-center justify-center">
|
files.length === 0 && (
|
||||||
<CardDescription className="border-muted-foreground border w-fit rounded-lg mb-2 text-center text-lg p-4">
|
<Card className="flex flex-col items-center justify-center border-none shadow-none">
|
||||||
<FileDashed
|
<CardHeader className="flex flex-col items-center justify-center">
|
||||||
weight="fill"
|
<CardDescription className="border-muted-foreground border w-fit rounded-lg mb-2 text-center text-lg p-4">
|
||||||
className="text-muted-foreground h-10 w-10"
|
<FileDashed
|
||||||
/>
|
weight="fill"
|
||||||
</CardDescription>
|
className="text-muted-foreground h-10 w-10"
|
||||||
<CardTitle className="text-center">
|
/>
|
||||||
Let's get started!
|
</CardDescription>
|
||||||
</CardTitle>
|
<CardTitle className="text-center">
|
||||||
</CardHeader>
|
Let's get started!
|
||||||
<CardContent>
|
</CardTitle>
|
||||||
<div className="text-muted-foreground items-center justify-center text-center flex">
|
</CardHeader>
|
||||||
To use search, upload docs via the "Add Documents" button.
|
<CardContent>
|
||||||
</div>
|
<div className="text-muted-foreground items-center justify-center text-center flex">
|
||||||
<Link
|
To use search, upload docs via the
|
||||||
href="https://docs.khoj.dev/data-sources/share_your_data"
|
"Add Documents" button.
|
||||||
className="no-underline"
|
|
||||||
>
|
|
||||||
<div className="mt-4 text-center text-secondary-foreground bg-secondary w-fit m-auto p-2 rounded-lg">
|
|
||||||
Learn More
|
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
<Link
|
||||||
</CardContent>
|
href="https://docs.khoj.dev/data-sources/share_your_data"
|
||||||
</Card>
|
className="no-underline"
|
||||||
)}
|
>
|
||||||
|
<div className="mt-4 text-center text-secondary-foreground bg-secondary w-fit m-auto p-2 rounded-lg">
|
||||||
|
Learn More
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
{files.map((file, index) => (
|
{files.map((file, index) => (
|
||||||
@@ -906,7 +890,9 @@ export default function Search() {
|
|||||||
file={file}
|
file={file}
|
||||||
index={index}
|
index={index}
|
||||||
setSelectedFile={setSelectedFile}
|
setSelectedFile={setSelectedFile}
|
||||||
setSelectedFileFullText={setSelectedFileFullText}
|
setSelectedFileFullText={
|
||||||
|
setSelectedFileFullText
|
||||||
|
}
|
||||||
handleDownload={handleDownload}
|
handleDownload={handleDownload}
|
||||||
handleDelete={handleDelete}
|
handleDelete={handleDelete}
|
||||||
isDeleting={isDeleting}
|
isDeleting={isDeleting}
|
||||||
@@ -920,14 +906,22 @@ export default function Search() {
|
|||||||
{/* Show prev button if not on first page */}
|
{/* Show prev button if not on first page */}
|
||||||
{pageNumber > 0 && (
|
{pageNumber > 0 && (
|
||||||
<PaginationItem className="list-none">
|
<PaginationItem className="list-none">
|
||||||
<PaginationPrevious onClick={() => setPageNumber(pageNumber - 1)} />
|
<PaginationPrevious
|
||||||
|
onClick={() =>
|
||||||
|
setPageNumber(pageNumber - 1)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Show first page if not on first two pages */}
|
{/* Show first page if not on first two pages */}
|
||||||
{pageNumber > 1 && (
|
{pageNumber > 1 && (
|
||||||
<PaginationItem className="list-none">
|
<PaginationItem className="list-none">
|
||||||
<PaginationLink onClick={() => setPageNumber(0)}>1</PaginationLink>
|
<PaginationLink
|
||||||
|
onClick={() => setPageNumber(0)}
|
||||||
|
>
|
||||||
|
1
|
||||||
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -941,19 +935,33 @@ export default function Search() {
|
|||||||
{/* Show previous page if not on first page */}
|
{/* Show previous page if not on first page */}
|
||||||
{pageNumber > 0 && (
|
{pageNumber > 0 && (
|
||||||
<PaginationItem className="list-none">
|
<PaginationItem className="list-none">
|
||||||
<PaginationLink onClick={() => setPageNumber(pageNumber - 1)}>{pageNumber}</PaginationLink>
|
<PaginationLink
|
||||||
|
onClick={() =>
|
||||||
|
setPageNumber(pageNumber - 1)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageNumber}
|
||||||
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Current page */}
|
{/* Current page */}
|
||||||
<PaginationItem className="list-none">
|
<PaginationItem className="list-none">
|
||||||
<PaginationLink isActive>{pageNumber + 1}</PaginationLink>
|
<PaginationLink isActive>
|
||||||
|
{pageNumber + 1}
|
||||||
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
|
|
||||||
{/* Show next page if not on last page */}
|
{/* Show next page if not on last page */}
|
||||||
{pageNumber < numPages - 1 && (
|
{pageNumber < numPages - 1 && (
|
||||||
<PaginationItem className="list-none">
|
<PaginationItem className="list-none">
|
||||||
<PaginationLink onClick={() => setPageNumber(pageNumber + 1)}>{pageNumber + 2}</PaginationLink>
|
<PaginationLink
|
||||||
|
onClick={() =>
|
||||||
|
setPageNumber(pageNumber + 1)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{pageNumber + 2}
|
||||||
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -967,22 +975,30 @@ export default function Search() {
|
|||||||
{/* Show last page if not on last two pages */}
|
{/* Show last page if not on last two pages */}
|
||||||
{pageNumber < numPages - 2 && (
|
{pageNumber < numPages - 2 && (
|
||||||
<PaginationItem className="list-none">
|
<PaginationItem className="list-none">
|
||||||
<PaginationLink onClick={() => setPageNumber(numPages - 1)}>{numPages}</PaginationLink>
|
<PaginationLink
|
||||||
|
onClick={() =>
|
||||||
|
setPageNumber(numPages - 1)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{numPages}
|
||||||
|
</PaginationLink>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Show next button if not on last page */}
|
{/* Show next button if not on last page */}
|
||||||
{pageNumber < numPages - 1 && (
|
{pageNumber < numPages - 1 && (
|
||||||
<PaginationItem className="list-none">
|
<PaginationItem className="list-none">
|
||||||
<PaginationNext onClick={() => setPageNumber(pageNumber + 1)} />
|
<PaginationNext
|
||||||
|
onClick={() =>
|
||||||
|
setPageNumber(pageNumber + 1)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
)}
|
)}
|
||||||
</PaginationContent>
|
</PaginationContent>
|
||||||
</Pagination>
|
</Pagination>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user