mirror of https://github.com/buster-so/buster.git
remove chunks for files
This commit is contained in:
parent
fe8ccf4a33
commit
863bcef721
|
@ -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;
|
||||
|
|
|
@ -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};
|
||||
`
|
||||
};
|
||||
});
|
|
@ -23,6 +23,7 @@ export const ReasoningController: React.FC<ReasoningControllerProps> = ({ chatId
|
|||
<ReasoningMessageContainer
|
||||
reasoningMessages={reasoningMessages}
|
||||
isCompletedStream={isCompletedStream}
|
||||
chatId={chatId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in New Issue