Merge branch 'big-nate/bus-939-create-new-structure-for-chats' into evals

This commit is contained in:
Nate Kelley 2025-03-11 14:02:46 -06:00
commit 2d99ad32b7
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
29 changed files with 167 additions and 76 deletions

View File

@ -61,11 +61,23 @@ export const useGetListLogs = (
}); });
}; };
export const useGetChat = (params: Parameters<typeof getChat>[0]) => { export const useGetChat = <TData = IBusterChat>(
const queryFn = useMemoizedFn(async () => { params: Parameters<typeof getChat>[0],
return await getChat(params).then((chat) => { select?: (chat: IBusterChat) => TData
console.log('TODO move this to put message in a better spot'); ) => {
return updateChatToIChat(chat, true).iChat; const queryClient = useQueryClient();
const queryFn = useMemoizedFn(() => {
return getChat(params).then((chat) => {
const { iChat, iChatMessages } = updateChatToIChat(chat, false);
iChat.message_ids.forEach((messageId) => {
queryClient.setQueryData(
queryKeys.chatsMessages(messageId).queryKey,
iChatMessages[messageId]
);
});
return iChat;
}); });
}); });
@ -75,10 +87,11 @@ export const useGetChat = (params: Parameters<typeof getChat>[0]) => {
enabled: !!params.id enabled: !!params.id
}); });
return useQuery<IBusterChat, RustApiError>({ return useQuery({
...queryKeys.chatsGetChat(params.id), ...queryKeys.chatsGetChat(params.id),
queryKey: queryKeys.chatsGetChat(params.id).queryKey, enabled: !!params.id,
enabled: !!params.id queryFn,
select
}); });
}; };

View File

@ -16,20 +16,12 @@ import {
import { useMemoizedFn } from '@/hooks'; import { useMemoizedFn } from '@/hooks';
import { QueryClient, useQueryClient } from '@tanstack/react-query'; import { QueryClient, useQueryClient } from '@tanstack/react-query';
import { queryKeys } from '@/api/query_keys'; import { queryKeys } from '@/api/query_keys';
import type { import type { UserRequestUserListPayload } from '@/api/request_interfaces/user/interfaces';
UsersFavoritePostPayload,
UserFavoriteDeletePayload,
UserUpdateFavoritesPayload,
UserRequestUserListPayload
} from '@/api/request_interfaces/user/interfaces';
export const useGetMyUserInfo = () => { export const useGetMyUserInfo = () => {
const queryFn = useMemoizedFn(async () => {
return getMyUserInfo();
});
return useQuery({ return useQuery({
...queryKeys.userGetUserMyself, ...queryKeys.userGetUserMyself,
queryFn, queryFn: getMyUserInfo,
enabled: false //This is a server only query enabled: false //This is a server only query
}); });
}; };

View File

@ -18,7 +18,8 @@ const favoritesGetList = queryOptions<BusterUserFavorite[]>({
}); });
const userGetUserMyself = queryOptions<BusterUserResponse>({ const userGetUserMyself = queryOptions<BusterUserResponse>({
queryKey: ['users', 'myself'] as const queryKey: ['users', 'myself'] as const,
staleTime: 1000 * 60 * 60 // 1 hour
}); });
const userGetUser = (userId: string) => const userGetUser = (userId: string) =>

View File

@ -100,7 +100,7 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
'relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0', 'relative flex cursor-pointer items-center gap-2 rounded-sm px-2 py-1.5 text-base outline-none select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0',
'focus:bg-item-hover focus:text-foreground', 'focus:bg-item-hover focus:text-foreground',
inset && 'pl-8', inset && 'pl-8',
truncate && 'overflow-hidden', truncate && 'overflow-hidden',

View File

