next-chat / components /message-actions.tsx
NeoPy's picture
Upload folder using huggingface_hub
867b17d verified
raw
history blame
4.68 kB
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 (
<Actions>
<Action
tooltip="Copy"
onClick={async () => {
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!');
}}
>
<CopyIcon />
</Action>
<Action
tooltip="Upvote Response"
data-testid="message-upvote"
disabled={vote?.isUpvoted}
onClick={async () => {
const upvote = fetch('/api/vote', {
method: 'PATCH',
body: JSON.stringify({
chatId,
messageId: message.id,
type: 'up',
}),
});
toast.promise(upvote, {
loading: 'Upvoting Response...',
success: () => {
mutate<Array<Vote>>(
`/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.',
});
}}
>
<ThumbUpIcon />
</Action>
<Action
tooltip="Downvote Response"
data-testid="message-downvote"
disabled={vote && !vote.isUpvoted}
onClick={async () => {
const downvote = fetch('/api/vote', {
method: 'PATCH',
body: JSON.stringify({
chatId,
messageId: message.id,
type: 'down',
}),
});
toast.promise(downvote, {
loading: 'Downvoting Response...',
success: () => {
mutate<Array<Vote>>(
`/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.',
});
}}
>
<ThumbDownIcon />
</Action>
</Actions>
);
}
export const MessageActions = memo(
PureMessageActions,
(prevProps, nextProps) => {
if (!equal(prevProps.vote, nextProps.vote)) return false;
if (prevProps.isLoading !== nextProps.isLoading) return false;
return true;
},
);