chore: Update dependencies and fix import paths
This commit is contained in:
parent
3b737a078a
commit
81c5e30fda
46 changed files with 1626 additions and 371 deletions
|
@ -20,13 +20,13 @@ const Chat = ({
|
|||
rewrite: (messageId: string) => void;
|
||||
}) => {
|
||||
const [dividerWidth, setDividerWidth] = useState(0);
|
||||
const dividerRef = useRef<HTMLDivElement | null>(null);
|
||||
const dividerReference = useRef<HTMLDivElement | null>(null);
|
||||
const messageEnd = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const updateDividerWidth = () => {
|
||||
if (dividerRef.current) {
|
||||
setDividerWidth(dividerRef.current.scrollWidth);
|
||||
if (dividerReference.current) {
|
||||
setDividerWidth(dividerReference.current.scrollWidth);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -43,29 +43,29 @@ const Chat = ({
|
|||
messageEnd.current?.scrollIntoView({ behavior: "smooth" });
|
||||
|
||||
if (messages.length === 1) {
|
||||
document.title = `${messages[0].content.substring(0, 30)} - Perplexica`;
|
||||
document.title = `${messages[0].content.slice(0, 30)} - Perplexica`;
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-32 sm:mx-4 md:mx-8">
|
||||
{messages.map((msg, i) => {
|
||||
const isLast = i === messages.length - 1;
|
||||
{messages.map((message, index) => {
|
||||
const isLast = index === messages.length - 1;
|
||||
|
||||
return (
|
||||
<Fragment key={msg.messageId}>
|
||||
<Fragment key={message.messageId}>
|
||||
<MessageBox
|
||||
key={i}
|
||||
message={msg}
|
||||
messageIndex={i}
|
||||
key={index}
|
||||
message={message}
|
||||
messageIndex={index}
|
||||
history={messages}
|
||||
loading={loading}
|
||||
dividerRef={isLast ? dividerRef : undefined}
|
||||
dividerRef={isLast ? dividerReference : undefined}
|
||||
isLast={isLast}
|
||||
rewrite={rewrite}
|
||||
sendMessage={sendMessage}
|
||||
/>
|
||||
{!isLast && msg.role === "assistant" && (
|
||||
{!isLast && message.role === "assistant" && (
|
||||
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||
)}
|
||||
</Fragment>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Document } from "@langchain/core/documents";
|
|||
import Navbar from "./Navbar";
|
||||
import Chat from "./Chat";
|
||||
import EmptyChat from "./EmptyChat";
|
||||
import crypto from "crypto";
|
||||
import crypto from "node:crypto";
|
||||
import { toast } from "sonner";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { getSuggestions } from "@/lib/actions";
|
||||
|
@ -62,20 +62,20 @@ const useSocket = (url: string, setIsWSReady: (ready: boolean) => void, setError
|
|||
}
|
||||
|
||||
const wsURL = new URL(url);
|
||||
const searchParams = new URLSearchParams({});
|
||||
const searchParameters = new URLSearchParams({});
|
||||
|
||||
searchParams.append("chatModel", chatModel!);
|
||||
searchParams.append("chatModelProvider", chatModelProvider);
|
||||
searchParameters.append("chatModel", chatModel!);
|
||||
searchParameters.append("chatModelProvider", chatModelProvider);
|
||||
|
||||
if (chatModelProvider === "custom_openai") {
|
||||
searchParams.append("openAIApiKey", localStorage.getItem("openAIApiKey")!);
|
||||
searchParams.append("openAIBaseURL", localStorage.getItem("openAIBaseURL")!);
|
||||
searchParameters.append("openAIApiKey", localStorage.getItem("openAIApiKey")!);
|
||||
searchParameters.append("openAIBaseURL", localStorage.getItem("openAIBaseURL")!);
|
||||
}
|
||||
|
||||
searchParams.append("embeddingModel", embeddingModel!);
|
||||
searchParams.append("embeddingModelProvider", embeddingModelProvider);
|
||||
searchParameters.append("embeddingModel", embeddingModel!);
|
||||
searchParameters.append("embeddingModelProvider", embeddingModelProvider);
|
||||
|
||||
wsURL.search = searchParams.toString();
|
||||
wsURL.search = searchParameters.toString();
|
||||
|
||||
const ws = new WebSocket(wsURL.toString());
|
||||
|
||||
|
@ -85,26 +85,27 @@ const useSocket = (url: string, setIsWSReady: (ready: boolean) => void, setError
|
|||
setError(true);
|
||||
toast.error("Failed to connect to the server. Please try again later.");
|
||||
}
|
||||
}, 10000);
|
||||
}, 10_000);
|
||||
|
||||
ws.onopen = () => {
|
||||
ws.addEventListener("open", () => {
|
||||
console.log("[DEBUG] open");
|
||||
clearTimeout(timeoutId);
|
||||
setError(false);
|
||||
setIsWSReady(true);
|
||||
};
|
||||
});
|
||||
|
||||
// eslint-disable-next-line unicorn/prefer-add-event-listener
|
||||
ws.onerror = () => {
|
||||
clearTimeout(timeoutId);
|
||||
setError(true);
|
||||
toast.error("WebSocket connection error.");
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
ws.addEventListener("close", () => {
|
||||
clearTimeout(timeoutId);
|
||||
setError(true);
|
||||
console.log("[DEBUG] closed");
|
||||
};
|
||||
});
|
||||
|
||||
setWs(ws);
|
||||
};
|
||||
|
@ -144,17 +145,17 @@ const loadMessages = async (
|
|||
|
||||
const data = await res.json();
|
||||
|
||||
const messages = data.messages.map((msg: any) => {
|
||||
const messages = data.messages.map((message: any) => {
|
||||
return {
|
||||
...msg,
|
||||
...JSON.parse(msg.metadata),
|
||||
...message,
|
||||
...JSON.parse(message.metadata),
|
||||
};
|
||||
}) as Message[];
|
||||
|
||||
setMessages(messages);
|
||||
|
||||
const history = messages.map(msg => {
|
||||
return [msg.role, msg.content];
|
||||
const history = messages.map(message => {
|
||||
return [message.role, message.content];
|
||||
}) as [string, string][];
|
||||
|
||||
console.log("[DEBUG] messages loaded");
|
||||
|
@ -167,8 +168,8 @@ const loadMessages = async (
|
|||
};
|
||||
|
||||
const ChatWindow = ({ id }: { id?: string }) => {
|
||||
const searchParams = useSearchParams();
|
||||
const initialMessage = searchParams.get("q");
|
||||
const searchParameters = useSearchParams();
|
||||
const initialMessage = searchParameters.get("q");
|
||||
|
||||
const [chatId, setChatId] = useState<string | undefined>(id);
|
||||
const [newChatCreated, setNewChatCreated] = useState(false);
|
||||
|
@ -202,10 +203,10 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const messagesRef = useRef<Message[]>([]);
|
||||
const messagesReference = useRef<Message[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
messagesRef.current = messages;
|
||||
messagesReference.current = messages;
|
||||
}, [messages]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -219,7 +220,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
setLoading(true);
|
||||
setMessageAppeared(false);
|
||||
|
||||
let sources: Document[] | undefined = undefined;
|
||||
let sources: Document[] | undefined;
|
||||
let recievedMessage = "";
|
||||
let added = false;
|
||||
|
||||
|
@ -237,8 +238,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
}),
|
||||
);
|
||||
|
||||
setMessages(prevMessages => [
|
||||
...prevMessages,
|
||||
setMessages(previousMessages => [
|
||||
...previousMessages,
|
||||
{
|
||||
content: message,
|
||||
messageId: messageId,
|
||||
|
@ -260,8 +261,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
if (data.type === "sources") {
|
||||
sources = data.data;
|
||||
if (!added) {
|
||||
setMessages(prevMessages => [
|
||||
...prevMessages,
|
||||
setMessages(previousMessages => [
|
||||
...previousMessages,
|
||||
{
|
||||
content: "",
|
||||
messageId: data.messageId,
|
||||
|
@ -278,8 +279,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
|
||||
if (data.type === "message") {
|
||||
if (!added) {
|
||||
setMessages(prevMessages => [
|
||||
...prevMessages,
|
||||
setMessages(previousMessages => [
|
||||
...previousMessages,
|
||||
{
|
||||
content: data.data,
|
||||
messageId: data.messageId,
|
||||
|
@ -292,8 +293,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
added = true;
|
||||
}
|
||||
|
||||
setMessages(prev =>
|
||||
prev.map(message => {
|
||||
setMessages(previous =>
|
||||
previous.map(message => {
|
||||
if (message.messageId === data.messageId) {
|
||||
return { ...message, content: message.content + data.data };
|
||||
}
|
||||
|
@ -307,21 +308,27 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
}
|
||||
|
||||
if (data.type === "messageEnd") {
|
||||
setChatHistory(prevHistory => [...prevHistory, ["human", message], ["assistant", recievedMessage]]);
|
||||
setChatHistory(previousHistory => [...previousHistory, ["human", message], ["assistant", recievedMessage]]);
|
||||
|
||||
ws?.removeEventListener("message", messageHandler);
|
||||
setLoading(false);
|
||||
|
||||
const lastMsg = messagesRef.current[messagesRef.current.length - 1];
|
||||
const lastMessage = messagesReference.current.at(-1);
|
||||
|
||||
if (lastMsg.role === "assistant" && lastMsg.sources && lastMsg.sources.length > 0 && !lastMsg.suggestions) {
|
||||
const suggestions = await getSuggestions(messagesRef.current);
|
||||
setMessages(prev =>
|
||||
prev.map(msg => {
|
||||
if (msg.messageId === lastMsg.messageId) {
|
||||
return { ...msg, suggestions: suggestions };
|
||||
if (
|
||||
lastMessage &&
|
||||
lastMessage.role === "assistant" &&
|
||||
lastMessage.sources &&
|
||||
lastMessage.sources.length > 0 &&
|
||||
!lastMessage.suggestions
|
||||
) {
|
||||
const suggestions = await getSuggestions(messagesReference.current);
|
||||
setMessages(previous =>
|
||||
previous.map(message_ => {
|
||||
if (message_.messageId === lastMessage.messageId) {
|
||||
return { ...message_, suggestions: suggestions };
|
||||
}
|
||||
return msg;
|
||||
return message_;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
@ -332,17 +339,19 @@ const ChatWindow = ({ id }: { id?: string }) => {
|
|||
};
|
||||
|
||||
const rewrite = (messageId: string) => {
|
||||
const index = messages.findIndex(msg => msg.messageId === messageId);
|
||||
const index = messages.findIndex(message_ => message_.messageId === messageId);
|
||||
|
||||
if (index === -1) return;
|
||||
|
||||
const message = messages[index - 1];
|
||||
|
||||
setMessages(prev => {
|
||||
return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
|
||||
setMessages(previous => {
|
||||
// eslint-disable-next-line unicorn/no-useless-spread
|
||||
return [...previous.slice(0, messages.length > 2 ? index - 1 : 0)];
|
||||
});
|
||||
setChatHistory(prev => {
|
||||
return [...prev.slice(0, messages.length > 2 ? index - 1 : 0)];
|
||||
setChatHistory(previous => {
|
||||
// eslint-disable-next-line unicorn/no-useless-spread
|
||||
return [...previous.slice(0, messages.length > 2 ? index - 1 : 0)];
|
||||
});
|
||||
|
||||
sendMessage(message.content);
|
||||
|
|
|
@ -33,8 +33,9 @@ const DeleteChat = ({
|
|||
const newChats = chats.filter(chat => chat.id !== chatId);
|
||||
|
||||
setChats(newChats);
|
||||
} catch (err: any) {
|
||||
toast.error(err.message);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
} catch (error: any) {
|
||||
toast.error(error.message);
|
||||
} finally {
|
||||
setConfirmationDialogOpen(false);
|
||||
setLoading(false);
|
||||
|
|
|
@ -16,12 +16,12 @@ const EmptyChatMessageInput = ({
|
|||
const [copilotEnabled, setCopilotEnabled] = useState(false);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const inputReference = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "/") {
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
inputReference.current?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -51,7 +51,7 @@ const EmptyChatMessageInput = ({
|
|||
>
|
||||
<div className="flex flex-col bg-light-secondary dark:bg-dark-secondary px-5 pt-5 pb-2 rounded-lg w-full border border-light-200 dark:border-dark-200">
|
||||
<TextareaAutosize
|
||||
ref={inputRef}
|
||||
ref={inputReference}
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
minRows={2}
|
||||
|
|
|
@ -8,7 +8,8 @@ const Copy = ({ message, initialMessage }: { message: Message; initialMessage: s
|
|||
return (
|
||||
<button
|
||||
onClick={() => {
|
||||
const contentToCopy = `${initialMessage}${message.sources && message.sources.length > 0 && `\n\nCitations:\n${message.sources?.map((source: any, i: any) => `[${i + 1}] ${source.metadata.url}`).join(`\n`)}`}`;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const contentToCopy = `${initialMessage}${message.sources && message.sources.length > 0 && `\n\nCitations:\n${message.sources?.map((source: any, index: any) => `[${index + 1}] ${source.metadata.url}`).join(`\n`)}`}`;
|
||||
navigator.clipboard.writeText(contentToCopy);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 1000);
|
||||
|
|
|
@ -36,11 +36,11 @@ const MessageBox = ({
|
|||
const [speechMessage, setSpeechMessage] = useState(message.content);
|
||||
|
||||
useEffect(() => {
|
||||
const regex = /\[(\d+)\]/g;
|
||||
const regex = /\[(\d+)]/g;
|
||||
|
||||
if (message.role === "assistant" && message?.sources && message.sources.length > 0) {
|
||||
return setParsedMessage(
|
||||
message.content.replace(
|
||||
message.content.replaceAll(
|
||||
regex,
|
||||
(_, number) =>
|
||||
`<a href="${message.sources?.[number - 1]?.metadata?.url}" target="_blank" className="bg-light-secondary dark:bg-dark-secondary px-1 rounded ml-1 no-underline text-xs text-black/70 dark:text-white/70 relative">${number}</a>`,
|
||||
|
@ -48,7 +48,7 @@ const MessageBox = ({
|
|||
);
|
||||
}
|
||||
|
||||
setSpeechMessage(message.content.replace(regex, ""));
|
||||
setSpeechMessage(message.content.replaceAll(regex, ""));
|
||||
setParsedMessage(message.content);
|
||||
}, [message.content, message.sources, message.role]);
|
||||
|
||||
|
@ -128,8 +128,8 @@ const MessageBox = ({
|
|||
<h3 className="text-xl font-medium">Related</h3>
|
||||
</div>
|
||||
<div className="flex flex-col space-y-3">
|
||||
{message.suggestions.map((suggestion, i) => (
|
||||
<div className="flex flex-col space-y-3 text-sm" key={i}>
|
||||
{message.suggestions.map((suggestion, index) => (
|
||||
<div className="flex flex-col space-y-3 text-sm" key={index}>
|
||||
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
|
||||
<div
|
||||
onClick={() => {
|
||||
|
|
|
@ -19,12 +19,12 @@ const MessageInput = ({ sendMessage, loading }: { sendMessage: (message: string)
|
|||
}
|
||||
}, [textareaRows, mode, message]);
|
||||
|
||||
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
||||
const inputReference = useRef<HTMLTextAreaElement | null>(null);
|
||||
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "/") {
|
||||
e.preventDefault();
|
||||
inputRef.current?.focus();
|
||||
inputReference.current?.focus();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -58,11 +58,11 @@ const MessageInput = ({ sendMessage, loading }: { sendMessage: (message: string)
|
|||
>
|
||||
{mode === "single" && <Attach />}
|
||||
<TextareaAutosize
|
||||
ref={inputRef}
|
||||
ref={inputReference}
|
||||
value={message}
|
||||
onChange={e => setMessage(e.target.value)}
|
||||
onHeightChange={(height, props) => {
|
||||
setTextareaRows(Math.ceil(height / props.rowHeight));
|
||||
onHeightChange={(height, properties) => {
|
||||
setTextareaRows(Math.ceil(height / properties.rowHeight));
|
||||
}}
|
||||
className="transition bg-transparent dark:placeholder:text-white/50 placeholder:text-sm text-sm dark:text-white resize-none focus:outline-none w-full px-2 max-h-24 lg:max-h-36 xl:max-h-48 flex-grow flex-shrink"
|
||||
placeholder="Ask a follow-up"
|
||||
|
|
|
@ -54,14 +54,14 @@ const Focus = ({ focusMode, setFocusMode }: { focusMode: string; setFocusMode: (
|
|||
type="button"
|
||||
className="p-2 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
|
||||
>
|
||||
{focusMode !== "webSearch" ? (
|
||||
{focusMode === "webSearch" ? (
|
||||
<ScanEye />
|
||||
) : (
|
||||
<div className="flex flex-row items-center space-x-1">
|
||||
{focusModes.find(mode => mode.key === focusMode)?.icon}
|
||||
<p className="text-xs font-medium">{focusModes.find(mode => mode.key === focusMode)?.title}</p>
|
||||
<ChevronDown size={20} />
|
||||
</div>
|
||||
) : (
|
||||
<ScanEye />
|
||||
)}
|
||||
</Popover.Button>
|
||||
<Transition
|
||||
|
@ -75,10 +75,10 @@ const Focus = ({ focusMode, setFocusMode }: { focusMode: string; setFocusMode: (
|
|||
>
|
||||
<Popover.Panel className="absolute z-10 w-full">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-1 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-2 max-h-[200px] md:max-h-none overflow-y-auto">
|
||||
{focusModes.map((mode, i) => (
|
||||
{focusModes.map((mode, index) => (
|
||||
<Popover.Button
|
||||
onClick={() => setFocusMode(mode.key)}
|
||||
key={i}
|
||||
key={index}
|
||||
className={cn(
|
||||
"p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition",
|
||||
focusMode === mode.key
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { Document } from "@langchain/core/documents";
|
||||
import { Fragment, useState } from "react";
|
||||
|
@ -18,12 +17,13 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||
|
||||
return (
|
||||
<div className="grid grid-cols-2 lg:grid-cols-4 gap-2">
|
||||
{sources.slice(0, 3).map((source, i) => (
|
||||
{sources.slice(0, 3).map((source, index) => (
|
||||
<a
|
||||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||
key={i}
|
||||
key={index}
|
||||
href={source.metadata.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{source.metadata.title}
|
||||
|
@ -38,12 +38,12 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||
className="rounded-lg h-4 w-4"
|
||||
/>
|
||||
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{source.metadata.url.replace(/.+\/\/|www.|\..+/g, "")}
|
||||
{source.metadata.url.replaceAll(/.+\/\/|www.|\..+/g, "")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
||||
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
||||
<span>{i + 1}</span>
|
||||
<span>{index + 1}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
@ -54,14 +54,14 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||
>
|
||||
<div className="flex flex-row items-center space-x-1">
|
||||
{sources.slice(3, 6).map((source, i) => (
|
||||
{sources.slice(3, 6).map((source, index) => (
|
||||
<img
|
||||
src={`https://s2.googleusercontent.com/s2/favicons?domain_url=${source.metadata.url}`}
|
||||
width={16}
|
||||
height={16}
|
||||
alt="favicon"
|
||||
className="rounded-lg h-4 w-4"
|
||||
key={i}
|
||||
key={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
@ -84,12 +84,13 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||
<Dialog.Panel className="w-full max-w-md transform rounded-2xl bg-light-secondary dark:bg-dark-secondary border border-light-200 dark:border-dark-200 p-6 text-left align-middle shadow-xl transition-all">
|
||||
<Dialog.Title className="text-lg font-medium leading-6 dark:text-white">Sources</Dialog.Title>
|
||||
<div className="grid grid-cols-2 gap-2 overflow-auto max-h-[300px] mt-2 pr-2">
|
||||
{sources.map((source, i) => (
|
||||
{sources.map((source, index) => (
|
||||
<a
|
||||
className="bg-light-secondary hover:bg-light-200 dark:bg-dark-secondary dark:hover:bg-dark-200 border border-light-200 dark:border-dark-200 transition duration-200 rounded-lg p-3 flex flex-col space-y-2 font-medium"
|
||||
key={i}
|
||||
key={index}
|
||||
href={source.metadata.url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<p className="dark:text-white text-xs overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{source.metadata.title}
|
||||
|
@ -104,12 +105,12 @@ const MessageSources = ({ sources }: { sources: Document[] }) => {
|
|||
className="rounded-lg h-4 w-4"
|
||||
/>
|
||||
<p className="text-xs text-black/50 dark:text-white/50 overflow-hidden whitespace-nowrap text-ellipsis">
|
||||
{source.metadata.url.replace(/.+\/\/|www.|\..+/g, "")}
|
||||
{source.metadata.url.replaceAll(/.+\/\/|www.|\..+/g, "")}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-row items-center space-x-1 text-black/50 dark:text-white/50 text-xs">
|
||||
<div className="bg-black/50 dark:bg-white/50 h-[4px] w-[4px] rounded-full" />
|
||||
<span>{i + 1}</span>
|
||||
<span>{index + 1}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
|
|
|
@ -10,7 +10,7 @@ const Navbar = ({ messages }: { messages: Message[] }) => {
|
|||
useEffect(() => {
|
||||
if (messages.length > 0) {
|
||||
const newTitle =
|
||||
messages[0].content.length > 20 ? `${messages[0].content.substring(0, 20).trim()}...` : messages[0].content;
|
||||
messages[0].content.length > 20 ? `${messages[0].content.slice(0, 20).trim()}...` : messages[0].content;
|
||||
setTitle(newTitle);
|
||||
const newTimeAgo = formatTimeDifference(new Date(), messages[0].createdAt);
|
||||
setTimeAgo(newTimeAgo);
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import { ImagesIcon, PlusIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import Lightbox from "yet-another-react-lightbox";
|
||||
|
@ -15,6 +14,7 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
const [images, setImages] = useState<Image[] | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const [slides, setSlides] = useState<any[]>([]);
|
||||
|
||||
return (
|
||||
|
@ -64,9 +64,9 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
)}
|
||||
{loading && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<div
|
||||
key={i}
|
||||
key={index}
|
||||
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
||||
/>
|
||||
))}
|
||||
|
@ -76,25 +76,25 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
<>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{images.length > 4
|
||||
? images.slice(0, 3).map((image, i) => (
|
||||
? images.slice(0, 3).map((image, index) => (
|
||||
<img
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
||||
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||
}}
|
||||
key={i}
|
||||
key={index}
|
||||
src={image.img_src}
|
||||
alt={image.title}
|
||||
className="h-full w-full aspect-video object-cover rounded-lg transition duration-200 active:scale-95 hover:scale-[1.02] cursor-zoom-in"
|
||||
/>
|
||||
))
|
||||
: images.map((image, i) => (
|
||||
: images.map((image, index) => (
|
||||
<img
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
||||
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||
}}
|
||||
key={i}
|
||||
key={index}
|
||||
src={image.img_src}
|
||||
alt={image.title}
|
||||
className="h-full w-full aspect-video object-cover rounded-lg transition duration-200 active:scale-95 hover:scale-[1.02] cursor-zoom-in"
|
||||
|
@ -106,9 +106,9 @@ const SearchImages = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
||||
>
|
||||
<div className="flex flex-row items-center space-x-1">
|
||||
{images.slice(3, 6).map((image, i) => (
|
||||
{images.slice(3, 6).map((image, index) => (
|
||||
<img
|
||||
key={i}
|
||||
key={index}
|
||||
src={image.img_src}
|
||||
alt={image.title}
|
||||
className="h-6 w-12 rounded-md lg:h-3 lg:w-6 lg:rounded-sm aspect-video object-cover"
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
/* eslint-disable @next/next/no-img-element */
|
||||
import { PlayCircle, PlayIcon, PlusIcon, VideoIcon } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import Lightbox, { GenericSlide, VideoSlide } from "yet-another-react-lightbox";
|
||||
|
@ -79,9 +78,9 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
)}
|
||||
{loading && (
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<div
|
||||
key={i}
|
||||
key={index}
|
||||
className="bg-light-secondary dark:bg-dark-secondary h-32 w-full rounded-lg animate-pulse aspect-video object-cover"
|
||||
/>
|
||||
))}
|
||||
|
@ -91,14 +90,14 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
<>
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
{videos.length > 4
|
||||
? videos.slice(0, 3).map((video, i) => (
|
||||
? videos.slice(0, 3).map((video, index) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
||||
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||
}}
|
||||
className="relative transition duration-200 active:scale-95 hover:scale-[1.02] cursor-pointer"
|
||||
key={i}
|
||||
key={index}
|
||||
>
|
||||
<img
|
||||
src={video.img_src}
|
||||
|
@ -111,14 +110,14 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
</div>
|
||||
</div>
|
||||
))
|
||||
: videos.map((video, i) => (
|
||||
: videos.map((video, index) => (
|
||||
<div
|
||||
onClick={() => {
|
||||
setOpen(true);
|
||||
setSlides([slides[i], ...slides.slice(0, i), ...slides.slice(i + 1)]);
|
||||
setSlides([slides[index], ...slides.slice(0, index), ...slides.slice(index + 1)]);
|
||||
}}
|
||||
className="relative transition duration-200 active:scale-95 hover:scale-[1.02] cursor-pointer"
|
||||
key={i}
|
||||
key={index}
|
||||
>
|
||||
<img
|
||||
src={video.img_src}
|
||||
|
@ -137,9 +136,9 @@ const Searchvideos = ({ query, chat_history }: { query: string; chat_history: Me
|
|||
className="bg-light-100 hover:bg-light-200 dark:bg-dark-100 dark:hover:bg-dark-200 transition duration-200 active:scale-95 hover:scale-[1.02] h-auto w-full rounded-lg flex flex-col justify-between text-white p-2"
|
||||
>
|
||||
<div className="flex flex-row items-center space-x-1">
|
||||
{videos.slice(3, 6).map((video, i) => (
|
||||
{videos.slice(3, 6).map((video, index) => (
|
||||
<img
|
||||
key={i}
|
||||
key={index}
|
||||
src={video.img_src}
|
||||
alt={video.title}
|
||||
className="h-6 w-12 rounded-md lg:h-3 lg:w-6 lg:rounded-sm aspect-video object-cover"
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
/* eslint-disable unicorn/no-nested-ternary */
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { CloudUpload, RefreshCcw, RefreshCw } from "lucide-react";
|
||||
import React, { Fragment, useEffect, useState, type SelectHTMLAttributes } from "react";
|
||||
import ThemeSwitcher from "./theme/Switcher";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
interface InputProperties extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = ({ className, ...restProps }: InputProps) => {
|
||||
const Input = ({ className, ...restProperties }: InputProperties) => {
|
||||
return (
|
||||
<input
|
||||
{...restProps}
|
||||
{...restProperties}
|
||||
className={cn(
|
||||
"bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm",
|
||||
className,
|
||||
|
@ -18,14 +19,14 @@ const Input = ({ className, ...restProps }: InputProps) => {
|
|||
);
|
||||
};
|
||||
|
||||
interface SelectProps extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||
interface SelectProperties extends SelectHTMLAttributes<HTMLSelectElement> {
|
||||
options: { value: string; label: string; disabled?: boolean }[];
|
||||
}
|
||||
|
||||
export const Select = ({ className, options, ...restProps }: SelectProps) => {
|
||||
export const Select = ({ className, options, ...restProperties }: SelectProperties) => {
|
||||
return (
|
||||
<select
|
||||
{...restProps}
|
||||
{...restProperties}
|
||||
className={cn(
|
||||
"bg-light-secondary dark:bg-dark-secondary px-3 py-2 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 dark:text-white rounded-lg text-sm",
|
||||
className,
|
||||
|
@ -129,8 +130,8 @@ const SettingsDialog = ({ isOpen, setIsOpen }: { isOpen: boolean; setIsOpen: (is
|
|||
localStorage.setItem("embeddingModel", selectedEmbeddingModel!);
|
||||
localStorage.setItem("openAIApiKey", customOpenAIApiKey!);
|
||||
localStorage.setItem("openAIBaseURL", customOpenAIBaseURL!);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
setIsOpen(false);
|
||||
|
|
|
@ -46,9 +46,9 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
|||
<SquarePen className="cursor-pointer" />
|
||||
</a>
|
||||
<VerticalIconContainer>
|
||||
{navLinks.map((link, i) => (
|
||||
{navLinks.map((link, index) => (
|
||||
<Link
|
||||
key={i}
|
||||
key={index}
|
||||
href={link.href}
|
||||
className={cn(
|
||||
"relative flex flex-row items-center justify-center cursor-pointer hover:bg-black/10 dark:hover:bg-white/10 duration-150 transition w-full py-2 rounded-lg",
|
||||
|
@ -70,10 +70,10 @@ const Sidebar = ({ children }: { children: React.ReactNode }) => {
|
|||
</div>
|
||||
|
||||
<div className="fixed bottom-0 w-full z-50 flex flex-row items-center gap-x-6 bg-light-primary dark:bg-dark-primary px-4 py-4 shadow-sm lg:hidden">
|
||||
{navLinks.map((link, i) => (
|
||||
{navLinks.map((link, index) => (
|
||||
<Link
|
||||
href={link.href}
|
||||
key={i}
|
||||
key={index}
|
||||
className={cn(
|
||||
"relative flex flex-col items-center space-y-1 text-center w-full",
|
||||
link.active ? "text-black dark:text-white" : "text-black dark:text-white/70",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue