import { useCopilotChatConfiguration } from "../../providers/CopilotChatConfigurationProvider.mjs"; import { isReactComponentType, renderSlot } from "../../lib/slots.mjs"; import { useCopilotKit } from "../../providers/CopilotKitProvider.mjs"; import { useRenderCustomMessages } from "../../hooks/use-render-custom-messages.mjs"; import { useRenderActivityMessage } from "../../hooks/use-render-activity-message.mjs"; import "../../hooks/index.mjs"; import CopilotChatAssistantMessage_default from "./CopilotChatAssistantMessage.mjs"; import CopilotChatUserMessage_default from "./CopilotChatUserMessage.mjs"; import CopilotChatReasoningMessage_default from "./CopilotChatReasoningMessage.mjs"; import React, { useEffect, useReducer, useState } from "react"; import { twMerge } from "tailwind-merge"; import { jsx, jsxs } from "react/jsx-runtime"; //#region src/components/chat/CopilotChatMessageView.tsx /** * Memoized wrapper for assistant messages to prevent re-renders when other messages change. */ const MemoizedAssistantMessage = React.memo(function MemoizedAssistantMessage({ message, messages, isRunning, AssistantMessageComponent, slotProps }) { return /* @__PURE__ */ jsx(AssistantMessageComponent, { message, messages, isRunning, ...slotProps }); }, (prevProps, nextProps) => { if (prevProps.message.id !== nextProps.message.id) return false; if (prevProps.message.content !== nextProps.message.content) return false; const prevToolCalls = prevProps.message.toolCalls; const nextToolCalls = nextProps.message.toolCalls; if (prevToolCalls?.length !== nextToolCalls?.length) return false; if (prevToolCalls && nextToolCalls) for (let i = 0; i < prevToolCalls.length; i++) { const prevTc = prevToolCalls[i]; const nextTc = nextToolCalls[i]; if (!prevTc || !nextTc) return false; if (prevTc.id !== nextTc.id) return false; if (prevTc.function.arguments !== nextTc.function.arguments) return false; } if (prevToolCalls && prevToolCalls.length > 0) { const toolCallIds = new Set(prevToolCalls.map((tc) => tc.id)); const prevToolResults = prevProps.messages.filter((m) => m.role === "tool" && toolCallIds.has(m.toolCallId)); const nextToolResults = nextProps.messages.filter((m) => m.role === "tool" && toolCallIds.has(m.toolCallId)); if (prevToolResults.length !== nextToolResults.length) return false; for (let i = 0; i < prevToolResults.length; i++) if (prevToolResults[i].content !== nextToolResults[i].content) return false; } if (nextProps.messages[nextProps.messages.length - 1]?.id === nextProps.message.id && prevProps.isRunning !== nextProps.isRunning) return false; if (prevProps.AssistantMessageComponent !== nextProps.AssistantMessageComponent) return false; if (prevProps.slotProps !== nextProps.slotProps) return false; return true; }); /** * Memoized wrapper for user messages to prevent re-renders when other messages change. */ const MemoizedUserMessage = React.memo(function MemoizedUserMessage({ message, UserMessageComponent, slotProps }) { return /* @__PURE__ */ jsx(UserMessageComponent, { message, ...slotProps }); }, (prevProps, nextProps) => { if (prevProps.message.id !== nextProps.message.id) return false; if (prevProps.message.content !== nextProps.message.content) return false; if (prevProps.UserMessageComponent !== nextProps.UserMessageComponent) return false; if (prevProps.slotProps !== nextProps.slotProps) return false; return true; }); /** * Memoized wrapper for activity messages to prevent re-renders when other messages change. */ const MemoizedActivityMessage = React.memo(function MemoizedActivityMessage({ message, renderActivityMessage }) { return renderActivityMessage(message); }, (prevProps, nextProps) => { if (prevProps.message.id !== nextProps.message.id) return false; if (prevProps.message.activityType !== nextProps.message.activityType) return false; if (JSON.stringify(prevProps.message.content) !== JSON.stringify(nextProps.message.content)) return false; return true; }); /** * Memoized wrapper for reasoning messages to prevent re-renders when other messages change. */ const MemoizedReasoningMessage = React.memo(function MemoizedReasoningMessage({ message, messages, isRunning, ReasoningMessageComponent, slotProps }) { return /* @__PURE__ */ jsx(ReasoningMessageComponent, { message, messages, isRunning, ...slotProps }); }, (prevProps, nextProps) => { if (prevProps.message.id !== nextProps.message.id) return false; if (prevProps.message.content !== nextProps.message.content) return false; const prevIsLatest = prevProps.messages[prevProps.messages.length - 1]?.id === prevProps.message.id; const nextIsLatest = nextProps.messages[nextProps.messages.length - 1]?.id === nextProps.message.id; if (prevIsLatest !== nextIsLatest) return false; if (nextIsLatest && prevProps.isRunning !== nextProps.isRunning) return false; if (prevProps.ReasoningMessageComponent !== nextProps.ReasoningMessageComponent) return false; if (prevProps.slotProps !== nextProps.slotProps) return false; return true; }); /** * Memoized wrapper for custom messages to prevent re-renders when other messages change. */ const MemoizedCustomMessage = React.memo(function MemoizedCustomMessage({ message, position, renderCustomMessage }) { return renderCustomMessage({ message, position }); }, (prevProps, nextProps) => { if (prevProps.message.id !== nextProps.message.id) return false; if (prevProps.position !== nextProps.position) return false; if (prevProps.message.content !== nextProps.message.content) return false; if (prevProps.message.role !== nextProps.message.role) return false; if (JSON.stringify(prevProps.stateSnapshot) !== JSON.stringify(nextProps.stateSnapshot)) return false; return true; }); function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, isRunning = false, children, className, ...props }) { const renderCustomMessage = useRenderCustomMessages(); const { renderActivityMessage } = useRenderActivityMessage(); const { copilotkit } = useCopilotKit(); const config = useCopilotChatConfiguration(); const [, forceUpdate] = useReducer((x) => x + 1, 0); useEffect(() => { if (!config?.agentId) return; const agent = copilotkit.getAgent(config.agentId); if (!agent) return; const subscription = agent.subscribe({ onStateChanged: forceUpdate }); return () => subscription.unsubscribe(); }, [ config?.agentId, copilotkit, forceUpdate ]); const [interruptElement, setInterruptElement] = useState(null); useEffect(() => { setInterruptElement(copilotkit.interruptElement); const subscription = copilotkit.subscribe({ onInterruptElementChanged: ({ interruptElement }) => { setInterruptElement(interruptElement); } }); return () => subscription.unsubscribe(); }, [copilotkit]); const getStateSnapshotForMessage = (messageId) => { if (!config) return void 0; const resolvedRunId = copilotkit.getRunIdForMessage(config.agentId, config.threadId, messageId) ?? copilotkit.getRunIdsForThread(config.agentId, config.threadId).slice(-1)[0]; if (!resolvedRunId) return void 0; return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId); }; const deduplicatedMessages = [...new Map(messages.map((m) => [m.id, m])).values()]; if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Deduplicated ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`); const messageElements = deduplicatedMessages.flatMap((message) => { const elements = []; const stateSnapshot = getStateSnapshotForMessage(message.id); if (renderCustomMessage) elements.push(/* @__PURE__ */ jsx(MemoizedCustomMessage, { message, position: "before", renderCustomMessage, stateSnapshot }, `${message.id}-custom-before`)); if (message.role === "assistant") { let AssistantComponent = CopilotChatAssistantMessage_default; let assistantSlotProps; if (isReactComponentType(assistantMessage)) AssistantComponent = assistantMessage; else if (typeof assistantMessage === "string") assistantSlotProps = { className: assistantMessage }; else if (assistantMessage && typeof assistantMessage === "object") assistantSlotProps = assistantMessage; elements.push(/* @__PURE__ */ jsx(MemoizedAssistantMessage, { message, messages, isRunning, AssistantMessageComponent: AssistantComponent, slotProps: assistantSlotProps }, message.id)); } else if (message.role === "user") { let UserComponent = CopilotChatUserMessage_default; let userSlotProps; if (isReactComponentType(userMessage)) UserComponent = userMessage; else if (typeof userMessage === "string") userSlotProps = { className: userMessage }; else if (userMessage && typeof userMessage === "object") userSlotProps = userMessage; elements.push(/* @__PURE__ */ jsx(MemoizedUserMessage, { message, UserMessageComponent: UserComponent, slotProps: userSlotProps }, message.id)); } else if (message.role === "activity") { const activityMsg = message; elements.push(/* @__PURE__ */ jsx(MemoizedActivityMessage, { message: activityMsg, renderActivityMessage }, message.id)); } else if (message.role === "reasoning") { let ReasoningComponent = CopilotChatReasoningMessage_default; let reasoningSlotProps; if (isReactComponentType(reasoningMessage)) ReasoningComponent = reasoningMessage; else if (typeof reasoningMessage === "string") reasoningSlotProps = { className: reasoningMessage }; else if (reasoningMessage && typeof reasoningMessage === "object") reasoningSlotProps = reasoningMessage; elements.push(/* @__PURE__ */ jsx(MemoizedReasoningMessage, { message, messages, isRunning, ReasoningMessageComponent: ReasoningComponent, slotProps: reasoningSlotProps }, message.id)); } if (renderCustomMessage) elements.push(/* @__PURE__ */ jsx(MemoizedCustomMessage, { message, position: "after", renderCustomMessage, stateSnapshot }, `${message.id}-custom-after`)); return elements; }).filter(Boolean); if (children) return /* @__PURE__ */ jsx("div", { "data-copilotkit": true, style: { display: "contents" }, children: children({ messageElements, messages, isRunning, interruptElement }) }); const lastMessage = messages[messages.length - 1]; const showCursor = isRunning && lastMessage?.role !== "reasoning"; return /* @__PURE__ */ jsxs("div", { "data-copilotkit": true, "data-testid": "copilot-message-list", className: twMerge("copilotKitMessages cpk:flex cpk:flex-col", className), ...props, children: [ messageElements, interruptElement, showCursor && /* @__PURE__ */ jsx("div", { className: "cpk:mt-2", children: renderSlot(cursor, CopilotChatMessageView.Cursor, {}) }) ] }); } CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) { return /* @__PURE__ */ jsx("div", { "data-testid": "copilot-loading-cursor", className: twMerge("cpk:w-[11px] cpk:h-[11px] cpk:rounded-full cpk:bg-foreground cpk:animate-pulse-cursor cpk:ml-1", className), ...props }); }; //#endregion export { CopilotChatMessageView as default }; //# sourceMappingURL=CopilotChatMessageView.mjs.map