feat: add copy button in code snippet

This commit is contained in:
jang_yoonsu 2024-05-16 23:23:46 +09:00
parent 889a050f25
commit 3767d14e5c

View File

@ -1,15 +1,14 @@
import { forwardRef, useState } from 'react'; import { forwardRef, useState } from 'react';
import Avatar from '../components/Avatar'; import Avatar from '../components/Avatar';
import CoppyButton from '../components/CopyButton';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import { FEEDBACK, MESSAGE_TYPE } from './conversationModels'; import { FEEDBACK, MESSAGE_TYPE } from './conversationModels';
import classes from './ConversationBubble.module.css'; import classes from './ConversationBubble.module.css';
import Alert from './../assets/alert.svg'; import Alert from './../assets/alert.svg';
import Like from './../assets/like.svg?react'; import Like from './../assets/like.svg?react';
import Dislike from './../assets/dislike.svg?react'; import Dislike from './../assets/dislike.svg?react';
import Copy from './../assets/copy.svg?react';
import CheckMark from './../assets/checkmark.svg?react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import copy from 'copy-to-clipboard';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import DocsGPT3 from '../assets/cute_docsgpt3.svg'; import DocsGPT3 from '../assets/cute_docsgpt3.svg';
@ -30,29 +29,19 @@ const ConversationBubble = forwardRef<
ref, ref,
) { ) {
const [openSource, setOpenSource] = useState<number | null>(null); const [openSource, setOpenSource] = useState<number | null>(null);
const [copied, setCopied] = useState(false);
const handleCopyClick = (text: string) => {
copy(text);
setCopied(true);
// Reset copied to false after a few seconds
setTimeout(() => {
setCopied(false);
}, 3000);
};
const [isCopyHovered, setIsCopyHovered] = useState(false);
const [isLikeHovered, setIsLikeHovered] = useState(false); const [isLikeHovered, setIsLikeHovered] = useState(false);
const [isDislikeHovered, setIsDislikeHovered] = useState(false); const [isDislikeHovered, setIsDislikeHovered] = useState(false);
const [isLikeClicked, setIsLikeClicked] = useState(false); const [isLikeClicked, setIsLikeClicked] = useState(false);
const [isDislikeClicked, setIsDislikeClicked] = useState(false); const [isDislikeClicked, setIsDislikeClicked] = useState(false);
let bubble; let bubble;
if (type === 'QUESTION') { if (type === 'QUESTION') {
bubble = ( bubble = (
<div ref={ref} className={`flex flex-row-reverse self-end ${className}`}> <div ref={ref} className={`flex flex-row-reverse self-end ${className}`}>
<Avatar className="mt-2 text-2xl" avatar="🧑‍💻"></Avatar> <Avatar className="mt-2 text-2xl" avatar="🧑‍💻"></Avatar>
<div className="mr-2 ml-10 flex items-center rounded-3xl bg-purple-30 p-3.5 text-white"> <div className="ml-10 mr-2 flex items-center rounded-3xl bg-purple-30 p-3.5 text-white">
<ReactMarkdown className="whitespace-pre-wrap break-normal leading-normal"> <ReactMarkdown className="whitespace-pre-wrap break-normal leading-normal">
{message} {message}
</ReactMarkdown> </ReactMarkdown>
@ -94,19 +83,28 @@ const ConversationBubble = forwardRef<
code({ node, inline, className, children, ...props }) { code({ node, inline, className, children, ...props }) {
const match = /language-(\w+)/.exec(className || ''); const match = /language-(\w+)/.exec(className || '');
return !inline && match ? ( return (
<SyntaxHighlighter <div className="relative">
PreTag="div" {!inline && match ? (
language={match[1]} <SyntaxHighlighter
{...props} PreTag="div"
style={vscDarkPlus} language={match[1]}
> {...props}
{String(children).replace(/\n$/, '')} style={vscDarkPlus}
</SyntaxHighlighter> >
) : ( {String(children).replace(/\n$/, '')}
<code className={className ? className : ''} {...props}> </SyntaxHighlighter>
{children} ) : (
</code> <code className={className ? className : ''} {...props}>
{children}
</code>
)}
<div className="absolute right-3 top-3">
<CoppyButton
text={String(children).replace(/\n$/, '')}
/>
</div>
</div>
); );
}, },
ul({ children }) { ul({ children }) {
@ -172,7 +170,7 @@ const ConversationBubble = forwardRef<
{sources?.map((source, index) => ( {sources?.map((source, index) => (
<div <div
key={index} key={index}
className={`max-w-xs sm:max-w-sm md:max-w-md cursor-pointer rounded-[28px] py-1 px-4 ${ className={`max-w-xs cursor-pointer rounded-[28px] px-4 py-1 sm:max-w-sm md:max-w-md ${
openSource === index openSource === index
? 'bg-[#007DFF]' ? 'bg-[#007DFF]'
: 'bg-[#D7EBFD] hover:bg-[#BFE1FF]' : 'bg-[#D7EBFD] hover:bg-[#BFE1FF]'
@ -203,31 +201,7 @@ const ConversationBubble = forwardRef<
${type !== 'ERROR' ? 'group-hover:lg:visible' : ''}`} ${type !== 'ERROR' ? 'group-hover:lg:visible' : ''}`}
> >
<div className="absolute left-2 top-4"> <div className="absolute left-2 top-4">
<div <CoppyButton text={message} />
className={`flex items-center justify-center rounded-full p-2
${
isCopyHovered
? 'bg-[#EEEEEE] dark:bg-purple-taupe'
: 'bg-[#ffffff] dark:bg-transparent'
}`}
>
{copied ? (
<CheckMark
className="cursor-pointer stroke-green-2000"
onMouseEnter={() => setIsCopyHovered(true)}
onMouseLeave={() => setIsCopyHovered(false)}
/>
) : (
<Copy
className={`cursor-pointer fill-none`}
onClick={() => {
handleCopyClick(message);
}}
onMouseEnter={() => setIsCopyHovered(true)}
onMouseLeave={() => setIsCopyHovered(false)}
></Copy>
)}
</div>
</div> </div>
</div> </div>
<div <div