* add courses page * add api for courses * add course page * update course type * update client * update course page * refactor courses pages * render chapter content * shadow in course * fix video handler * update style * mark finished examples * fix media player * update locale * finish chapter * refactor * auto update chapter status * audo finish chapter * fix media provider * fix wavesurfer player * update continue btn * refactor chapters & page * minor fix * fix undefined * refactor * refactor * disable sentry in dev * clean markdown format before alignment * refactor * fix regenerate * fix transcription pre-process for `-` connector * upgrade deps * handle no chapters * add llm chat api * create llm chat * display llm message * create message * handle error * generate llm message * display llm datetime * scroll to message * tts for llm message * add course provider * refactor * translate llm message * fix llm chat introduction * refacotr * upgrade deps * refactor style * handle undefined * fix posts * update locales * update courses api * add enrollments count * upgrade yarn * upgrade deps * restore dep to fix package in mac * upgrade deps
217 lines
6.9 KiB
TypeScript
217 lines
6.9 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, ConversationShortcuts } from "@renderer/components";
|
|
import { useContext, useState } from "react";
|
|
import { AppSettingsProviderContext } from "@renderer/context";
|
|
import {
|
|
CheckCircleIcon,
|
|
LoaderIcon,
|
|
AlertCircleIcon,
|
|
CopyIcon,
|
|
CheckIcon,
|
|
Share2Icon,
|
|
ForwardIcon,
|
|
MoreVerticalIcon,
|
|
} from "lucide-react";
|
|
import { useCopyToClipboard } from "@uidotdev/usehooks";
|
|
import { t } from "i18next";
|
|
import { useNavigate } from "react-router-dom";
|
|
import Markdown from "react-markdown";
|
|
import { formatDateTime } from "@renderer/lib/utils";
|
|
|
|
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 navigate = useNavigate();
|
|
|
|
const handleShare = async () => {
|
|
if (message.role === "user") {
|
|
const content = message.content;
|
|
webApi
|
|
.createPost({
|
|
metadata: {
|
|
type: "prompt",
|
|
content,
|
|
},
|
|
})
|
|
.then(() => {
|
|
toast.success(t("sharedSuccessfully"), {
|
|
description: t("sharedPrompt"),
|
|
action: {
|
|
label: t("view"),
|
|
onClick: () => {
|
|
navigate("/community");
|
|
},
|
|
},
|
|
actionButtonStyle: {
|
|
backgroundColor: "var(--primary)",
|
|
},
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
toast.error(t("shareFailed"), { description: err.message });
|
|
});
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div id={`message-${message.id}`} className="">
|
|
<div className="flex items-center justify-end space-x-2 mb-2">
|
|
<div className="text-sm text-muted-foreground">{user.name}</div>
|
|
<Avatar className="w-8 h-8 bg-background">
|
|
<AvatarImage src={user.avatarUrl} />
|
|
<AvatarFallback className="bg-primary text-white capitalize">
|
|
{user.name?.[0] ?? "U"}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</div>
|
|
<div className="flex flex-col gap-2 px-4 py-2 bg-sky-500/30 border-sky-500 rounded-lg shadow-sm w-full mb-2">
|
|
<Markdown
|
|
className="select-text prose dark:prose-invert"
|
|
components={{
|
|
a({ node, children, ...props }) {
|
|
try {
|
|
new URL(props.href ?? "");
|
|
props.target = "_blank";
|
|
props.rel = "noopener noreferrer";
|
|
} catch (e) {}
|
|
|
|
return <a {...props}>{children}</a>;
|
|
},
|
|
}}
|
|
>
|
|
{message.content}
|
|
</Markdown>
|
|
|
|
{Boolean(speech) && <SpeechPlayer speech={speech} />}
|
|
|
|
<DropdownMenu>
|
|
<div className="flex items-center justify-end space-x-4">
|
|
{message.createdAt ? (
|
|
<CheckCircleIcon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("sent")}
|
|
className="w-4 h-4"
|
|
/>
|
|
) : message.status === "pending" ? (
|
|
<LoaderIcon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("sending")}
|
|
className="w-4 h-4 animate-spin"
|
|
/>
|
|
) : (
|
|
message.status === "error" && (
|
|
<DropdownMenuTrigger>
|
|
<AlertCircleIcon className="w-4 h-4 text-destructive" />
|
|
</DropdownMenuTrigger>
|
|
)
|
|
)}
|
|
{copied ? (
|
|
<CheckIcon className="w-4 h-4 text-green-500" />
|
|
) : (
|
|
<CopyIcon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("copy")}
|
|
className="w-4 h-4 cursor-pointer"
|
|
onClick={() => {
|
|
copyToClipboard(message.content);
|
|
setCopied(true);
|
|
setTimeout(() => {
|
|
setCopied(false);
|
|
}, 3000);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
<ConversationShortcuts
|
|
prompt={message.content}
|
|
excludedIds={[message.conversationId]}
|
|
trigger={
|
|
<ForwardIcon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("forward")}
|
|
className="w-4 h-4 cursor-pointer"
|
|
/>
|
|
}
|
|
/>
|
|
|
|
{message.createdAt && (
|
|
<AlertDialog>
|
|
<AlertDialogTrigger asChild>
|
|
<Share2Icon
|
|
data-tooltip-id="global-tooltip"
|
|
data-tooltip-content={t("share")}
|
|
className="w-4 h-4 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>
|
|
)}
|
|
|
|
<DropdownMenuTrigger>
|
|
<MoreVerticalIcon className="w-4 h-4" />
|
|
</DropdownMenuTrigger>
|
|
</div>
|
|
|
|
<DropdownMenuContent>
|
|
<DropdownMenuItem className="cursor-pointer" onClick={onResend}>
|
|
<span className="mr-auto capitalize">{t("resend")}</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem className="cursor-pointer" onClick={onRemove}>
|
|
<span className="mr-auto text-destructive capitalize">
|
|
{t("remove")}
|
|
</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
<div className="flex justify-end text-xs text-muted-foreground timestamp">
|
|
{formatDateTime(message.createdAt)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|