import { Fragment, useMemo } from "react"; import { ChatMessage, useChatStore } from "@/app/store/chat"; import { CHAT_PAGE_SIZE } from "@/app/constant"; import Locale from "@/app/locales"; import { getMessageTextContent, selectOrCopy } from "@/app/utils"; import LoadingIcon from "@/app/icons/three-dots.svg"; import { Avatar } from "@/app/components/emoji"; import { MaskAvatar } from "@/app/components/mask"; import { useAppConfig } from "@/app/store/config"; import ClearContextDivider from "./ClearContextDivider"; import dynamic from "next/dynamic"; import useRelativePosition, { Orientation, } from "@/app/hooks/useRelativePosition"; import MessageActions, { RenderMessage } from "./MessageActions"; import Imgs from "@/app/components/Imgs"; export type { RenderMessage }; export interface ChatMessagePanelProps { scrollRef: React.RefObject; inputRef: React.RefObject; isMobileScreen: boolean; msgRenderIndex: number; userInput: string; context: any[]; renderMessages: RenderMessage[]; scrollDomToBottom: () => void; setAutoScroll?: (value: boolean) => void; setMsgRenderIndex?: (newIndex: number) => void; setHitBottom?: (value: boolean) => void; setUserInput?: (v: string) => void; setIsLoading?: (value: boolean) => void; setShowPromptModal?: (value: boolean) => void; } let MarkdownLoadedCallback: () => void; const Markdown = dynamic( async () => { const bundle = await import("@/app/components/markdown"); if (MarkdownLoadedCallback) { MarkdownLoadedCallback(); } return bundle.Markdown; }, { loading: () => , }, ); export default function ChatMessagePanel(props: ChatMessagePanelProps) { const { scrollRef, inputRef, setAutoScroll, setMsgRenderIndex, isMobileScreen, msgRenderIndex, setHitBottom, setUserInput, userInput, context, renderMessages, setIsLoading, setShowPromptModal, scrollDomToBottom, } = props; const chatStore = useChatStore(); const session = chatStore.currentSession(); const config = useAppConfig(); const fontSize = config.fontSize; const { position, getRelativePosition } = useRelativePosition({ containerRef: scrollRef, delay: 0, offsetDistance: 20, }); // clear context index = context length + index in messages const clearContextIndex = (session.clearContextIndex ?? -1) >= 0 ? session.clearContextIndex! + context.length - msgRenderIndex : -1; if (!MarkdownLoadedCallback) { MarkdownLoadedCallback = () => { window.setTimeout(scrollDomToBottom, 100); }; } const messages = useMemo(() => { const endRenderIndex = Math.min( msgRenderIndex + 3 * CHAT_PAGE_SIZE, renderMessages.length, ); return renderMessages.slice(msgRenderIndex, endRenderIndex); }, [msgRenderIndex, renderMessages]); const onChatBodyScroll = (e: HTMLElement) => { const bottomHeight = e.scrollTop + e.clientHeight; const edgeThreshold = e.clientHeight; const isTouchTopEdge = e.scrollTop <= edgeThreshold; const isTouchBottomEdge = bottomHeight >= e.scrollHeight - edgeThreshold; const isHitBottom = bottomHeight >= e.scrollHeight - (isMobileScreen ? 4 : 10); const prevPageMsgIndex = msgRenderIndex - CHAT_PAGE_SIZE; const nextPageMsgIndex = msgRenderIndex + CHAT_PAGE_SIZE; if (isTouchTopEdge && !isTouchBottomEdge) { setMsgRenderIndex?.(prevPageMsgIndex); } else if (isTouchBottomEdge) { setMsgRenderIndex?.(nextPageMsgIndex); } setHitBottom?.(isHitBottom); setAutoScroll?.(isHitBottom); }; const onRightClick = (e: any, message: ChatMessage) => { // copy to clipboard if (selectOrCopy(e.currentTarget, getMessageTextContent(message))) { if (userInput.length === 0) { setUserInput?.(getMessageTextContent(message)); } e.preventDefault(); } }; return (
onChatBodyScroll(e.currentTarget)} onMouseDown={() => inputRef.current?.blur()} onTouchStart={() => { inputRef.current?.blur(); setAutoScroll?.(false); }} > {messages.map((message, i) => { const isUser = message.role === "user"; const isContext = i < context.length; const shouldShowClearContextDivider = i === clearContextIndex - 1; const actionsBarPosition = position?.id === message.id && position?.poi.overlapPositions[Orientation.bottom] ? "bottom-[calc(100%-0.25rem)]" : "top-[calc(100%-0.25rem)]"; return (
{isUser ? ( ) : ( <> {["system"].includes(message.role) ? ( ) : ( )} )}
getRelativePosition(e.currentTarget, message.id) } > onRightClick(e, message)} onDoubleClickCapture={() => { if (!isMobileScreen) return; setUserInput?.(getMessageTextContent(message)); }} fontSize={fontSize} parentRef={scrollRef} defaultShow={i >= messages.length - 6} className={`leading-6 max-w-message-width ${ isUser ? " text-text-chat-message-markdown-user" : "text-text-chat-message-markdown-bot" }`} />
{shouldShowClearContextDivider && }
); })}
); }