import clsx from "clsx"; import React, { useState, useEffect } from "react"; import { t } from "../../../i18n"; import { FilledButton } from "../../FilledButton"; import { TrashIcon, codeIcon, stackPushIcon, RetryIcon } from "../../icons"; import type { TChat, TTTDDialog } from "../types"; export const ChatMessage: React.FC<{ message: TChat.ChatMessage; onMermaidTabClick?: (message: TChat.ChatMessage) => void; onAiRepairClick?: (message: TChat.ChatMessage) => void; onDeleteMessage?: (messageId: string) => void; onInsertMessage?: (message: TChat.ChatMessage) => void; onRetry?: (message: TChat.ChatMessage) => void; rateLimitRemaining?: number; isLastMessage?: boolean; renderWarning?: TTTDDialog.renderWarning; allowFixingParseError?: boolean; }> = ({ message, onMermaidTabClick, onAiRepairClick, onDeleteMessage, onInsertMessage, onRetry, rateLimitRemaining, isLastMessage, renderWarning, allowFixingParseError, }) => { const [canRetry, setCanRetry] = useState(false); useEffect(() => { if (!message.error || !isLastMessage) { return; } if (message.error && !message.lastAttemptAt) { setCanRetry(true); return; } const timeSinceLastAttempt = Date.now() - message.lastAttemptAt!; const remainingTime = Math.max(0, 5000 - timeSinceLastAttempt); if (remainingTime === 0) { setCanRetry(true); return; } setCanRetry(false); const timer = setTimeout(() => { setCanRetry(true); }, remainingTime); return () => clearTimeout(timer); }, [message.error, message.lastAttemptAt, isLastMessage]); const formatTime = (date: Date) => { return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); }; if (message.type === "warning") { const customOverride = renderWarning?.(message); return (
{t("chat.role.system")} {formatTime(message.timestamp)}
{customOverride ? ( customOverride ) : message.warningType === "messageLimitExceeded" ? ( <> {t("chat.rateLimit.messageLimit")}
{ window.open( `${ import.meta.env.VITE_APP_PLUS_LP }/plus?utm_source=excalidraw&utm_medium=app&utm_content=ttdChatBanner#excalidraw-redirect`, "_blank", "noopener", ); }} > {t("chat.upsellBtnLabel")}
) : ( t("chat.rateLimit.generalRateLimit") )}
); } return (
{message.type === "user" ? t("chat.role.user") : t("chat.role.assistant")} {formatTime(message.timestamp)}
{message.error ? ( <>
{message.content}
{message.errorType !== "parse" && (
Error: {message.error || t("chat.errors.generationFailed")}
)} {message.errorType === "parse" && allowFixingParseError && (

{t("chat.errors.invalidDiagram")}

{onMermaidTabClick && ( )} {onAiRepairClick && ( )}
)} ) : (
{message.content} {message.isGenerating && ( ▋ )}
)}
{message.type === "assistant" && !message.isGenerating && (
{!message.error && onInsertMessage && ( )} {onMermaidTabClick && message.content && ( )} {onDeleteMessage && message.errorType !== "network" && ( )} {message.errorType === "network" && onRetry && isLastMessage && ( )}
)}
); };