import { LightBulbIcon, XMarkIcon } from "@heroicons/react/24/outline";
import Markdown from "react-markdown";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import "katex/dist/katex.min.css";
import {
useContext,
useState,
useMemo,
useInsertionEffect,
useEffect,
} from "react";
import { ChatStoreMessage } from "@/types/chatstore";
import { addTotalCost } from "@/utils/totalCost";
import { Tr } from "@/translate";
import { getMessageText } from "@/chatgpt";
import { EditMessage } from "@/components/editMessage";
import logprobToColor from "@/utils/logprob";
import {
ChatBubble,
ChatBubbleMessage,
ChatBubbleAction,
ChatBubbleActionWrapper,
} from "@/components/ui/chat/chat-bubble";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import {
Collapsible,
CollapsibleContent,
CollapsibleTrigger,
} from "@/components/ui/collapsible";
import { useToast } from "@/hooks/use-toast";
import {
ClipboardIcon,
PencilIcon,
MessageSquareOffIcon,
MessageSquarePlusIcon,
AudioLinesIcon,
LoaderCircleIcon,
ChevronsUpDownIcon,
} from "lucide-react";
import { AppChatStoreContext, AppContext } from "@/pages/App";
import { Card, CardContent, CardHeader, CardTitle } from "./ui/card";
interface HideMessageProps {
chat: ChatStoreMessage;
}
function MessageHide({ chat }: HideMessageProps) {
return (
<>
{getMessageText(chat).split("\n")[0].slice(0, 28)} ...
Removed from context
>
);
}
interface MessageDetailProps {
chat: ChatStoreMessage;
renderMarkdown: boolean;
}
function MessageDetail({ chat, renderMarkdown }: MessageDetailProps) {
if (typeof chat.content === "string") {
return ;
}
return (
{chat.content.map((mdt) =>
mdt.type === "text" ? (
chat.hide ? (
mdt.text?.split("\n")[0].slice(0, 16) + " ..."
) : renderMarkdown ? (
{mdt.text}
) : (
mdt.text
)
) : (

{
window.open(mdt.image_url?.url, "_blank");
}}
/>
)
)}
);
}
interface ToolCallMessageProps {
chat: ChatStoreMessage;
copyToClipboard: (text: string) => void;
}
function MessageToolCall({ chat, copyToClipboard }: ToolCallMessageProps) {
return (
{chat.tool_calls?.map((tool_call) => (
Tool Call ID:{" "}
copyToClipboard(String(tool_call.id))}
>
{tool_call?.id}
Type: {tool_call?.type}
Function:
copyToClipboard(tool_call.function.name)}
>
{tool_call.function.name}
Arguments:
copyToClipboard(tool_call.function.arguments)}
>
{tool_call.function.arguments}
))}
{/* [TODO] */}
{chat.content as string}
);
}
interface ToolRespondMessageProps {
chat: ChatStoreMessage;
copyToClipboard: (text: string) => void;
}
function MessageToolResp({ chat, copyToClipboard }: ToolRespondMessageProps) {
return (
Tool Response ID:{" "}
copyToClipboard(String(chat.tool_call_id))}
>
{chat.tool_call_id}
{/* [TODO] */}
{chat.content as string}
);
}
interface TTSProps {
chat: ChatStoreMessage;
}
interface TTSPlayProps {
chat: ChatStoreMessage;
}
export function TTSPlay(props: TTSPlayProps) {
const src = useMemo(() => {
if (props.chat.audio instanceof Blob) {
return URL.createObjectURL(props.chat.audio);
}
return "";
}, [props.chat.audio]);
if (props.chat.hide) {
return <>>;
}
if (props.chat.audio instanceof Blob) {
return ;
}
return <>>;
}
function TTSButton(props: TTSProps) {
const [generating, setGenerating] = useState(false);
const { chatStore, setChatStore } = useContext(AppChatStoreContext);
return (
);
}
export default function Message(props: { messageIndex: number }) {
const { messageIndex } = props;
const { chatStore, setChatStore } = useContext(AppChatStoreContext);
const chat = chatStore.history[messageIndex];
const [showEdit, setShowEdit] = useState(false);
const { defaultRenderMD } = useContext(AppContext);
const [renderMarkdown, setRenderWorkdown] = useState(defaultRenderMD);
const [renderColor, setRenderColor] = useState(false);
useEffect(() => {
setRenderWorkdown(defaultRenderMD);
}, [defaultRenderMD]);
const { toast } = useToast();
const copyToClipboard = async (text: string) => {
try {
await navigator.clipboard.writeText(text);
toast({
description: Message copied to clipboard!
,
});
} catch (err) {
const textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
document.execCommand("copy");
toast({
description: Message copied to clipboard!
,
});
} catch (err) {
toast({
description: Failed to copy to clipboard
,
});
}
document.body.removeChild(textArea);
}
};
return (
<>
{chatStore.postBeginIndex !== 0 &&
!chatStore.history[messageIndex].hide &&
chatStore.postBeginIndex ===
chatStore.history.slice(0, messageIndex).filter(({ hide }) => !hide)
.length && (
Above messages are "forgotten"
)}
{chat.role === "assistant" ? (
{chat.reasoning_content ? (
{chat.response_model_name}
{chat.reasoning_content.trim()}
) : null}
{chat.hide ? (
) : typeof chat.content !== "string" ? (
) : chat.tool_calls ? (
) : renderMarkdown ? (
(
{children}
),
pre: ({ children }) => (
{children}
),
a: ({ href, children }) => (
{children}
),
}}
>
{getMessageText(chat)}
) : (
{chat.content &&
(chat.logprobs && renderColor
? chat.logprobs.content
.filter((c) => c.token)
.map((c) => (
{c.token}
))
: getMessageText(chat))}
)}
) : (
)
}
onClick={() => {
chatStore.history[messageIndex].hide =
!chatStore.history[messageIndex].hide;
chatStore.totalTokens = 0;
for (const i of chatStore.history
.filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex)
.map(({ token }) => token)) {
chatStore.totalTokens += i;
}
setChatStore({ ...chatStore });
}}
/>
}
onClick={() => setShowEdit(true)}
/>
}
onClick={() => copyToClipboard(getMessageText(chat))}
/>
{chatStore.tts_api && chatStore.tts_key && (
)}
) : (
{chat.hide ? (
) : typeof chat.content !== "string" ? (
) : chat.tool_calls ? (
) : chat.role === "tool" ? (
) : renderMarkdown ? (
{getMessageText(chat)}
) : (
{chat.content &&
(chat.logprobs && renderColor
? chat.logprobs.content
.filter((c) => c.token)
.map((c) => (
{c.token}
))
: getMessageText(chat))}
)}
) : (
)
}
onClick={() => {
chatStore.history[messageIndex].hide =
!chatStore.history[messageIndex].hide;
chatStore.totalTokens = 0;
for (const i of chatStore.history
.filter(({ hide }) => !hide)
.slice(chatStore.postBeginIndex)
.map(({ token }) => token)) {
chatStore.totalTokens += i;
}
setChatStore({ ...chatStore });
}}
/>
}
onClick={() => setShowEdit(true)}
/>
}
onClick={() => copyToClipboard(getMessageText(chat))}
/>
{chatStore.tts_api && chatStore.tts_key && (
)}
)}
{chatStore.develop_mode && (
)}
>
);
}