import { ActionBarPrimitive, AssistantIf, ErrorPrimitive, MessagePrimitive, useAssistantState, } from "@assistant-ui/react"; import { useAtom, useAtomValue } from "jotai"; import { CheckIcon, CopyIcon, DownloadIcon, RefreshCwIcon } from "lucide-react"; import type { FC } from "react"; import { useContext, useEffect, useRef, useState } from "react"; import { addingCommentToMessageIdAtom, commentsEnabledAtom, } from "@/atoms/chat/current-thread.atom"; import { activeSearchSpaceIdAtom } from "@/atoms/search-spaces/search-space-query.atoms"; import { BranchPicker } from "@/components/assistant-ui/branch-picker"; import { MarkdownText } from "@/components/assistant-ui/markdown-text"; import { ThinkingStepsContext, ThinkingStepsDisplay, } from "@/components/assistant-ui/thinking-steps"; import { ToolFallback } from "@/components/assistant-ui/tool-fallback"; import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; import { CommentPanelContainer } from "@/components/chat-comments/comment-panel-container/comment-panel-container"; import { CommentTrigger } from "@/components/chat-comments/comment-trigger/comment-trigger"; import { useComments } from "@/hooks/use-comments"; export const MessageError: FC = () => { return ( ); }; /** * Custom component to render thinking steps from Context */ const ThinkingStepsPart: FC = () => { const thinkingStepsMap = useContext(ThinkingStepsContext); // Get the current message ID to look up thinking steps const messageId = useAssistantState(({ message }) => message?.id); const thinkingSteps = thinkingStepsMap.get(messageId) || []; // Check if this specific message is currently streaming // A message is streaming if: thread is running AND this is the last assistant message const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning); const isLastMessage = useAssistantState(({ message }) => message?.isLast ?? false); const isMessageStreaming = isThreadRunning && isLastMessage; if (thinkingSteps.length === 0) return null; return (
); }; const AssistantMessageInner: FC = () => { return ( <> {/* Render thinking steps from message content - this ensures proper scroll tracking */}
); }; function parseMessageId(assistantUiMessageId: string | undefined): number | null { if (!assistantUiMessageId) return null; const match = assistantUiMessageId.match(/^msg-(\d+)$/); return match ? Number.parseInt(match[1], 10) : null; } export const AssistantMessage: FC = () => { const [messageHeight, setMessageHeight] = useState(undefined); const messageRef = useRef(null); const messageId = useAssistantState(({ message }) => message?.id); const searchSpaceId = useAtomValue(activeSearchSpaceIdAtom); const dbMessageId = parseMessageId(messageId); const commentsEnabled = useAtomValue(commentsEnabledAtom); const [addingCommentToMessageId, setAddingCommentToMessageId] = useAtom( addingCommentToMessageIdAtom ); const isThreadRunning = useAssistantState(({ thread }) => thread.isRunning); const isLastMessage = useAssistantState(({ message }) => message?.isLast ?? false); const isMessageStreaming = isThreadRunning && isLastMessage; const { data: commentsData } = useComments({ messageId: dbMessageId ?? 0, enabled: !!dbMessageId, }); const hasComments = (commentsData?.total_count ?? 0) > 0; const isAddingComment = dbMessageId !== null && addingCommentToMessageId === dbMessageId; const showCommentPanel = hasComments || isAddingComment; const handleToggleAddComment = () => { if (!dbMessageId) return; setAddingCommentToMessageId(isAddingComment ? null : dbMessageId); }; useEffect(() => { if (!messageRef.current) return; const el = messageRef.current; const update = () => setMessageHeight(el.offsetHeight); update(); const observer = new ResizeObserver(update); observer.observe(el); return () => observer.disconnect(); }, []); return ( {searchSpaceId && commentsEnabled && !isMessageStreaming && (
{!hasComments && ( )} {showCommentPanel && dbMessageId && (
)}
)}
); }; const AssistantActionBar: FC = () => { return ( message.isCopied}> !message.isCopied}> ); };