167 lines
5.2 KiB
TypeScript
167 lines
5.2 KiB
TypeScript
import {
|
|
AlertDialog,
|
|
AlertDialogTrigger,
|
|
AlertDialogHeader,
|
|
AlertDialogDescription,
|
|
AlertDialogTitle,
|
|
AlertDialogContent,
|
|
AlertDialogFooter,
|
|
AlertDialogCancel,
|
|
AlertDialogAction,
|
|
Avatar,
|
|
AvatarImage,
|
|
AvatarFallback,
|
|
Button,
|
|
DropdownMenu,
|
|
DropdownMenuTrigger,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuSeparator,
|
|
toast,
|
|
} from "@renderer/components/ui";
|
|
import { SpeechPlayer } from "@renderer/components";
|
|
import { useContext, useState } from "react";
|
|
import { AppSettingsProviderContext } from "@renderer/context";
|
|
import {
|
|
CheckCircleIcon,
|
|
LoaderIcon,
|
|
AlertCircleIcon,
|
|
CopyIcon,
|
|
CheckIcon,
|
|
Share2Icon,
|
|
} from "lucide-react";
|
|
import { useCopyToClipboard } from "@uidotdev/usehooks";
|
|
import { t } from "i18next";
|
|
import Markdown from "react-markdown";
|
|
|
|
export const UserMessageComponent = (props: {
|
|
message: MessageType;
|
|
configuration?: { [key: string]: any };
|
|
onResend?: () => void;
|
|
onRemove?: () => void;
|
|
}) => {
|
|
const { message, onResend, onRemove } = props;
|
|
const speech = message.speeches?.[0];
|
|
const { user, webApi } = useContext(AppSettingsProviderContext);
|
|
const [_, copyToClipboard] = useCopyToClipboard();
|
|
const [copied, setCopied] = useState<boolean>(false);
|
|
|
|
const handleShare = async () => {
|
|
if (message.role === "user") {
|
|
const content = message.content;
|
|
webApi
|
|
.createPost({
|
|
metadata: {
|
|
type: "prompt",
|
|
content,
|
|
},
|
|
})
|
|
.then(() => {
|
|
toast(t("sharedSuccessfully"), { description: t("sharedPrompt") });
|
|
})
|
|
.catch((err) => {
|
|
toast.error(t("shareFailed"), { description: err.message });
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div
|
|
id={`message-${message.id}`}
|
|
className="flex items-end justify-end space-x-2 pl-10"
|
|
>
|
|
<DropdownMenu>
|
|
<div className="flex flex-col gap-2 px-4 py-2 bg-sky-500/30 border-sky-500 rounded-lg shadow-sm w-full">
|
|
<Markdown className="select-text prose">{message.content}</Markdown>
|
|
|
|
{Boolean(speech) && <SpeechPlayer speech={speech} />}
|
|
|
|
<div className="flex items-center justify-end space-x-2">
|
|
{message.createdAt ? (
|
|
<CheckCircleIcon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("sent")}
|
|
className="w-3 h-3"
|
|
/>
|
|
) : message.status === "pending" ? (
|
|
<LoaderIcon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("sending")}
|
|
className="w-3 h-3 animate-spin"
|
|
/>
|
|
) : (
|
|
message.status === "error" && (
|
|
<DropdownMenuTrigger>
|
|
<AlertCircleIcon className="w-3 h-3 text-destructive" />
|
|
</DropdownMenuTrigger>
|
|
)
|
|
)}
|
|
{copied ? (
|
|
<CheckIcon className="w-3 h-3 text-green-500" />
|
|
) : (
|
|
<CopyIcon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("copy")}
|
|
className="w-3 h-3 cursor-pointer"
|
|
onClick={() => {
|
|
copyToClipboard(message.content);
|
|
setCopied(true);
|
|
setTimeout(() => {
|
|
setCopied(false);
|
|
}, 3000);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{message.createdAt && (
|
|
<AlertDialog>
|
|
<AlertDialogTrigger asChild>
|
|
<Share2Icon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("share")}
|
|
className="w-3 h-3 cursor-pointer"
|
|
/>
|
|
</AlertDialogTrigger>
|
|
<AlertDialogContent>
|
|
<AlertDialogHeader>
|
|
<AlertDialogTitle>{t("sharePrompt")}</AlertDialogTitle>
|
|
<AlertDialogDescription>
|
|
{t("areYouSureToShareThisPromptToCommunity")}
|
|
</AlertDialogDescription>
|
|
</AlertDialogHeader>
|
|
<AlertDialogFooter>
|
|
<AlertDialogCancel>{t("cancel")}</AlertDialogCancel>
|
|
<AlertDialogAction asChild>
|
|
<Button variant="default" onClick={handleShare}>
|
|
{t("share")}
|
|
</Button>
|
|
</AlertDialogAction>
|
|
</AlertDialogFooter>
|
|
</AlertDialogContent>
|
|
</AlertDialog>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<DropdownMenuContent>
|
|
<DropdownMenuItem onClick={onResend}>
|
|
<span className="mr-auto capitalize">{t("resend")}</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={onRemove}>
|
|
<span className="mr-auto text-destructive capitalize">
|
|
{t("remove")}
|
|
</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<Avatar className="w-8 h-8 bg-white">
|
|
<AvatarImage src={user.avatarUrl} />
|
|
<AvatarFallback className="bg-primary text-white capitalize">
|
|
{user.name[0]}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</div>
|
|
);
|
|
};
|