@ -1,5 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react'; import type { Meta, StoryObj } from '@storybook/react';
import { AppPageLayout } from './AppPageLayout'; import { AppPageLayout } from './AppPageLayout';
import React from 'react';
const meta: Meta<typeof AppPageLayout> = { const meta: Meta<typeof AppPageLayout> = {
title: 'UI/Layouts/AppPageLayout', title: 'UI/Layouts/AppPageLayout',
@ -53,6 +54,7 @@ export const LongContent: Story = {
args: { args: {
header: <div className="bg-gray-100">Header Content</div>, header: <div className="bg-gray-100">Header Content</div>,
scrollable: true, scrollable: true,
headerBorderVariant: 'ghost',
children: ( children: (
<> <>
{Array.from({ length: 100 }, (_, i) => ( {Array.from({ length: 100 }, (_, i) => (

View File

@ -39,7 +39,14 @@ export const AppPageLayout: React.FC<
{header} {header}
</AppPageLayoutHeader> </AppPageLayoutHeader>
)} )}
<AppPageLayoutContent scrollable={scrollable}>{children}</AppPageLayoutContent>
<AppPageLayoutContent className="scroll-shadow-container" scrollable={scrollable}>
{header && scrollable && headerBorderVariant === 'ghost' && (
<div className="scroll-header"></div>
)}
{children}
</AppPageLayoutContent>
</div> </div>
); );
}; };

View File

@ -3,7 +3,7 @@ import React from 'react';
import { cva, type VariantProps } from 'class-variance-authority'; import { cva, type VariantProps } from 'class-variance-authority';
const headerVariants = cva( const headerVariants = cva(
'bg-page-background flex max-h-[38px] min-h-[38px] items-center justify-between gap-x-2.5 ', 'bg-page-background flex max-h-[38px] min-h-[38px] items-center justify-between gap-x-2.5 relative z-10',
{ {
variants: { variants: {
sizeVariant: { sizeVariant: {

View File

@ -70,7 +70,7 @@ const AppMarkdownBase: React.FC<{
}, []); }, []);
return ( return (
<div className={cn(styles.container, className)}> <div className={cn(styles.container, 'flex flex-col gap-1.5', className)}>
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkGfm]} remarkPlugins={[remarkGfm]}
skipHtml={true} skipHtml={true}

View File

@ -35,7 +35,11 @@ export const CustomParagraph: React.FC<
> = ({ children, markdown, showLoader, ...rest }) => { > = ({ children, markdown, showLoader, ...rest }) => {
if (Array.isArray(children)) { if (Array.isArray(children)) {
return ( return (
<p className={cn('leading-1.3', showLoader && 'animate-in fade-in duration-700')}> <p
className={cn(
'leading-1.3 text-size-inherit!',
showLoader && 'animate-in fade-in duration-700'
)}>
{children} {children}
</p> </p>
); );
@ -48,7 +52,13 @@ export const CustomParagraph: React.FC<
} }
return ( return (
<p className={cn('leading-1.3', showLoader && 'animate-in fade-in duration-700')}>{children}</p> <p
className={cn(
'leading-1.3 text-size-inherit!',
showLoader && 'animate-in fade-in duration-700'
)}>
{children}
</p>
); );
}; };
@ -112,7 +122,11 @@ export const CustomListItem: React.FC<
} & ExtraPropsExtra } & ExtraPropsExtra
> = ({ children, markdown, showLoader, ...rest }) => { > = ({ children, markdown, showLoader, ...rest }) => {
return ( return (
<li className={cn('leading-1.3', showLoader && 'animate-in fade-in duration-700')}> <li
className={cn(
'leading-1.3 list-inside list-disc',
showLoader && 'animate-in fade-in duration-700'
)}>
{children} {children}
</li> </li>
); );

View File

@ -38,12 +38,12 @@ export const useChatUpdate = () => {
const options = queryKeys.chatsMessages(newMessageConfig.id); const options = queryKeys.chatsMessages(newMessageConfig.id);
const queryKey = options.queryKey; const queryKey = options.queryKey;
const currentData = queryClient.getQueryData(queryKey); const currentData = queryClient.getQueryData(queryKey);
if (currentData) {
const iChatMessage = create(currentData!, (draft) => { const iChatMessage = create(currentData, (draft) => {
Object.assign(draft, newMessageConfig); Object.assign(draft, newMessageConfig);
}); });
queryClient.setQueryData(queryKey, iChatMessage);
queryClient.setQueryData(queryKey, iChatMessage); }
} }
); );

View File

@ -107,6 +107,10 @@ export const useBusterNewChat = () => {
const onFollowUpChat = useMemoizedFn( const onFollowUpChat = useMemoizedFn(
async ({ prompt, chatId }: { prompt: string; chatId: string }) => { async ({ prompt, chatId }: { prompt: string; chatId: string }) => {
busterSocket.once({
route: '/chats/post:initializeChat',
callback: initializeNewChatCallback
});
await busterSocket.emitAndOnce({ await busterSocket.emitAndOnce({
emitEvent: { emitEvent: {
route: '/chats/post', route: '/chats/post',

View File

@ -191,7 +191,7 @@ export const updateReasoningMessage = (
Object.assign(fileContentDraft, existingFile?.file || {}); Object.assign(fileContentDraft, existingFile?.file || {});
fileContentDraft.text = newFile.file.text_chunk fileContentDraft.text = newFile.file.text_chunk
? (existingFile?.file?.text || '') + newFile.file.text_chunk ? (existingFile?.file?.text || '') + newFile.file.text_chunk
: (newFile.file.text ?? existingFile?.file?.text); : (existingFile?.file?.text ?? newFile.file.text); //we are going to ignore newfile text in favor of existing... this is because Dallin is having a tough time keep yaml in order
fileContentDraft.modified = fileContentDraft.modified =
newFile.file.modified ?? existingFile?.file?.modified; newFile.file.modified ?? existingFile?.file?.modified;
}); });

View File

@ -37,10 +37,12 @@ export const useChatStreamMessage = () => {
const onUpdateChatMessageTransition = useMemoizedFn( const onUpdateChatMessageTransition = useMemoizedFn(
(chatMessage: Parameters<typeof onUpdateChatMessage>[0]) => { (chatMessage: Parameters<typeof onUpdateChatMessage>[0]) => {
const currentChatMessage = chatRefMessages.current[chatMessage.id]; const currentChatMessage = chatRefMessages.current[chatMessage.id] || {};
const iChatMessage: IBusterChatMessage = create(currentChatMessage, (draft) => { const iChatMessage: IBusterChatMessage = create(currentChatMessage, (draft) => {
Object.assign(draft || {}, chatMessage); Object.assign(draft || {}, chatMessage);
if (chatMessage.id) draft.id = chatMessage.id; if (chatMessage.id) {
draft.id = chatMessage.id;
}
})!; })!;
chatRefMessages.current[chatMessage.id] = iChatMessage; chatRefMessages.current[chatMessage.id] = iChatMessage;
@ -79,14 +81,17 @@ export const useChatStreamMessage = () => {
}); });
const initializeNewChatCallback = useMemoizedFn((d: BusterChat) => { const initializeNewChatCallback = useMemoizedFn((d: BusterChat) => {
const hasMultipleMessages = d.message_ids.length > 1;
const { iChat, iChatMessages } = updateChatToIChat(d, true); const { iChat, iChatMessages } = updateChatToIChat(d, true);
chatRef.current[iChat.id] = iChat; chatRef.current[iChat.id] = iChat;
normalizeChatMessage(iChatMessages); normalizeChatMessage(iChatMessages);
onUpdateChat(iChat); onUpdateChat(iChat);
onChangePage({ if (!hasMultipleMessages) {
route: BusterRoutes.APP_CHAT_ID, onChangePage({
chatId: iChat.id route: BusterRoutes.APP_CHAT_ID,
}); chatId: iChat.id
});
}
}); });
const replaceMessageCallback = useMemoizedFn( const replaceMessageCallback = useMemoizedFn(
@ -106,9 +111,12 @@ export const useChatStreamMessage = () => {
const _generatingTitleCallback = useMemoizedFn((_: null, newData: ChatEvent_GeneratingTitle) => { const _generatingTitleCallback = useMemoizedFn((_: null, newData: ChatEvent_GeneratingTitle) => {
const { chat_id } = newData; const { chat_id } = newData;
const updatedChat = updateChatTitle(chatRef.current[chat_id], newData); const currentChat = chatRef.current[chat_id];
chatRef.current[chat_id] = updatedChat; if (currentChat) {
onUpdateChat(updatedChat); const updatedChat = updateChatTitle(currentChat, newData);
chatRef.current[chat_id] = updatedChat;
onUpdateChat(updatedChat);
}
}); });
const _generatingResponseMessageCallback = useMemoizedFn( const _generatingResponseMessageCallback = useMemoizedFn(

View File

@ -1,10 +1,11 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { useChatIndividualContextSelector } from '@/layouts/ChatLayout/ChatContext';
import { useMessageIndividual } from '@/context/Chats'; import { useMessageIndividual } from '@/context/Chats';
import { ReasoningMessageSelector } from './ReasoningMessages'; import { ReasoningMessageSelector } from './ReasoningMessages';
import { BlackBoxMessage } from './ReasoningMessages/ReasoningBlackBoxMessage'; import { BlackBoxMessage } from './ReasoningMessages/ReasoningBlackBoxMessage';
import { useGetChat } from '@/api/buster_rest/chats';
import { FileIndeterminateLoader } from '@/components/features/FileIndeterminateLoader';
interface ReasoningControllerProps { interface ReasoningControllerProps {
chatId: string; chatId: string;
@ -12,12 +13,12 @@ interface ReasoningControllerProps {
} }
export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId, messageId }) => { export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId, messageId }) => {
const hasChat = useChatIndividualContextSelector((state) => state.hasChat); const { data: hasChat } = useGetChat({ id: chatId || '' }, (x) => !!x.id);
const reasoningMessageIds = useMessageIndividual(messageId, (x) => x?.reasoning_message_ids); const reasoningMessageIds = useMessageIndividual(messageId, (x) => x?.reasoning_message_ids);
const isCompletedStream = useMessageIndividual(messageId, (x) => x?.isCompletedStream); const isCompletedStream = useMessageIndividual(messageId, (x) => x?.isCompletedStream);
if (!hasChat || !reasoningMessageIds) if (!hasChat || !reasoningMessageIds) return <FileIndeterminateLoader />;
return <>ReasoningController: If you are seeing this there is probably an error...</>;
return ( return (
<div className="h-full flex-col space-y-2 overflow-y-auto p-5"> <div className="h-full flex-col space-y-2 overflow-y-auto p-5">

View File

@ -23,7 +23,11 @@ export const BarContainer: React.FC<{
/> />
<div className={`mb-2 flex w-full flex-col space-y-2 overflow-hidden`}> <div className={`mb-2 flex w-full flex-col space-y-2 overflow-hidden`}>
<TitleContainer title={title} secondaryTitle={secondaryTitle} /> <TitleContainer
title={title}
secondaryTitle={secondaryTitle}
isCompletedStream={isCompletedStream}
/>
{children} {children}
</div> </div>
</div> </div>
@ -77,12 +81,14 @@ VerticalBar.displayName = 'VerticalBar';
const TitleContainer: React.FC<{ const TitleContainer: React.FC<{
title: string; title: string;
secondaryTitle?: string; secondaryTitle?: string;
}> = React.memo(({ title, secondaryTitle }) => { isCompletedStream: boolean;
}> = React.memo(({ title, secondaryTitle, isCompletedStream }) => {
return ( return (
<div className={cn('@container', 'flex w-full items-center space-x-1.5 overflow-hidden')}> <div className={cn('@container', 'flex w-full items-center space-x-1.5 overflow-hidden')}>
<AnimatedThoughtTitle title={title} type="default" /> <AnimatedThoughtTitle title={title} type="default" isCompletedStream={isCompletedStream} />
<AnimatedThoughtTitle <AnimatedThoughtTitle
title={secondaryTitle} title={secondaryTitle}
isCompletedStream={isCompletedStream}
type="tertiary" type="tertiary"
className="secondary-text truncate" className="secondary-text truncate"
/> />
@ -96,22 +102,24 @@ const AnimatedThoughtTitle = React.memo(
({ ({
title, title,
type, type,
isCompletedStream,
className = '' className = ''
}: { }: {
title: string | undefined; title: string | undefined;
type: 'tertiary' | 'default'; type: 'tertiary' | 'default';
className?: string; className?: string;
isCompletedStream: boolean;
}) => { }) => {
const isSecondaryTitle = type === 'tertiary'; const isSecondaryTitle = type === 'tertiary';
return ( return (
<AnimatePresence initial={false} mode="wait"> <AnimatePresence initial={!isCompletedStream && isSecondaryTitle} mode="wait">
{title && ( {title && (
<motion.div <motion.div
className="flex" className="flex"
initial={{ opacity: 0 }} initial={{ opacity: 0 }}
animate={{ opacity: 1 }} animate={{ opacity: 1 }}
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
transition={{ delay: 0.125 }} transition={{ delay: isSecondaryTitle ? 0.5 : 0.125 }}
key={title}> key={title}>
<Text <Text
size="sm" size="sm"

View File

@ -96,7 +96,7 @@ export const ReasoningMessageSelector: React.FC<ReasoningMessageSelectorProps> =
isCompletedStream={isCompletedStream} isCompletedStream={isCompletedStream}
title={title ?? ''} title={title ?? ''}
secondaryTitle={secondary_title ?? ''}> secondaryTitle={secondary_title ?? ''}>
<AnimatePresence mode="wait"> <AnimatePresence mode="wait" initial={!isCompletedStream}>
<motion.div key={animationKey} {...itemAnimationConfig} className="overflow-hidden" layout> <motion.div key={animationKey} {...itemAnimationConfig} className="overflow-hidden" layout>
<div className="min-h-[1px]"> <div className="min-h-[1px]">
<ReasoningMessage <ReasoningMessage

View File

@ -3,7 +3,6 @@ import { ReasoningMessageProps } from '../ReasoningMessageSelector';
import { type BusterChatMessageReasoning_text } from '@/api/asset_interfaces/chat'; import { type BusterChatMessageReasoning_text } from '@/api/asset_interfaces/chat';
import { useMessageIndividual } from '@/context/Chats'; import { useMessageIndividual } from '@/context/Chats';
import { AppMarkdown } from '@/components/ui/typography/AppMarkdown'; import { AppMarkdown } from '@/components/ui/typography/AppMarkdown';
import { StreamingMessage_Text } from '@/components/ui/streaming/StreamingMessage_Text';
export const ReasoningMessage_Text: React.FC<ReasoningMessageProps> = React.memo( export const ReasoningMessage_Text: React.FC<ReasoningMessageProps> = React.memo(
({ reasoningMessageId, messageId, isCompletedStream }) => { ({ reasoningMessageId, messageId, isCompletedStream }) => {
@ -12,7 +11,14 @@ export const ReasoningMessage_Text: React.FC<ReasoningMessageProps> = React.memo
(x) => (x?.reasoning_messages[reasoningMessageId] as BusterChatMessageReasoning_text)?.message (x) => (x?.reasoning_messages[reasoningMessageId] as BusterChatMessageReasoning_text)?.message
)!; )!;
return <AppMarkdown markdown={message} showLoader={!isCompletedStream} stripFormatting />; return (
<AppMarkdown
markdown={message}
showLoader={!isCompletedStream}
className="text-text-secondary text-xs!"
stripFormatting
/>
);
} }
); );

View File

@ -16,7 +16,7 @@ export const ChatContainer = React.memo(() => {
return ( return (
<AppPageLayout <AppPageLayout
header={<ChatHeader showScrollOverflow={showScrollOverflow} />} header={<ChatHeader />}
headerBorderVariant="ghost" headerBorderVariant="ghost"
className="flex h-full w-full min-w-[295px] flex-col"> className="flex h-full w-full min-w-[295px] flex-col">
<ChatContent chatContentRef={chatContentRef} /> <ChatContent chatContentRef={chatContentRef} />

View File

@ -2,7 +2,6 @@ import React from 'react';
import { MessageContainer } from '../MessageContainer'; import { MessageContainer } from '../MessageContainer';
import { ChatResponseMessageSelector } from './ChatResponseMessageSelector'; import { ChatResponseMessageSelector } from './ChatResponseMessageSelector';
import { ChatResponseReasoning } from './ChatResponseReasoning'; import { ChatResponseReasoning } from './ChatResponseReasoning';
import { ShimmerText } from '@/components/ui/typography/ShimmerText';
import { useMessageIndividual } from '@/context/Chats'; import { useMessageIndividual } from '@/context/Chats';
interface ChatResponseMessagesProps { interface ChatResponseMessagesProps {

View File

@ -55,10 +55,12 @@ export const ChatResponseReasoning: React.FC<{
<motion.div <motion.div
{...animations} {...animations}
key={text} key={text}
className="mb-3.5 w-fit cursor-pointer" className="mb-3.5 flex h-[14px] max-h-[14px] w-fit cursor-pointer items-center"
onClick={onClickReasoning}> onClick={onClickReasoning}>
{isReasonginFileSelected ? ( {isReasonginFileSelected ? (
<Text className="hover:underline">{text}</Text> <Text className="text-text-secondary hover:text-text-default hover:underline">
{text}
</Text>
) : ( ) : (
<ShimmerText text={text ?? ''} /> <ShimmerText text={text ?? ''} />
)} )}

View File

@ -11,7 +11,7 @@ export const ChatUserMessage: React.FC<{ requestMessage: BusterChatMessageReques
return ( return (
<MessageContainer senderName={sender_name} senderId={sender_id} senderAvatar={sender_avatar}> <MessageContainer senderName={sender_name} senderId={sender_id} senderAvatar={sender_avatar}>
<Paragraph className="text-sm">{request}</Paragraph> <Paragraph>{request}</Paragraph>
</MessageContainer> </MessageContainer>
); );
} }

View File

@ -5,17 +5,15 @@ import { ChatHeaderOptions } from './ChatHeaderOptions';
import { ChatHeaderTitle } from './ChatHeaderTitle'; import { ChatHeaderTitle } from './ChatHeaderTitle';
import { useChatIndividualContextSelector } from '../../ChatContext'; import { useChatIndividualContextSelector } from '../../ChatContext';
export const ChatHeader: React.FC<{ export const ChatHeader: React.FC<{}> = React.memo(({}) => {
showScrollOverflow: boolean;
}> = React.memo(({ showScrollOverflow }) => {
const hasFile = useChatIndividualContextSelector((state) => state.hasFile); const hasFile = useChatIndividualContextSelector((state) => state.hasFile);
const chatTitle = useChatIndividualContextSelector((state) => state.chatTitle); const chatTitle = useChatIndividualContextSelector((state) => state.chatTitle);
if (!hasFile && !chatTitle) return null; if (!hasFile || !chatTitle) return null;
return ( return (
<> <>
<ChatHeaderTitle /> <ChatHeaderTitle chatTitle={chatTitle} />
<ChatHeaderOptions /> <ChatHeaderOptions />
</> </>
); );

View File

@ -3,7 +3,6 @@
import { Text } from '@/components/ui/typography'; import { Text } from '@/components/ui/typography';
import React from 'react'; import React from 'react';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { useChatIndividualContextSelector } from '../../ChatContext';
const animation = { const animation = {
initial: { opacity: 0 }, initial: { opacity: 0 },
@ -12,8 +11,10 @@ const animation = {
transition: { duration: 0.25 } transition: { duration: 0.25 }
}; };
export const ChatHeaderTitle: React.FC<{}> = React.memo(() => { export const ChatHeaderTitle: React.FC<{
const chatTitle = useChatIndividualContextSelector((state) => state.chatTitle); chatTitle: string;
}> = React.memo(({ chatTitle }) => {
if (!chatTitle) return <div></div>;
return ( return (
<AnimatePresence mode="wait" initial={false}> <AnimatePresence mode="wait" initial={false}>

View File

@ -20,8 +20,12 @@ const useChatIndividualContext = ({
const selectedFileType = selectedFile?.type; const selectedFileType = selectedFile?.type;
//CHAT //CHAT
const { data: chat } = useGetChat({ id: chatId || '' }); const { data: chat } = useGetChat({ id: chatId || '' }, (x) => ({
const hasChat = !!chatId && !!chat; title: x.title,
message_ids: x.message_ids,
id: x.id
}));
const hasChat = !!chatId && !!chat?.id;
const chatTitle = chat?.title; const chatTitle = chat?.title;
const chatMessageIds = chat?.message_ids ?? []; const chatMessageIds = chat?.message_ids ?? [];

View File

@ -11,7 +11,7 @@ export const useAutoChangeLayout = ({
lastMessageId: string; lastMessageId: string;
onSetSelectedFile: (file: SelectedFile) => void; onSetSelectedFile: (file: SelectedFile) => void;
}) => { }) => {
const hasSeeningReasoningPage = useRef(false); //used when there is a delay in page load const previousLastMessageId = useRef<string | null>(null);
const reasoningMessagesLength = useMessageIndividual( const reasoningMessagesLength = useMessageIndividual(
lastMessageId, lastMessageId,
(x) => x?.reasoning_message_ids?.length || 0 (x) => x?.reasoning_message_ids?.length || 0
@ -21,9 +21,10 @@ export const useAutoChangeLayout = ({
//change the page to reasoning file if we get a reasoning message //change the page to reasoning file if we get a reasoning message
useEffect(() => { useEffect(() => {
if (!isCompletedStream && !hasSeeningReasoningPage.current && hasReasoning) { if (!isCompletedStream && hasReasoning && previousLastMessageId.current !== lastMessageId) {
hasSeeningReasoningPage.current = true; // hasSeeningReasoningPage.current = true;
onSetSelectedFile({ id: lastMessageId, type: 'reasoning' }); onSetSelectedFile({ id: lastMessageId, type: 'reasoning' });
previousLastMessageId.current = lastMessageId;
} }
}, [isCompletedStream, hasReasoning]); }, [isCompletedStream, hasReasoning, lastMessageId]);
}; };

View File

@ -4,6 +4,7 @@ import { create } from 'mutative';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import { BusterMetric, IBusterMetric } from '@/api/asset_interfaces/metric'; import { BusterMetric, IBusterMetric } from '@/api/asset_interfaces/metric';
import { createDefaultChartConfig } from './messageAutoChartHandler'; import { createDefaultChartConfig } from './messageAutoChartHandler';
import last from 'lodash/last';
const chatUpgrader = (chat: BusterChat, { isNewChat }: { isNewChat: boolean }): IBusterChat => { const chatUpgrader = (chat: BusterChat, { isNewChat }: { isNewChat: boolean }): IBusterChat => {
return { return {
@ -20,7 +21,7 @@ const chatMessageUpgrader = (
return messageIds.reduce( return messageIds.reduce(
(acc, messageId) => { (acc, messageId) => {
acc[messageId] = create(message[messageId] as IBusterChatMessage, (draft) => { acc[messageId] = create(message[messageId] as IBusterChatMessage, (draft) => {
draft.isCompletedStream = streamingMessageId !== messageId; draft.isCompletedStream = !streamingMessageId || streamingMessageId !== messageId;
return draft; return draft;
}); });
return acc; return acc;
@ -37,7 +38,7 @@ export const updateChatToIChat = (
const iChatMessages = chatMessageUpgrader( const iChatMessages = chatMessageUpgrader(
chat.message_ids, chat.message_ids,
chat.messages, chat.messages,
isNewChat ? chat.message_ids[0] : undefined isNewChat ? last(chat.message_ids) : undefined
); );
return { return {
iChat, iChat,

View File

@ -5,3 +5,31 @@
display: none; display: none;
} }
} }
// This approach has very limited browser support
:root {
--globalScrollTimeline: none; // Define the timeline globally
}
.scroll-shadow-container {
position: relative;
scroll-timeline-name: --globalScrollTimeline;
@apply relative;
.scroll-header {
@apply fixed top-[30px] right-0 left-0 h-2 w-full;
animation: shadowAnimation linear;
animation-range: 0px 125px;
animation-timeline: --globalScrollTimeline;
animation-fill-mode: forwards;
}
@keyframes shadowAnimation {
from {
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
}
to {
box-shadow: 0px 1px 8px 0px #00000029;
}
}
}

View File

@ -45,5 +45,5 @@ body {
} }
p { p {
@apply leading-1.3 text-base; @apply leading-1.3;
} }

View File

@ -40,6 +40,7 @@
--text-3xl--line-height: 1; --text-3xl--line-height: 1;
--text-4xl: 30px; --text-4xl: 30px;
--text-4xl--line-height: 1; --text-4xl--line-height: 1;
--text-size-inherit: inherit;
--text-icon-size: 16px; --text-icon-size: 16px;
--text-icon-size--line-height: 1.05; --text-icon-size--line-height: 1.05;