remove chunks for files

This commit is contained in:
Nate Kelley 2025-02-10 15:21:59 -07:00
parent fe8ccf4a33
commit 863bcef721
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
15 changed files with 249 additions and 82 deletions

View File

@ -80,11 +80,6 @@ export type BusterChatMessageReasoning_file = {
version_number: number;
version_id: string;
status: 'loading' | 'completed' | 'failed';
file_chunk?: {
text: string;
line_number: number;
modified?: boolean; //defaults to true
}[];
file?: {
text: string;
line_number: number;

View File

@ -0,0 +1,34 @@
import { createStyles } from 'antd-style';
import { Text } from '@/components';
import React from 'react';
export const VersionPill: React.FC<{ version_number: number }> = React.memo(
({ version_number = 1 }) => {
const { cx, styles } = useStyles();
const text = `v${version_number}`;
return (
<div className={cx(styles.fileVersion, 'flex items-center justify-center')}>
<Text type="secondary" lineHeight={'100%'} size="sm">
{text}
</Text>
</div>
);
}
);
VersionPill.displayName = 'VersionPill';
const useStyles = createStyles(({ token, css }) => {
return {
fileVersion: css`
border-radius: ${token.borderRadius}px;
padding: 3px;
background: ${token?.colorFill};
height: 18px;
min-width: 18px;
border: 0.5px solid ${token.colorBorder};
`
};
});

View File

@ -23,6 +23,7 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
<ReasoningMessageContainer
reasoningMessages={reasoningMessages}
isCompletedStream={isCompletedStream}
chatId={chatId}
/>
</div>
);

View File

@ -6,7 +6,8 @@ import { createStyles } from 'antd-style';
export const ReasoningMessageContainer: React.FC<{
reasoningMessages: BusterChatMessageReasoning[];
isCompletedStream: boolean;
}> = React.memo(({ reasoningMessages, isCompletedStream }) => {
chatId: string;
}> = React.memo(({ reasoningMessages, isCompletedStream, chatId }) => {
const { cx, styles } = useStyles();
const lastMessageIndex = reasoningMessages.length - 1;
@ -31,6 +32,7 @@ export const ReasoningMessageContainer: React.FC<{
reasoningMessage={message}
isCompletedStream={isCompletedStream}
isLastMessageItem={index === lastMessageIndex}
chatId={chatId}
/>
<VerticalDivider />
</div>
@ -72,6 +74,17 @@ const useStyles = createStyles(({ token, css }) => ({
.vertical-divider {
display: none;
}
&:has(+ .file-card) {
margin-bottom: 0px;
.vertical-divider {
display: block;
}
}
&:has(+ .thought-card) {
margin-bottom: 14px;
}
`,
verticalDivider: css`
transition: opacity 0.2s ease-in-out;

View File

@ -11,6 +11,7 @@ export interface ReasoningMessageProps {
reasoningMessage: BusterChatMessageReasoning;
isCompletedStream: boolean;
isLastMessageItem: boolean;
chatId: string;
}
const ReasoningMessageRecord: Record<
@ -31,12 +32,14 @@ export interface ReasoningMessageSelectorProps {
reasoningMessage: BusterChatMessageReasoning;
isCompletedStream: boolean;
isLastMessageItem: boolean;
chatId: string;
}
export const ReasoningMessageSelector: React.FC<ReasoningMessageSelectorProps> = ({
reasoningMessage,
isCompletedStream,
isLastMessageItem
isLastMessageItem,
chatId
}) => {
const ReasoningMessage = ReasoningMessageRecord[reasoningMessage.type];
return (
@ -44,6 +47,7 @@ export const ReasoningMessageSelector: React.FC<ReasoningMessageSelectorProps> =
reasoningMessage={reasoningMessage}
isCompletedStream={isCompletedStream}
isLastMessageItem={isLastMessageItem}
chatId={chatId}
/>
);
};

View File

@ -0,0 +1,12 @@
import { TextPulseLoader } from '@/components/loaders';
import React from 'react';
export const LoaderDot = React.memo(() => {
return (
<div className="-mt-0.5 pl-1">
<TextPulseLoader />
</div>
);
});
LoaderDot.displayName = 'LoaderDot';

View File

@ -0,0 +1,54 @@
import type { FileType } from '@/api/asset_interfaces';
import { createChatAssetRoute } from '@appLayouts/ChatLayout/ChatLayoutContext/helpers';
import { AppMaterialIcons, AppTooltip } from '@/components';
import { Button } from 'antd';
import React from 'react';
import { useChatLayoutContextSelector } from '@/app/app/_layouts/ChatLayout';
import { useMemoizedFn } from 'ahooks';
export const ReasoningFileButtons = React.memo(
({
fileType,
fileId,
chatId,
isCompletedStream
}: {
fileType: FileType;
fileId: string;
chatId: string;
isCompletedStream: boolean;
}) => {
if (!isCompletedStream) return null;
const onSetSelectedFile = useChatLayoutContextSelector((state) => state.onSetSelectedFile);
const link = createChatAssetRoute({
chatId: chatId,
assetId: fileId,
type: fileType
});
const onOpenFile = useMemoizedFn((e: React.MouseEvent<HTMLAnchorElement>) => {
e.preventDefault();
e.stopPropagation();
onSetSelectedFile({
id: fileId,
type: fileType
});
});
return (
<div>
<AppTooltip title="Open file">
<Button
href={link}
onClick={onOpenFile}
type="text"
icon={<AppMaterialIcons icon="open_in_new" />}></Button>
</AppTooltip>
</div>
);
}
);
ReasoningFileButtons.displayName = 'ReasoningFileButtons';

View File

@ -0,0 +1,16 @@
import React from 'react';
import { Text } from '@/components';
import { VersionPill } from '@/app/app/_components/Text/VersionPill';
export const ReasoningFileTitle = React.memo(
({ file_name, version_number }: { file_name: string; version_number: number }) => {
return (
<div className="flex items-center gap-1.5">
<Text>{file_name}</Text>
{version_number && <VersionPill version_number={version_number} />}
</div>
);
}
);
ReasoningFileTitle.displayName = 'ReasoningFileTitle';

View File

@ -1,4 +1,4 @@
import React, { useEffect, useMemo, useState } from 'react';
import React, { useEffect, useState } from 'react';
import { ReasoningMessageProps } from '../ReasoningMessageSelector';
import { BusterChatMessageReasoning_file } from '@/api/asset_interfaces';
import {
@ -8,7 +8,10 @@ import {
import { useBusterStylesContext } from '@/context/BusterStyles';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { AnimatePresence, motion } from 'framer-motion';
import { TextPulseLoader } from '@/components/loaders';
import { itemAnimationConfig } from '../animationConfig';
import { LoaderDot } from './LoaderDot';
import { ReasoningFileButtons } from './ReasoningFileButtons';
import { ReasoningFileTitle } from './ReasoningFileTitle';
const style = SyntaxHighlighterLightTheme;
@ -30,8 +33,9 @@ const item = {
};
export const ReasoningMessage_File: React.FC<ReasoningMessageProps> = React.memo(
({ reasoningMessage, isCompletedStream, isLastMessageItem }) => {
const { file, file_name, file_chunk } = reasoningMessage as BusterChatMessageReasoning_file;
({ reasoningMessage, isCompletedStream, isLastMessageItem, chatId }) => {
const { file, file_name, file_type, version_id, version_number } =
reasoningMessage as BusterChatMessageReasoning_file;
const isDarkMode = useBusterStylesContext((s) => s.isDarkMode);
const showLoader = !isCompletedStream && isLastMessageItem;
@ -49,75 +53,73 @@ export const ReasoningMessage_File: React.FC<ReasoningMessageProps> = React.memo
// Append new chunks as they arrive
useEffect(() => {
if (file_chunk) {
if (file) {
setLineMap((prevMap) => {
const newMap = new Map(prevMap);
file_chunk.forEach((chunk) => {
const existingLine = prevMap.get(chunk.line_number) || '';
newMap.set(chunk.line_number, existingLine + chunk.text);
file.forEach((chunk) => {
newMap.set(chunk.line_number, chunk.text);
});
return newMap;
});
}
}, [file_chunk]);
}, [file]);
return (
<AppCodeBlockWrapper
title={file_name}
language={'yaml'}
showCopyButton={false}
isDarkMode={isDarkMode}>
<AnimatePresence>
<motion.div
className="w-full overflow-x-auto p-3"
variants={container}
initial="hidden"
animate="show"
exit="exit">
<div className="border border-red-500">
{Array.from(lineMap.entries()).map(([lineNumber, text]) => (
<motion.div
key={lineNumber}
variants={item}
className="line-number border border-blue-500">
<MemoizedSyntaxHighlighter lineNumber={lineNumber} text={text} />
</motion.div>
))}
</div>
{showLoader && <LoaderDot />}
</motion.div>
</AnimatePresence>
</AppCodeBlockWrapper>
<AnimatePresence initial={!isCompletedStream}>
<motion.div {...itemAnimationConfig}>
<AppCodeBlockWrapper
title={<ReasoningFileTitle file_name={file_name} version_number={version_number} />}
language={'yaml'}
showCopyButton={false}
isDarkMode={isDarkMode}
buttons={
<ReasoningFileButtons
fileType={file_type}
fileId={version_id}
isCompletedStream={isCompletedStream}
chatId={chatId}
/>
}>
<AnimatePresence initial={!isCompletedStream}>
<motion.div
className="w-full overflow-x-auto p-3"
variants={container}
initial="hidden"
animate="show"
exit="exit">
<div className="">
{Array.from(lineMap.entries()).map(([lineNumber, text]) => (
<motion.div key={lineNumber} variants={item} className="line-number w-fit pr-1">
<MemoizedSyntaxHighlighter lineNumber={lineNumber} text={text} />
</motion.div>
))}
</div>
{showLoader && <LoaderDot />}
</motion.div>
</AnimatePresence>
</AppCodeBlockWrapper>
</motion.div>
</AnimatePresence>
);
}
);
const LoaderDot = React.memo(() => {
return (
<div className="-mt-0.5 pl-1">
<TextPulseLoader />
</div>
);
});
LoaderDot.displayName = 'LoaderDot';
ReasoningMessage_File.displayName = 'ReasoningMessage_File';
const lineNumberStyles = { color: '#000' };
const lineNumberStyles: React.CSSProperties = {
minWidth: '2.25em'
};
const MemoizedSyntaxHighlighter = React.memo(
({ lineNumber, text }: { lineNumber: number; text: string }) => {
return (
<SyntaxHighlighter
style={style}
language={'yaml'}
key={lineNumber}
showLineNumbers
wrapLines
wrapLongLines
startingLineNumber={lineNumber}
lineNumberStyle={lineNumberStyles}
className={`!m-0 !border-none !p-0`}>
lineNumberContainerStyle={{ color: 'red' }}
className={`!m-0 !w-fit !border-none !p-0`}>
{text}
</SyntaxHighlighter>
);

View File

@ -12,6 +12,7 @@ import { useMemoizedFn } from 'ahooks';
import { StatusIndicator } from '@/components/indicators';
import { useChatLayoutContextSelector } from '../../../../ChatLayoutContext';
import { useChatIndividualContextSelector } from '../../../../ChatContext';
import { VersionPill } from '@appComponents/Text/VersionPill';
export const ChatResponseMessage_File: React.FC<ChatResponseMessageProps> = React.memo(
({ responseMessage: responseMessageProp, isCompletedStream }) => {
@ -69,19 +70,6 @@ const ChatResponseMessageHeader: React.FC<{ file_name: string; version_number: n
});
ChatResponseMessageHeader.displayName = 'ChatResponseMessageHeader';
const VersionPill: React.FC<{ version_number: number }> = ({ version_number = 1 }) => {
const { cx, styles } = useStyles();
const text = `v${version_number}`;
return (
<div className={cx(styles.fileVersion, 'flex items-center space-x-1.5')}>
<Text type="secondary" lineHeight={11} size="sm">
{text}
</Text>
</div>
);
};
const ChatResponseMessageBody: React.FC<{
metadata: BusterChatMessage_fileMetadata[];
@ -149,11 +137,6 @@ const useStyles = createStyles(({ token, css }) => ({
border-bottom: 0.5px solid ${token.colorBorder};
height: 32px;
`,
fileVersion: css`
border-radius: ${token.borderRadius}px;
padding: 4px;
background: ${token.colorFillTertiary};
`,
hideSecondaryText: css`
container-type: inline-size;
@container (max-width: 190px) {

View File

@ -13,7 +13,7 @@ export const AppCodeBlockWrapper: React.FC<{
language?: string;
showCopyButton?: boolean;
buttons?: React.ReactNode;
title?: string;
title?: string | React.ReactNode;
}> = React.memo(({ children, code, showCopyButton = true, language, buttons, title }) => {
const { cx, styles } = useStyles();
const { openSuccessMessage } = useBusterNotifications();

View File

@ -15,7 +15,7 @@ interface TextProps {
style?: React.CSSProperties;
onClick?: React.MouseEventHandler<HTMLSpanElement>;
children: React.ReactNode;
lineHeight?: number;
lineHeight?: number | string;
}
const useTextStyles = createStyles(({ css, token }) => {
@ -108,7 +108,11 @@ const TextComponent = React.memo<TextProps>(
const memoizedStyles = useMemo(() => {
return {
lineHeight: lineHeight ? `${lineHeight}px` : undefined,
lineHeight: lineHeight
? typeof lineHeight === 'number'
? `${lineHeight}px`
: lineHeight
: undefined,
cursor: type === 'link' ? 'pointer' : undefined,
...props.style
};

View File

@ -89,7 +89,7 @@ export const createMockResponseMessageFile = (): BusterChatMessage_file => {
export const createMockReasoningMessageFile = (): BusterChatMessageReasoning_file => {
return {
id: 'swag',
id: 'swag' + faker.string.uuid(),
type: 'file',
file_type: 'metric',
status: 'completed',
@ -130,6 +130,9 @@ export const MOCK_CHAT: BusterChat = {
reasoning: [
...Array.from({ length: 1 }, () => createMockResponseMessageThought()),
createMockReasoningMessageFile()
// createMockReasoningMessageFile(),
// createMockResponseMessageThought(),
// createMockResponseMessageThought()
],
response_messages: [
createMockResponseMessageText(),

View File

@ -13,10 +13,12 @@ import {
createMockResponseMessageFile,
createMockResponseMessageText,
createMockResponseMessageThought,
createMockReasoningMessageFile,
MOCK_CHAT
} from './MOCK_CHAT';
import { useHotkeys } from 'react-hotkeys-hook';
import { faker } from '@faker-js/faker';
export const useChatSubscriptions = ({
chatsRef,
chatsMessagesRef,
@ -91,10 +93,10 @@ export const useChatSubscriptions = ({
modified: true
};
// Create new reasoning file with updated chunks
// Create new reasoning file with appended chunk
const updatedReasoningFile = {
...lastReasoningFile,
file_chunk: [newChunk]
file: [...(lastReasoningFile.file || []), newChunk]
};
// Create new message with updated reasoning array
@ -129,6 +131,49 @@ export const useChatSubscriptions = ({
});
});
useHotkeys('y', () => {
// Find the last chat message
const lastChatId = Object.keys(chatsRef.current)[Object.keys(chatsRef.current).length - 1];
const lastChat = chatsRef.current[lastChatId];
if (!lastChat?.messages?.length) return;
const lastMessageId = lastChat.messages[lastChat.messages.length - 1];
const lastMessage = chatsMessagesRef.current[lastMessageId];
if (!lastMessage) return;
lastMessage.isCompletedStream = false;
// Create a new reasoning file message
const newReasoningFile = createMockReasoningMessageFile();
// Add the new reasoning file to the reasoning array
const updatedMessage = {
...lastMessage,
reasoning: [...(lastMessage.reasoning || []), newReasoningFile]
};
// Update the refs with new object references
chatsMessagesRef.current = {
...chatsMessagesRef.current,
[lastMessageId]: updatedMessage
};
chatsRef.current = {
...chatsRef.current,
[lastChatId]: {
...lastChat,
messages: [...lastChat.messages]
}
};
startTransition(() => {
// Force a re-render
chatsRef.current = { ...chatsRef.current };
chatsMessagesRef.current = { ...chatsMessagesRef.current };
});
});
return {
unsubscribeFromChat,
subscribeToChat

View File

@ -58,7 +58,8 @@ export const busterAppStyleConfig: ThemeConfig = {
lineHeightHeading3: 1.3,
lineHeightHeading4: 1.3,
lineHeightHeading5: 1.3,
fontFamily: 'Roobert_Pro'
fontFamily: 'Roobert_Pro',
colorFill: '#E6E6E6'
},
components: {
Typography: {