Perplexica/ui/components/MessageBox.tsx

161 lines
7 KiB
TypeScript
Raw Normal View History

2024-07-05 14:36:50 +08:00
"use client";
2024-07-05 14:36:50 +08:00
import React, { MutableRefObject, useEffect, useState } from "react";
import { Message } from "./ChatWindow";
import { cn } from "@/lib/utils";
import { BookCopy, Disc3, Volume2, StopCircle, Layers3, Plus } from "lucide-react";
import Markdown from "markdown-to-jsx";
import Copy from "./MessageActions/Copy";
import Rewrite from "./MessageActions/Rewrite";
import MessageSources from "./MessageSources";
import SearchImages from "./SearchImages";
import SearchVideos from "./SearchVideos";
import { useSpeech } from "react-text-to-speech";
2024-04-09 16:21:05 +05:30
const MessageBox = ({
message,
messageIndex,
history,
loading,
dividerRef,
isLast,
rewrite,
2024-05-18 13:11:15 +05:30
sendMessage,
2024-04-09 16:21:05 +05:30
}: {
message: Message;
messageIndex: number;
history: Message[];
loading: boolean;
dividerRef?: MutableRefObject<HTMLDivElement | null>;
isLast: boolean;
rewrite: (messageId: string) => void;
2024-05-18 13:11:15 +05:30
sendMessage: (message: string) => void;
2024-04-09 16:21:05 +05:30
}) => {
const [parsedMessage, setParsedMessage] = useState(message.content);
const [speechMessage, setSpeechMessage] = useState(message.content);
2024-04-09 16:21:05 +05:30
useEffect(() => {
const regex = /\[(\d+)]/g;
2024-05-04 10:48:42 +05:30
2024-07-05 14:36:50 +08:00
if (message.role === "assistant" && message?.sources && message.sources.length > 0) {
2024-04-09 16:21:05 +05:30
return setParsedMessage(
message.content.replaceAll(
2024-04-09 16:21:05 +05:30
regex,
(_, number) =>
2024-05-27 11:49:09 +08:00
`<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>`,
2024-04-09 16:21:05 +05:30
),
);
}
2024-05-04 10:48:42 +05:30
setSpeechMessage(message.content.replaceAll(regex, ""));
2024-04-09 16:21:05 +05:30
setParsedMessage(message.content);
}, [message.content, message.sources, message.role]);
const { speechStatus, start, stop } = useSpeech({ text: speechMessage });
2024-04-09 16:21:05 +05:30
return (
<div>
2024-07-05 14:36:50 +08:00
{message.role === "user" && (
<div className={cn("w-full", messageIndex === 0 ? "pt-16" : "pt-8")}>
<h2 className="text-black dark:text-white font-medium text-3xl lg:w-9/12">{message.content}</h2>
2024-04-09 16:21:05 +05:30
</div>
)}
2024-07-05 14:36:50 +08:00
{message.role === "assistant" && (
2024-04-09 16:21:05 +05:30
<div className="flex flex-col space-y-9 lg:space-y-0 lg:flex-row lg:justify-between lg:space-x-9">
2024-07-05 14:36:50 +08:00
<div ref={dividerRef} className="flex flex-col space-y-6 w-full lg:w-9/12">
2024-04-09 16:21:05 +05:30
{message.sources && message.sources.length > 0 && (
<div className="flex flex-col space-y-2">
<div className="flex flex-row items-center space-x-2">
2024-05-24 20:29:49 +08:00
<BookCopy className="text-black dark:text-white" size={20} />
2024-07-05 14:36:50 +08:00
<h3 className="text-black dark:text-white font-medium text-xl">Sources</h3>
2024-04-09 16:21:05 +05:30
</div>
<MessageSources sources={message.sources} />
</div>
)}
<div className="flex flex-col space-y-2">
<div className="flex flex-row items-center space-x-2">
<Disc3
2024-07-05 14:36:50 +08:00
className={cn("text-black dark:text-white", isLast && loading ? "animate-spin" : "animate-none")}
2024-04-09 16:21:05 +05:30
size={20}
/>
2024-07-05 14:36:50 +08:00
<h3 className="text-black dark:text-white font-medium text-xl">Answer</h3>
2024-04-09 16:21:05 +05:30
</div>
<Markdown
className={cn(
2024-07-05 14:36:50 +08:00
"prose dark:prose-invert prose-p:leading-relaxed prose-pre:p-0",
"max-w-none break-words text-black dark:text-white text-sm md:text-base font-medium",
)}
>
2024-04-09 16:21:05 +05:30
{parsedMessage}
</Markdown>
2024-05-04 10:48:42 +05:30
{loading && isLast ? null : (
2024-05-24 20:29:49 +08:00
<div className="flex flex-row items-center justify-between w-full text-black dark:text-white py-4 -mx-2">
2024-04-09 16:21:05 +05:30
<div className="flex flex-row items-center space-x-1">
2024-05-27 11:49:09 +08:00
{/* <button className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black text-black dark:hover:text-white">
2024-04-09 16:21:05 +05:30
<Share size={18} />
2024-05-18 13:11:15 +05:30
</button> */}
2024-06-29 11:09:51 +05:30
<Rewrite rewrite={rewrite} messageId={message.messageId} />
2024-04-09 16:21:05 +05:30
</div>
<div className="flex flex-row items-center space-x-1">
<Copy initialMessage={message.content} message={message} />
<button
onClick={() => {
2024-07-05 14:36:50 +08:00
if (speechStatus === "started") {
stop();
} else {
start();
}
}}
2024-05-27 11:49:09 +08:00
className="p-2 text-black/70 dark:text-white/70 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary transition duration-200 hover:text-black dark:hover:text-white"
>
2024-07-05 14:36:50 +08:00
{speechStatus === "started" ? <StopCircle size={18} /> : <Volume2 size={18} />}
2024-04-09 16:21:05 +05:30
</button>
</div>
</div>
)}
2024-05-18 13:11:15 +05:30
{isLast &&
message.suggestions &&
message.suggestions.length > 0 &&
2024-07-05 14:36:50 +08:00
message.role === "assistant" &&
2024-05-18 13:11:15 +05:30
!loading && (
<>
2024-05-27 11:49:09 +08:00
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
2024-05-24 20:29:49 +08:00
<div className="flex flex-col space-y-3 text-black dark:text-white">
2024-05-18 13:11:15 +05:30
<div className="flex flex-row items-center space-x-2 mt-4">
<Layers3 />
<h3 className="text-xl font-medium">Related</h3>
</div>
<div className="flex flex-col space-y-3">
{message.suggestions.map((suggestion, index) => (
<div className="flex flex-col space-y-3 text-sm" key={index}>
2024-05-27 11:49:09 +08:00
<div className="h-px w-full bg-light-secondary dark:bg-dark-secondary" />
2024-05-18 13:11:15 +05:30
<div
onClick={() => {
sendMessage(suggestion);
}}
className="cursor-pointer flex flex-row justify-between font-medium space-x-2 items-center"
>
2024-07-05 14:36:50 +08:00
<p className="transition duration-200 hover:text-[#24A0ED]">{suggestion}</p>
<Plus size={20} className="text-[#24A0ED] flex-shrink-0" />
2024-05-18 13:11:15 +05:30
</div>
</div>
))}
</div>
</div>
</>
)}
2024-04-09 16:21:05 +05:30
</div>
</div>
<div className="lg:sticky lg:top-20 flex flex-col items-center space-y-3 w-full lg:w-3/12 z-30 h-full pb-4">
2024-07-05 14:36:50 +08:00
<SearchImages query={history[messageIndex - 1].content} chat_history={history.slice(0, messageIndex - 1)} />
<SearchVideos chat_history={history.slice(0, messageIndex - 1)} query={history[messageIndex - 1].content} />
2024-04-09 16:21:05 +05:30
</div>
</div>
)}
</div>
);
};
export default MessageBox;