import { Avatar, Box, HStack, Menu, MenuButton, MenuDivider, MenuGroup, MenuItem, MenuList, SimpleGrid, useBreakpointValue, useColorModeValue, useDisclosure, useToast, } from "@chakra-ui/react"; import { boolean } from "boolean"; import { ClipboardList, Copy, Flag, Link, MessageSquare, MoreHorizontal, Slash, Trash, User } from "lucide-react"; import { useRouter } from "next/router"; import { useTranslation } from "next-i18next"; import { useCallback, useEffect, useMemo, useState } from "react"; import { LabelMessagePopup } from "src/components/Messages/LabelPopup"; import { MessageEmojiButton } from "src/components/Messages/MessageEmojiButton"; import { ReportPopup } from "src/components/Messages/ReportPopup"; import { useHasRole } from "src/hooks/auth/useHasRole"; import { del, post, put } from "src/lib/api"; import { colors } from "src/styles/Theme/colors"; import { Message, MessageEmojis } from "src/types/Conversation"; import { emojiIcons, isKnownEmoji } from "src/types/Emoji"; import { mutate } from "swr"; import useSWRMutation from "swr/mutation"; interface MessageTableEntryProps { message: Message; enabled?: boolean; highlight?: boolean; } export function MessageTableEntry({ message, enabled, highlight }: MessageTableEntryProps) { const router = useRouter(); const [emojiState, setEmojis] = useState({ emojis: {}, user_emojis: [] }); useEffect(() => { const emojis = { ...message.emojis }; emojis["+1"] = emojis["+1"] || 0; emojis["-1"] = emojis["-1"] || 0; setEmojis({ emojis: emojis, user_emojis: message?.user_emojis || [], }); }, [message.emojis, message.user_emojis]); const goToMessage = useCallback(() => router.push(`/messages/${message.id}`), [router, message.id]); const { isOpen: reportPopupOpen, onOpen: showReportPopup, onClose: closeReportPopup } = useDisclosure(); const { isOpen: labelPopupOpen, onOpen: showLabelPopup, onClose: closeLabelPopup } = useDisclosure(); const backgroundColor = useColorModeValue("gray.100", "gray.700"); const backgroundColor2 = useColorModeValue("#DFE8F1", "#42536B"); const borderColor = useColorModeValue("blackAlpha.200", "whiteAlpha.200"); const inlineAvatar = useBreakpointValue({ base: true, sm: false }); const avatar = useMemo( () => ( ), [borderColor, inlineAvatar, message.is_assistant] ); const highlightColor = useColorModeValue(colors.light.active, colors.dark.active); const { trigger: sendEmojiChange } = useSWRMutation(`/api/messages/${message.id}/emoji`, post, { onSuccess: (data) => { data.emojis["+1"] = data.emojis["+1"] || 0; data.emojis["-1"] = data.emojis["-1"] || 0; setEmojis(data); }, }); const react = (emoji: string, state: boolean) => { sendEmojiChange({ op: state ? "add" : "remove", emoji }); }; return ( {!inlineAvatar && avatar} {inlineAvatar && avatar} {message.text} e.stopPropagation()} > {Object.entries(emojiState.emojis) .filter(([emoji]) => isKnownEmoji(emoji)) .sort(([emoji]) => -emoji) .map(([emoji, count]) => { return ( 0} userIsAuthor={message.user_is_author} onClick={() => react(emoji, !emojiState.user_emojis.includes(emoji))} /> ); })} ); } const EmojiMenuItem = ({ emoji, checked, react, }: { emoji: string; checked?: boolean; react: (emoji: string, state: boolean) => void; }) => { const activeColor = useColorModeValue(colors.light.active, colors.dark.active); const EmojiIcon = emojiIcons.get(emoji); return ( react(emoji, !checked)} justifyContent="center" color={checked ? activeColor : undefined}> ); }; const CHAR_COUNT = 10; const MessageActions = ({ react, userEmoji, onLabel, onReport, message, }: { react: (emoji: string, state: boolean) => void; userEmoji: string[]; onLabel: () => void; onReport: () => void; message: Message; }) => { const toast = useToast(); const { t } = useTranslation(["message", "common"]); const { id } = message; const { trigger: deleteMessage } = useSWRMutation(`/api/admin/delete_message/${id}`, del); const { trigger: stopTree } = useSWRMutation(`/api/admin/stop_tree/${id}`, put, { onSuccess: () => { const displayId = id.slice(0, CHAR_COUNT) + "..." + id.slice(-CHAR_COUNT); toast({ title: t("common:success"), description: t("tree_stopped", { id: displayId }), status: "success", duration: 5000, isClosable: true, }); }, }); const handleDelete = async () => { await deleteMessage(); mutate((key) => typeof key === "string" && key.startsWith("/api/messages"), undefined, { revalidate: true }); }; const handleStop = () => { stopTree(); }; const handleCopy = (text: string) => { navigator.clipboard.writeText(text); const displayId = text.slice(0, CHAR_COUNT) + "..." + text.slice(-CHAR_COUNT); toast({ title: t("common:copied"), description: displayId, status: "info", duration: 5000, isClosable: true, }); }; const isAdmin = useHasRole("admin"); return ( {["+1", "-1"].map((emoji) => ( ))} }> {t("label_action")} }> {t("report_action")} }> {t("open_new_tab_action")} handleCopy(`${window.location.protocol}//${window.location.host}/messages/${id}`)} icon={} > {t("copy_message_link")} {!!isAdmin && ( <> handleCopy(id)} icon={}> {t("copy_message_id")} }> {t("view_user")} }> {t("common:delete")} }> {t("stop_tree")} )} ); };