buster/web/src/controllers/ReasoningController/ReasoningMessages/ReasoningMessageSelector.tsx

121 lines
3.3 KiB
TypeScript

import React, { useMemo } from 'react';
import type {
BusterChatMessageReasoning,
BusterChatMessageReasoning_text
} from '@/api/asset_interfaces/chat';
import { ReasoningMessage_PillsContainer } from './ReasoningMessage_PillContainers';
import { ReasoningMessage_Files } from './ReasoningMessage_Files';
import { ReasoningMessage_Text } from './ReasoningMessage_Text';
import { useGetChatMessage } from '@/api/buster_rest/chats';
import { AnimatePresence, motion } from 'framer-motion';
import { BarContainer } from './BarContainer';
export interface ReasoningMessageProps {
reasoningMessageId: string;
messageId: string;
isCompletedStream: boolean;
chatId: string;
}
const itemAnimationConfig = {
initial: { opacity: 0, height: 0 },
animate: {
opacity: 1,
height: 'auto',
transition: {
height: {
type: 'spring',
stiffness: 400,
damping: 32
},
opacity: { duration: 0.16 }
}
},
exit: {
opacity: 0,
height: 0,
transition: {
height: {
type: 'spring',
stiffness: 450,
damping: 35
},
opacity: { duration: 0.12 }
}
}
};
const ReasoningMessageRecord: Record<
BusterChatMessageReasoning['type'],
React.FC<ReasoningMessageProps>
> = {
pills: ReasoningMessage_PillsContainer,
text: ReasoningMessage_Text,
files: ReasoningMessage_Files
};
export interface ReasoningMessageSelectorProps {
reasoningMessageId: string;
messageId: string;
isCompletedStream: boolean;
chatId: string;
}
export const ReasoningMessageSelector: React.FC<ReasoningMessageSelectorProps> = ({
reasoningMessageId,
isCompletedStream,
chatId,
messageId
}) => {
const messageStuff = useGetChatMessage(messageId, (x) => ({
title: x?.reasoning_messages[reasoningMessageId]?.title,
secondary_title: x?.reasoning_messages[reasoningMessageId]?.secondary_title,
type: x?.reasoning_messages[reasoningMessageId]?.type,
status: x?.reasoning_messages[reasoningMessageId]?.status,
hasMessage: !!(x?.reasoning_messages[reasoningMessageId] as BusterChatMessageReasoning_text)
?.message
}));
const { title, secondary_title, type, status, hasMessage } = messageStuff || {};
const showBar = useMemo(() => {
if (type === 'text') return !!hasMessage;
return true;
}, [type, hasMessage]);
if (!type || !status) return null;
const ReasoningMessage = ReasoningMessageRecord[type];
const animationKey = reasoningMessageId + type;
return (
<BarContainer
showBar={showBar}
status={status}
isCompletedStream={isCompletedStream}
title={title ?? ''}
secondaryTitle={secondary_title ?? ''}>
<AnimatePresence mode="wait" initial={!isCompletedStream}>
<motion.div key={animationKey} {...itemAnimationConfig} className="overflow-hidden" layout>
<div className="min-h-[1px]">
<ReasoningMessage
reasoningMessageId={reasoningMessageId}
isCompletedStream={isCompletedStream}
messageId={messageId}
chatId={chatId}
/>
</div>
</motion.div>
</AnimatePresence>
</BarContainer>
);
};
const showBarHelper = (
type: BusterChatMessageReasoning['type'],
status: BusterChatMessageReasoning['status']
) => {
if (type === 'pills') return false;
if (status === 'loading') return false;
return true;
};