mirror of
https://github.com/arc53/DocsGPT
synced 2024-11-19 21:25:39 +00:00
feat: add copy button in code snippet
This commit is contained in:
parent
889a050f25
commit
3767d14e5c
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user