Add a few stable selectors

This commit is contained in:
Nate Kelley 2025-08-08 18:30:52 -06:00
parent 0d20155b97
commit 6af0f6e011
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
5 changed files with 57 additions and 44 deletions

View File

@ -26,8 +26,6 @@ export const ChatMessageBlock: React.FC<{
select: selectIsCompleted
});
console.log('chat message block', messageId, messageIndex);
if (!messageExists) return null;
return (

View File

@ -1,5 +1,5 @@
import React from 'react';
import type { BusterChatMessageResponse } from '@/api/asset_interfaces';
import React, { useCallback } from 'react';
import type { BusterChatMessage, BusterChatMessageResponse } from '@/api/asset_interfaces/chat';
import { useGetChatMessage } from '@/api/buster_rest/chats';
import { ChatResponseMessage_File } from './ChatResponseMessage_File';
import { ChatResponseMessage_Text } from './ChatResponseMessage_Text';
@ -26,23 +26,29 @@ export interface ChatResponseMessageSelectorProps {
chatId: string;
}
export const ChatResponseMessageSelector: React.FC<ChatResponseMessageSelectorProps> = React.memo(
({ responseMessageId, messageId, chatId, isStreamFinished }) => {
const { data: messageType } = useGetChatMessage(messageId, {
select: (x) => x?.response_messages?.[responseMessageId]?.type || 'text'
});
const ChatResponseMessage =
ChatResponseMessageRecord[messageType as BusterChatMessageResponse['type']];
export const ChatResponseMessageSelector: React.FC<ChatResponseMessageSelectorProps> = ({
responseMessageId,
messageId,
chatId,
isStreamFinished
}) => {
const { data: messageType } = useGetChatMessage(messageId, {
select: useCallback(
(x: BusterChatMessage) => x?.response_messages?.[responseMessageId]?.type || 'text',
[responseMessageId]
)
});
const ChatResponseMessage =
ChatResponseMessageRecord[messageType as BusterChatMessageResponse['type']];
return (
<ChatResponseMessage
isStreamFinished={isStreamFinished}
responseMessageId={responseMessageId}
messageId={messageId}
chatId={chatId}
/>
);
}
);
return (
<ChatResponseMessage
isStreamFinished={isStreamFinished}
responseMessageId={responseMessageId}
messageId={messageId}
chatId={chatId}
/>
);
};
ChatResponseMessageSelector.displayName = 'ChatResponseMessageSelector';

View File

@ -4,6 +4,7 @@ import { ChatMessageOptions } from '../ChatMessageOptions';
import { MessageContainer } from '../MessageContainer';
import { ChatResponseMessageSelector } from './ChatResponseMessageSelector';
import { ChatResponseReasoning } from './ChatResponseReasoning';
import type { BusterChatMessage } from '@/api/asset_interfaces/chat';
interface ChatResponseMessagesProps {
isStreamFinished: boolean;
@ -12,16 +13,21 @@ interface ChatResponseMessagesProps {
messageIndex: number;
}
const stableResponseMessageIdsSelector = (x: BusterChatMessage) => x?.response_message_ids || [];
const stableLastReasoningMessageIdSelector = (x: BusterChatMessage) =>
x?.reasoning_message_ids?.[x.reasoning_message_ids.length - 1];
const stableFinalReasoningMessageSelector = (x: BusterChatMessage) => x?.final_reasoning_message;
export const ChatResponseMessages: React.FC<ChatResponseMessagesProps> = React.memo(
({ chatId, isStreamFinished, messageId, messageIndex }) => {
const { data: responseMessageIds } = useGetChatMessage(messageId, {
select: (x) => x?.response_message_ids || []
select: stableResponseMessageIdsSelector
});
const { data: lastReasoningMessageId } = useGetChatMessage(messageId, {
select: (x) => x?.reasoning_message_ids?.[x.reasoning_message_ids.length - 1]
select: stableLastReasoningMessageIdSelector
});
const { data: finalReasoningMessage } = useGetChatMessage(messageId, {
select: (x) => x?.final_reasoning_message
select: stableFinalReasoningMessageSelector
});
const showReasoningMessage =
messageIndex === 0 ? !!lastReasoningMessageId || !isStreamFinished : true;

View File

@ -11,6 +11,7 @@ import { ShimmerText } from '@/components/ui/typography/ShimmerText';
import { BusterRoutes, createBusterRoute } from '@/routes';
import { useChatLayoutContextSelector } from '../../../ChatLayoutContext';
import { BLACK_BOX_INITIAL_THOUGHT } from '@/layouts/ChatLayout/ChatContext/useBlackBoxMessage';
import type { BusterChatMessage } from '@/api/asset_interfaces/chat';
const animations = {
initial: { opacity: 0 },
@ -19,6 +20,9 @@ const animations = {
transition: { delay: 0, duration: 0.35 }
};
const stableLastMessageTitleSelector = (x: BusterChatMessage) =>
x?.reasoning_messages?.[x.reasoning_message_ids?.[x.reasoning_message_ids.length - 1]]?.title;
export const ChatResponseReasoning: React.FC<{
reasoningMessageId: string | undefined;
finalReasoningMessage: string | undefined | null;
@ -29,7 +33,7 @@ export const ChatResponseReasoning: React.FC<{
({ finalReasoningMessage, reasoningMessageId, isStreamFinished, messageId, chatId }) => {
const urlMessageId = useChatLayoutContextSelector((x) => x.messageId);
const { data: lastMessageTitle } = useGetChatMessage(messageId, {
select: (x) => x?.reasoning_messages?.[reasoningMessageId ?? '']?.title
select: stableLastMessageTitleSelector
});
const selectedFileType = useChatLayoutContextSelector((x) => x.selectedFileType);
const isReasonginFileSelected = selectedFileType === 'reasoning' && urlMessageId === messageId;

View File

@ -1,6 +1,6 @@
'use client';
import React, { useRef, useState } from 'react';
import React, { useCallback, useRef, useState } from 'react';
import type { BusterChatMessageRequest } from '@/api/asset_interfaces';
import { Button } from '@/components/ui/buttons';
import { Copy, PenWriting } from '@/components/ui/icons';
@ -25,22 +25,25 @@ export const ChatUserMessage: React.FC<{
const { sender_avatar, sender_id, sender_name, request } = requestMessage;
const onSetIsEditing = useMemoizedFn((isEditing: boolean) => {
const onSetIsEditing = useCallback((isEditing: boolean) => {
setIsEditing(isEditing);
setIsTooltipOpen(false);
});
}, []);
const handleCopy = useMemoizedFn((e?: React.ClipboardEvent) => {
// Prevent default copy behavior
//I do not know why this is needed, but it is...
if (e?.clipboardData) {
e.preventDefault();
e.clipboardData.setData('text/plain', request || '');
} else {
navigator.clipboard.writeText(request || '');
}
openSuccessMessage('Copied to clipboard');
});
const handleCopy = useCallback(
(e?: React.ClipboardEvent) => {
// Prevent default copy behavior
//I do not know why this is needed, but it is...
if (e?.clipboardData) {
e.preventDefault();
e.clipboardData.setData('text/plain', request || '');
} else {
navigator.clipboard.writeText(request || '');
}
openSuccessMessage('Copied to clipboard');
},
[openSuccessMessage, request]
);
return (
<MessageContainer
@ -66,7 +69,6 @@ export const ChatUserMessage: React.FC<{
{isStreamFinished && (
<RequestMessageTooltip
isTooltipOpen={isTooltipOpen}
requestMessage={requestMessage}
setIsEditing={setIsEditing}
onCopy={handleCopy}
/>
@ -81,12 +83,9 @@ ChatUserMessage.displayName = 'ChatUserMessage';
const RequestMessageTooltip: React.FC<{
isTooltipOpen: boolean;
requestMessage: NonNullable<BusterChatMessageRequest>;
setIsEditing: (isEditing: boolean) => void;
onCopy: () => void;
}> = React.memo(({ isTooltipOpen, requestMessage, setIsEditing, onCopy }) => {
const { openSuccessMessage } = useBusterNotifications();
}> = React.memo(({ isTooltipOpen, setIsEditing, onCopy }) => {
const onEdit = useMemoizedFn(() => {
setIsEditing(true);
});