feat: implement copy message

This commit is contained in:
shadcn 2023-06-13 23:58:59 +04:00
parent 320db5daeb
commit 8255f2d547
5 changed files with 86 additions and 40 deletions

View file

@ -0,0 +1,38 @@
'use client'
import { Button } from '@/components/ui/button'
import { IconCheck, IconCopy } from '@/components/ui/icons'
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
import { cn } from '@/lib/utils'
import { type Message } from 'ai-connector'
interface ChatMessageActionsProps extends React.ComponentProps<'div'> {
message: Message
}
export function ChatMessageActions({
message,
className,
...props
}: ChatMessageActionsProps) {
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
return (
<div
className={cn(
'flex items-center justify-end w-full md:absolute md:-top-2 md:-right-10 md:opacity-0 group-hover:opacity-100 transition-opacity',
className
)}
{...props}
>
<Button
variant="ghost"
size="icon"
onClick={() => copyToClipboard(message.content)}
>
{isCopied ? <IconCheck /> : <IconCopy />}
<span className="sr-only">Copy message</span>
</Button>
</div>
)
}

View file

@ -6,6 +6,7 @@ import { cn } from '@/lib/utils'
import { CodeBlock } from '@/components/ui/codeblock'
import { MemoizedReactMarkdown } from '@/components/markdown'
import { IconOpenAI, IconUser } from '@/components/ui/icons'
import { ChatMessageActions } from '@/components/chat-message-actions'
export interface ChatMessageProps {
message: Message
@ -13,7 +14,10 @@ export interface ChatMessageProps {
export function ChatMessage({ message, ...props }: ChatMessageProps) {
return (
<div className={cn('relative mb-4 flex items-start md:-ml-12')} {...props}>
<div
className={cn('relative mb-4 flex items-start group md:-ml-12')}
{...props}
>
<div
className={cn(
'flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border shadow',
@ -24,44 +28,49 @@ export function ChatMessage({ message, ...props }: ChatMessageProps) {
>
{message.role === 'user' ? <IconUser /> : <IconOpenAI />}
</div>
<div className="ml-4 px-1">
<MemoizedReactMarkdown
className="prose leading-6 dark:prose-invert prose-p:leading-[1.8rem] prose-pre:rounded-md prose-pre:bg-[#282c34]"
remarkPlugins={[remarkGfm, remarkMath]}
components={{
p({ children }) {
return <p className="mb-2 last:mb-0">{children}</p>
},
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == '▍') {
return (
<span className="mt-1 animate-pulse cursor-default"></span>
)
<div className="ml-4 px-1 space-y-2 flex-1">
<div>
<MemoizedReactMarkdown
className="prose leading-6 dark:prose-invert prose-p:leading-[1.8rem] prose-pre:rounded-md prose-pre:bg-[#282c34]"
remarkPlugins={[remarkGfm, remarkMath]}
components={{
p({ children }) {
return <p className="mb-2 last:mb-0">{children}</p>
},
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == '▍') {
return (
<span className="mt-1 animate-pulse cursor-default">
</span>
)
}
children[0] = (children[0] as string).replace('`▍`', '▍')
}
children[0] = (children[0] as string).replace('`▍`', '▍')
const match = /language-(\w+)/.exec(className || '')
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
const match = /language-(\w+)/.exec(className || '')
return !inline ? (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/>
) : (
<code className={className} {...props}>
{children}
</code>
)
}
}}
>
{message.content}
</MemoizedReactMarkdown>
}}
>
{message.content}
</MemoizedReactMarkdown>
</div>
<ChatMessageActions message={message} />
</div>
</div>
)

View file

@ -4,7 +4,7 @@ import { FC, memo } from 'react'
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-cliipboard'
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons'
interface Props {