mirror of https://github.com/buster-so/buster.git
Merge branch 'big-nate/bus-939-create-new-structure-for-chats' into evals
This commit is contained in:
commit
2d99ad32b7
|
@ -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
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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) =>
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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) => (
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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);
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
/>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 ?? ''} />
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
|
@ -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 ?? [];
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -45,5 +45,5 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@apply leading-1.3 text-base;
|
@apply leading-1.3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in New Issue