import { useSWRConfig } from 'swr'; import { useCopyToClipboard } from 'usehooks-ts'; import type { Vote } from '@/lib/db/schema'; import { CopyIcon, ThumbDownIcon, ThumbUpIcon } from './icons'; import { Actions, Action } from './elements/actions'; import { memo } from 'react'; import equal from 'fast-deep-equal'; import { toast } from 'sonner'; import type { ChatMessage } from '@/lib/types'; export function PureMessageActions({ chatId, message, vote, isLoading, }: { chatId: string; message: ChatMessage; vote: Vote | undefined; isLoading: boolean; }) { const { mutate } = useSWRConfig(); const [_, copyToClipboard] = useCopyToClipboard(); if (isLoading) return null; if (message.role === 'user') return null; return ( { const textFromParts = message.parts ?.filter((part) => part.type === 'text') .map((part) => part.text) .join('\n') .trim(); if (!textFromParts) { toast.error("There's no text to copy!"); return; } await copyToClipboard(textFromParts); toast.success('Copied to clipboard!'); }} > { const upvote = fetch('/api/vote', { method: 'PATCH', body: JSON.stringify({ chatId, messageId: message.id, type: 'up', }), }); toast.promise(upvote, { loading: 'Upvoting Response...', success: () => { mutate>( `/api/vote?chatId=${chatId}`, (currentVotes) => { if (!currentVotes) return []; const votesWithoutCurrent = currentVotes.filter( (vote) => vote.messageId !== message.id, ); return [ ...votesWithoutCurrent, { chatId, messageId: message.id, isUpvoted: true, }, ]; }, { revalidate: false }, ); return 'Upvoted Response!'; }, error: 'Failed to upvote response.', }); }} > { const downvote = fetch('/api/vote', { method: 'PATCH', body: JSON.stringify({ chatId, messageId: message.id, type: 'down', }), }); toast.promise(downvote, { loading: 'Downvoting Response...', success: () => { mutate>( `/api/vote?chatId=${chatId}`, (currentVotes) => { if (!currentVotes) return []; const votesWithoutCurrent = currentVotes.filter( (vote) => vote.messageId !== message.id, ); return [ ...votesWithoutCurrent, { chatId, messageId: message.id, isUpvoted: false, }, ]; }, { revalidate: false }, ); return 'Downvoted Response!'; }, error: 'Failed to downvote response.', }); }} > ); } export const MessageActions = memo( PureMessageActions, (prevProps, nextProps) => { if (!equal(prevProps.vote, nextProps.vote)) return false; if (prevProps.isLoading !== nextProps.isLoading) return false; return true; }, );