2024-07-24 12:13:19 +00:00
|
|
|
'use client'
|
|
|
|
|
|
|
|
import { Input } from '@/components/ui/input';
|
|
|
|
|
|
|
|
import { useAuthenticatedData } from '../common/auth';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
|
|
import SidePanel from '../components/sidePanel/chatHistorySidePanel';
|
|
|
|
import NavMenu from '../components/navMenu/navMenu';
|
|
|
|
import styles from './search.module.css';
|
|
|
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
|
|
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
|
2024-07-24 12:30:33 +00:00
|
|
|
import { ArrowLeft, ArrowRight, Folder, FolderOpen, GithubLogo, LinkSimple, MagnifyingGlass, NoteBlank, NotionLogo } from '@phosphor-icons/react';
|
|
|
|
import { Button } from '@/components/ui/button';
|
2024-07-24 12:13:19 +00:00
|
|
|
|
|
|
|
interface AdditionalData {
|
|
|
|
file: string;
|
|
|
|
source: string;
|
|
|
|
compiled: string;
|
|
|
|
heading: string;
|
|
|
|
}
|
|
|
|
|
|
|
|
interface SearchResult {
|
|
|
|
type: string;
|
|
|
|
additional: AdditionalData;
|
|
|
|
entry: string;
|
|
|
|
score: number;
|
|
|
|
"corpus-id": string;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getNoteTypeIcon(source: string) {
|
|
|
|
if (source === 'notion') {
|
|
|
|
return <NotionLogo className='text-muted-foreground' />;
|
|
|
|
}
|
|
|
|
if (source === 'github') {
|
|
|
|
return <GithubLogo className='text-muted-foreground' />;
|
|
|
|
}
|
|
|
|
return <NoteBlank className='text-muted-foreground' />;
|
|
|
|
}
|
|
|
|
|
2024-07-24 12:30:33 +00:00
|
|
|
interface NoteResultProps {
|
|
|
|
note: SearchResult;
|
|
|
|
setFocusSearchResult: (note: SearchResult) => void;
|
|
|
|
}
|
|
|
|
|
|
|
|
function Note(props: NoteResultProps) {
|
|
|
|
const note = props.note;
|
2024-07-24 12:13:19 +00:00
|
|
|
const isFileNameURL = (note.additional.file || '').startsWith('http');
|
|
|
|
const fileName = isFileNameURL ? note.additional.heading : note.additional.file.split('/').pop();
|
2024-07-24 12:30:33 +00:00
|
|
|
|
2024-07-24 12:13:19 +00:00
|
|
|
return (
|
|
|
|
<Card className='bg-secondary h-full shadow-sm rounded-lg bg-gradient-to-b from-background to-slate-50 dark:to-gray-950 border border-muted mb-4'>
|
|
|
|
<CardHeader>
|
2024-07-24 12:58:23 +00:00
|
|
|
<CardDescription className='p-1 border-muted border w-fit rounded-lg mb-2'>
|
|
|
|
{getNoteTypeIcon(note.additional.source)}
|
2024-07-24 12:13:19 +00:00
|
|
|
</CardDescription>
|
|
|
|
<CardTitle>
|
|
|
|
{fileName}
|
|
|
|
</CardTitle>
|
|
|
|
</CardHeader>
|
|
|
|
<CardContent>
|
2024-07-24 12:58:23 +00:00
|
|
|
<div className='line-clamp-4 text-muted-foreground'>
|
2024-07-24 12:13:19 +00:00
|
|
|
{note.entry}
|
|
|
|
</div>
|
2024-07-24 12:58:23 +00:00
|
|
|
<Button onClick={() => props.setFocusSearchResult(note)} variant={'ghost'} className='p-0 mt-2 text-orange-400 hover:bg-inherit'>
|
|
|
|
See content<ArrowRight className='inline ml-2' />
|
|
|
|
</Button>
|
2024-07-24 12:13:19 +00:00
|
|
|
</CardContent>
|
|
|
|
<CardFooter>
|
|
|
|
{
|
|
|
|
isFileNameURL ?
|
|
|
|
<a href={note.additional.file} target="_blank" className='underline text-sm bg-muted p-1 rounded-lg text-muted-foreground'>
|
|
|
|
<LinkSimple className='inline m-2' />{note.additional.file}
|
|
|
|
</a>
|
|
|
|
:
|
|
|
|
<div className='bg-muted p-1 text-sm rounded-lg text-muted-foreground'>
|
|
|
|
<FolderOpen className='inline m-2' />{note.additional.file}
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
</CardFooter>
|
|
|
|
</Card>
|
|
|
|
);
|
2024-07-24 12:30:33 +00:00
|
|
|
}
|
2024-07-24 12:13:19 +00:00
|
|
|
|
2024-07-24 12:30:33 +00:00
|
|
|
function focusNote(note: SearchResult) {
|
|
|
|
const isFileNameURL = (note.additional.file || '').startsWith('http');
|
|
|
|
const fileName = isFileNameURL ? note.additional.heading : note.additional.file.split('/').pop();
|
|
|
|
return (
|
|
|
|
<Card className='bg-secondary h-full shadow-sm rounded-lg bg-gradient-to-b from-background to-slate-50 dark:to-gray-950 border border-muted mb-4'>
|
|
|
|
<CardHeader>
|
|
|
|
<CardTitle>
|
|
|
|
{fileName}
|
|
|
|
</CardTitle>
|
|
|
|
</CardHeader>
|
2024-07-24 12:58:23 +00:00
|
|
|
<CardFooter>
|
|
|
|
{
|
|
|
|
isFileNameURL ?
|
|
|
|
<a href={note.additional.file} target="_blank" className='underline text-sm bg-muted p-3 rounded-lg text-muted-foreground flex items-center gap-2'>
|
|
|
|
<LinkSimple className='inline' />{note.additional.file}
|
|
|
|
</a>
|
|
|
|
:
|
|
|
|
<div className='bg-muted p-3 text-sm rounded-lg text-muted-foreground flex items-center gap-2'>
|
|
|
|
<FolderOpen className='inline' />{note.additional.file}
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
</CardFooter>
|
2024-07-24 12:30:33 +00:00
|
|
|
<CardContent>
|
|
|
|
<div className='text-m'>
|
|
|
|
{note.entry}
|
|
|
|
</div>
|
|
|
|
</CardContent>
|
|
|
|
</Card>
|
|
|
|
);
|
2024-07-24 12:13:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export default function Search() {
|
|
|
|
const authenticatedData = useAuthenticatedData();
|
|
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
|
|
const [isMobileWidth, setIsMobileWidth] = useState(false);
|
|
|
|
const [title, setTitle] = useState('Search');
|
|
|
|
const [searchResults, setSearchResults] = useState<SearchResult[]>([]);
|
|
|
|
const [searchResultsLoading, setSearchResultsLoading] = useState(false);
|
2024-07-24 12:30:33 +00:00
|
|
|
const [focusSearchResult, setFocusSearchResult] = useState<SearchResult | null>(null);
|
2024-07-24 12:13:19 +00:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setIsMobileWidth(window.innerWidth < 786);
|
|
|
|
|
|
|
|
window.addEventListener('resize', () => {
|
|
|
|
setIsMobileWidth(window.innerWidth < 786);
|
|
|
|
});
|
|
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
setTitle(isMobileWidth ? '' : 'Search');
|
|
|
|
}, [isMobileWidth]);
|
|
|
|
|
|
|
|
function search(query: string) {
|
|
|
|
if (searchResultsLoading) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!searchQuery.trim()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log('searchResults', searchResults);
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div className={`${styles.searchLayout}`}>
|
|
|
|
<div className='h-full'>
|
|
|
|
<SidePanel
|
|
|
|
webSocketConnected={true}
|
|
|
|
conversationId={null}
|
|
|
|
uploadedFiles={[]}
|
|
|
|
isMobileWidth={isMobileWidth}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
<div className="md:w-3/4 sm:w-full mr-auto ml-auto">
|
|
|
|
<NavMenu title={title} selected='Chat' />
|
2024-07-24 13:58:38 +00:00
|
|
|
<div className='p-4 md:w-3/4 sm:w-full mr-auto ml-auto'>
|
2024-07-24 12:13:19 +00:00
|
|
|
{
|
|
|
|
isMobileWidth && <div className='font-bold'>Search</div>
|
|
|
|
}
|
|
|
|
<div className='flex justify-between items-center border-2 border-muted p-2 gap-4 rounded-lg'>
|
|
|
|
<MagnifyingGlass className='inline m-2' />
|
|
|
|
<Input
|
|
|
|
className='border-none'
|
2024-07-24 13:58:38 +00:00
|
|
|
onChange={(e) => setSearchQuery(e.currentTarget.value)}
|
|
|
|
onKeyDown={(e) => e.key === 'Enter' && search(searchQuery)}
|
2024-07-24 12:13:19 +00:00
|
|
|
type="search"
|
|
|
|
placeholder="Search Documents" />
|
|
|
|
<button className='px-4 rounded' onClick={() => search(searchQuery)}>
|
2024-07-24 13:58:38 +00:00
|
|
|
Find
|
2024-07-24 12:13:19 +00:00
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
{
|
2024-07-24 12:30:33 +00:00
|
|
|
focusSearchResult &&
|
|
|
|
<div className='mt-4'>
|
|
|
|
<Button onClick={() => setFocusSearchResult(null)} className='mb-4' variant={'outline'}>
|
|
|
|
<ArrowLeft className='inline mr-2' />
|
|
|
|
Back
|
|
|
|
</Button>
|
|
|
|
{focusNote(focusSearchResult)}
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
{
|
|
|
|
!focusSearchResult && searchResults.length > 0 &&
|
2024-07-24 12:13:19 +00:00
|
|
|
<div className='mt-4'>
|
|
|
|
<ScrollArea className="h-[80vh]">
|
|
|
|
{
|
|
|
|
searchResults.map((result, index) => {
|
|
|
|
return (
|
2024-07-24 12:30:33 +00:00
|
|
|
<Note key={result["corpus-id"]}
|
|
|
|
note={result}
|
|
|
|
setFocusSearchResult={setFocusSearchResult} />
|
2024-07-24 12:13:19 +00:00
|
|
|
);
|
|
|
|
})
|
|
|
|
}
|
|
|
|
</ScrollArea>
|
|
|
|
</div>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|