Perplexica/ui/components/LegalSearch.tsx

200 lines
No EOL
7.4 KiB
TypeScript

/* eslint-disable @next/next/no-img-element */
import { BookCopy, PlusIcon } from 'lucide-react';
import { useState } from 'react';
import { Message } from './ChatWindow';
import Lightbox, { GenericSlide } from 'yet-another-react-lightbox';
import 'yet-another-react-lightbox/styles.css';
type Document = {
url: string;
title: string;
snippet: string;
source: string;
type: string;
iframe_src: string;
};
declare module 'yet-another-react-lightbox' {
export interface PDFSlide extends GenericSlide {
type: 'pdf';
url: string;
iframe_src: string;
}
interface SlideTypes {
'pdf': PDFSlide;
}
}
const LegalSearch = ({
query,
chatHistory,
}: {
query: string;
chatHistory: Message[];
}) => {
const [documents, setDocuments] = useState<Document[] | null>(null);
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const [currentDoc, setCurrentDoc] = useState<Document | null>(null);
const openDocument = (doc: Document) => {
setCurrentDoc(doc);
setOpen(true);
};
return (
<>
{!loading && documents === null && (
<button
onClick={async () => {
setLoading(true);
const chatModelProvider = localStorage.getItem('chatModelProvider');
const chatModel = localStorage.getItem('chatModel');
const customOpenAIBaseURL = localStorage.getItem('openAIBaseURL');
const customOpenAIKey = localStorage.getItem('openAIApiKey');
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/legal`,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query: query,
chatHistory: chatHistory,
chatModel: {
provider: chatModelProvider,
model: chatModel,
...(chatModelProvider === 'custom_openai' && {
customOpenAIBaseURL: customOpenAIBaseURL,
customOpenAIKey: customOpenAIKey,
}),
},
}),
},
);
const data = await res.json();
setDocuments(data.documents ?? []);
setLoading(false);
}}
className="border border-dashed border-light-200 dark:border-dark-200 hover:bg-light-200 dark:hover:bg-dark-200 active:scale-95 duration-200 transition px-4 py-2 flex flex-row items-center justify-between rounded-lg dark:text-white text-sm w-full"
>
<div className="flex flex-row items-center space-x-2">
<BookCopy size={17} />
<p>Rechercher des textes légaux</p>
</div>
<PlusIcon className="text-[#24A0ED]" size={17} />
</button>
)}
{loading && (
<div className="flex flex-col space-y-2">
{[...Array(3)].map((_, i) => (
<div
key={i}
className="bg-light-secondary dark:bg-dark-secondary h-24 w-full rounded-lg animate-pulse"
/>
))}
</div>
)}
{documents !== null && documents.length > 0 && (
<>
<div className="flex flex-col space-y-2">
{documents.length > 4
? documents.slice(0, 3).map((doc, i) => (
<div
key={i}
onClick={() => openDocument(doc)}
className="bg-light-100 dark:bg-dark-100 p-3 rounded-lg hover:bg-light-200 dark:hover:bg-dark-200 transition duration-200 cursor-pointer"
>
<h4 className="text-sm font-medium text-black dark:text-white line-clamp-2">
{doc.title}
</h4>
<p className="text-xs text-black/50 dark:text-white/50 mt-1 line-clamp-2">
{doc.snippet}
</p>
<div className="flex items-center space-x-2 mt-2">
<span className="text-xs text-black/30 dark:text-white/30">
{doc.source}
</span>
<span className="text-xs bg-light-secondary dark:bg-dark-secondary px-1.5 py-0.5 rounded text-black/50 dark:text-white/50">
{doc.type}
</span>
</div>
</div>
))
: documents.map((doc, i) => (
<div
key={i}
onClick={() => openDocument(doc)}
className="bg-light-100 dark:bg-dark-100 p-3 rounded-lg hover:bg-light-200 dark:hover:bg-dark-200 transition duration-200 cursor-pointer"
>
<h4 className="text-sm font-medium text-black dark:text-white line-clamp-2">
{doc.title}
</h4>
<p className="text-xs text-black/50 dark:text-white/50 mt-1 line-clamp-2">
{doc.snippet}
</p>
<div className="flex items-center space-x-2 mt-2">
<span className="text-xs text-black/30 dark:text-white/30">
{doc.source}
</span>
<span className="text-xs bg-light-secondary dark:bg-dark-secondary px-1.5 py-0.5 rounded text-black/50 dark:text-white/50">
{doc.type}
</span>
</div>
</div>
))}
{documents.length > 4 && (
<button
onClick={() => openDocument(documents[3])}
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 p-3 rounded-lg text-black/70 dark:text-white/70 text-sm"
>
Voir {documents.length - 3} documents supplémentaires
</button>
)}
</div>
<Lightbox
open={open}
close={() => setOpen(false)}
render={{
slide: ({ slide }) =>
slide.type === 'pdf' ? (
<div className="h-full w-full flex flex-col items-center justify-center">
<div className="text-center mb-4 text-white">
<p>Le document ne peut pas être affiché directement.</p>
<a
href={slide.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-400 hover:text-blue-300 underline mt-2 inline-block"
>
Ouvrir le document dans un nouvel onglet
</a>
</div>
<div className="bg-white/10 p-4 rounded-lg max-w-2xl">
<h3 className="text-white/90 font-medium mb-2">{currentDoc?.title}</h3>
<p className="text-white/70 text-sm">{currentDoc?.snippet}</p>
</div>
</div>
) : null,
}}
slides={[
{
type: 'pdf',
url: currentDoc?.url || '',
iframe_src: currentDoc?.url || '',
}
]}
/>
</>
)}
</>
);
};
export default LegalSearch;