diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx
index d21cb36c1..18b035f0e 100644
--- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx
+++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatMessageBlock.tsx
@@ -6,7 +6,7 @@ import type { IBusterChatMessage } from '@/context/Chats/interfaces';
export const ChatMessageBlock: React.FC<{
message: IBusterChatMessage;
}> = React.memo(({ message }) => {
- const { request_message, response_messages, id, isCompletedStream } = message;
+ const { request_message, response_messages, id, isCompletedStream, reasoning } = message;
return (
@@ -14,6 +14,8 @@ export const ChatMessageBlock: React.FC<{
);
diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessageSelector.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessageSelector.tsx
index 48573c611..8881ea0cc 100644
--- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessageSelector.tsx
+++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessageSelector.tsx
@@ -1,7 +1,9 @@
-import React from 'react';
+import React, { useMemo } from 'react';
import { ChatResponseMessage_File } from './ChatResponseMessage_File';
import { StreamingMessage_Text } from '@appComponents/Streaming/StreamingMessage_Text';
import type { BusterChatMessage_text, BusterChatMessageResponse } from '@/api/asset_interfaces';
+import { createStyles } from 'antd-style';
+import { useMemoizedFn } from 'ahooks';
export interface ChatResponseMessageProps {
responseMessage: BusterChatMessageResponse;
@@ -32,11 +34,74 @@ export const ChatResponseMessageSelector: React.FC {
const messageType = responseMessage.type;
const ChatResponseMessage = ChatResponseMessageRecord[messageType];
+ const { cx, styles } = useStyles();
+
+ const typeClassRecord: Record = useMemo(() => {
+ return {
+ text: cx(styles.textCard, 'text-card'),
+ file: cx(styles.fileCard, 'file-card')
+ };
+ }, []);
+
+ const getContainerClass = useMemoizedFn((item: BusterChatMessageResponse) => {
+ return typeClassRecord[item.type];
+ });
+
return (
-
+
+
+
+
);
};
+
+const VerticalDivider: React.FC<{ className?: string }> = React.memo(({ className }) => {
+ const { cx, styles } = useStyles();
+ return ;
+});
+VerticalDivider.displayName = 'VerticalDivider';
+
+const useStyles = createStyles(({ token, css }) => ({
+ textCard: css`
+ margin-bottom: 14px;
+
+ &:has(+ .text-card) {
+ margin-bottom: 8px;
+ }
+
+ .vertical-divider {
+ display: none;
+ }
+ `,
+ fileCard: css`
+ &:has(+ .text-card) {
+ .vertical-divider {
+ opacity: 0;
+ }
+ }
+
+ &:has(+ .file-card) {
+ .vertical-divider {
+ opacity: 1;
+ }
+ margin-bottom: 1px;
+ }
+
+ &:last-child {
+ .vertical-divider {
+ opacity: 0;
+ }
+ }
+ `,
+ verticalDivider: css`
+ transition: opacity 0.2s ease-in-out;
+ height: 9px;
+ width: 0.5px;
+ margin: 3px 0 3px 16px;
+ background: ${token.colorTextTertiary};
+ `
+}));
diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx
index 4d67005a8..7a8467bfe 100644
--- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx
+++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessages.tsx
@@ -1,46 +1,56 @@
import React, { useMemo } from 'react';
-import type { BusterChatMessageResponse } from '@/api/asset_interfaces';
+import type {
+ BusterChatMessage_text,
+ BusterChatMessageReasoning,
+ BusterChatMessageResponse
+} from '@/api/asset_interfaces';
import { MessageContainer } from '../MessageContainer';
-import { useMemoizedFn } from 'ahooks';
import { ChatResponseMessageSelector } from './ChatResponseMessageSelector';
-import { createStyles } from 'antd-style';
+import { ChatResponseReasoning } from './ChatResponseReasoning';
interface ChatResponseMessagesProps {
responseMessages: BusterChatMessageResponse[];
isCompletedStream: boolean;
+ reasoningMessages: BusterChatMessageReasoning[];
+ messageId: string;
}
export const ChatResponseMessages: React.FC = React.memo(
- ({ responseMessages, isCompletedStream }) => {
- const { styles, cx } = useStyles();
-
- const firstResponseMessage = responseMessages[0];
- const restResponseMessages = responseMessages.slice(1);
+ ({ responseMessages, reasoningMessages, isCompletedStream, messageId }) => {
+ const firstResponseMessage = responseMessages[0] as BusterChatMessage_text;
+ const restResponseMessages = useMemo(() => {
+ if (!firstResponseMessage) return [];
+ return responseMessages.slice(1);
+ }, [firstResponseMessage, responseMessages]);
const lastMessageIndex = responseMessages.length - 1;
- const typeClassRecord: Record = useMemo(() => {
- return {
- text: cx(styles.textCard, 'text-card'),
- file: cx(styles.fileCard, 'file-card')
- };
- }, []);
-
- const getContainerClass = useMemoizedFn((item: BusterChatMessageResponse) => {
- return typeClassRecord[item.type];
- });
-
return (
- {responseMessages.map((responseMessage, index) => (
-
-
-
-
+ {firstResponseMessage && (
+
+ )}
+
+ {firstResponseMessage && (
+
+ )}
+
+ {restResponseMessages.map((responseMessage, index) => (
+
))}
);
@@ -48,52 +58,3 @@ export const ChatResponseMessages: React.FC = React.m
);
ChatResponseMessages.displayName = 'ChatResponseMessages';
-
-const VerticalDivider: React.FC<{ className?: string }> = React.memo(({ className }) => {
- const { cx, styles } = useStyles();
- return ;
-});
-VerticalDivider.displayName = 'VerticalDivider';
-
-const useStyles = createStyles(({ token, css }) => ({
- textCard: css`
- margin-bottom: 14px;
-
- &:has(+ .text-card) {
- margin-bottom: 8px;
- }
-
- .vertical-divider {
- display: none;
- }
- `,
- fileCard: css`
- &:has(+ .text-card),
- &:has(+ .hidden-card) {
- .vertical-divider {
- opacity: 0;
- }
- margin-bottom: 0px;
- }
-
- &:has(+ .thought-card) {
- .vertical-divider {
- opacity: 0;
- }
- margin-bottom: 0px;
- }
-
- &:last-child {
- .vertical-divider {
- opacity: 0;
- }
- }
- `,
- verticalDivider: css`
- transition: opacity 0.2s ease-in-out;
- height: 9px;
- width: 0.5px;
- margin: 3px 0 3px 16px;
- background: ${token.colorTextTertiary};
- `
-}));
diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseReasoning.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseReasoning.tsx
new file mode 100644
index 000000000..2488d8427
--- /dev/null
+++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseReasoning.tsx
@@ -0,0 +1,175 @@
+import { BusterChatMessageReasoning } from '@/api/asset_interfaces';
+import React, { useEffect, useMemo, useState } from 'react';
+import last from 'lodash/last';
+import { ShimmerText } from '@/components/text';
+import { useMemoizedFn } from 'ahooks';
+import { motion } from 'framer-motion';
+import { AnimatePresence } from 'framer-motion';
+import { AppMaterialIcons, Text } from '@/components';
+import { createStyles } from 'antd-style';
+import { useChatLayoutContextSelector } from '../../../ChatLayoutContext';
+
+export const ChatResponseReasoning: React.FC<{
+ reasoningMessages: BusterChatMessageReasoning[];
+ isCompletedStream: boolean;
+ messageId: string;
+}> = React.memo(({ reasoningMessages, isCompletedStream, messageId }) => {
+ const lastMessage = last(reasoningMessages);
+ const onSetSelectedFile = useChatLayoutContextSelector((x) => x.onSetSelectedFile);
+ const selectedFileType = useChatLayoutContextSelector((x) => x.selectedFileType);
+ const isReasonginFileSelected = selectedFileType === 'reasoning';
+
+ const text = useMemo(() => {
+ if (!lastMessage) return null;
+ if (lastMessage.type === 'text') {
+ return lastMessage.message;
+ }
+ return lastMessage.thought_title;
+ }, [lastMessage]);
+
+ const getRandomThought = useMemoizedFn(() => {
+ return DEFAULT_THOUGHTS[Math.floor(Math.random() * DEFAULT_THOUGHTS.length)];
+ });
+
+ const onClickReasoning = useMemoizedFn(() => {
+ onSetSelectedFile({
+ type: 'reasoning',
+ id: messageId
+ });
+ });
+
+ const [thought, setThought] = useState(text || DEFAULT_THOUGHTS[0]);
+
+ useEffect(() => {
+ if (!isCompletedStream && !text) {
+ const randomInterval = Math.floor(Math.random() * 3000) + 1200;
+ const interval = setTimeout(() => {
+ setThought(getRandomThought());
+ }, randomInterval);
+ return () => clearTimeout(interval);
+ }
+ if (text) {
+ setThought(text);
+ }
+ }, [thought, isCompletedStream, text, getRandomThought]);
+
+ const animations = useMemo(() => {
+ return {
+ initial: { opacity: 0 },
+ animate: { opacity: 1 },
+ exit: { opacity: 0 }
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+});
+
+ChatResponseReasoning.displayName = 'ChatThoughts';
+
+const DEFAULT_THOUGHTS = [
+ 'Thinking through next steps...',
+ 'Looking through context...',
+ 'Reflecting on the instructions...',
+ 'Analyzing available actions',
+ 'Reviewing the objective...',
+ 'Deciding feasible options...',
+ 'Sorting out some details...',
+ 'Exploring other possibilities...',
+ 'Confirming things....',
+ 'Mapping information across files...',
+ 'Making a few edits...',
+ 'Filling out arguments...',
+ 'Double-checking the logic...',
+ 'Validating my approach...',
+ 'Looking at a few edge cases...',
+ 'Ensuring everything aligns...',
+ 'Polishing the details...',
+ 'Making some adjustments...',
+ 'Writing out arguments...',
+ 'Mapping trends and patterns...',
+ 'Re-evaluating this step...',
+ 'Updating parameters...',
+ 'Evaluating available data...',
+ 'Reviewing all parameters...',
+ 'Processing relevant info...',
+ 'Aligning with user request...',
+ 'Gathering necessary details...',
+ 'Sorting through options...',
+ 'Editing my system logic...',
+ 'Cross-checking references...',
+ 'Validating my approach...',
+ 'Rewriting operational details...',
+ 'Mapping new information...',
+ 'Adjusting priorities & approach...',
+ 'Revisiting earlier inputs...',
+ 'Finalizing plan details...'
+];
+
+const ShimmerTextWithIcon = React.memo(
+ ({
+ text,
+ isCompletedStream,
+ isSelected
+ }: {
+ text: string;
+ isCompletedStream: boolean;
+ isSelected: boolean;
+ }) => {
+ const { cx, styles } = useStyles();
+
+ if (isCompletedStream) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ }
+);
+ShimmerTextWithIcon.displayName = 'ShimmerTextWithIcon';
+
+const useStyles = createStyles(({ token, css }) => ({
+ iconContainerCompleted: css`
+ color: ${token.colorIcon};
+ &:hover {
+ color: ${token.colorText};
+ }
+ &.is-selected {
+ color: ${token.colorText};
+ }
+ `,
+ iconContainer: css`
+ cursor: pointer;
+ `,
+ icon: css`
+ color: ${token.colorIcon};
+ `
+}));
diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatThoughts.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatThoughts.tsx
deleted file mode 100644
index 2943ccbd5..000000000
--- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatThoughts.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import { BusterChatMessageReasoning } from '@/api/asset_interfaces';
-import React, { useMemo } from 'react';
-import last from 'lodash/last';
-import { ShimmerText } from '@/components/text';
-
-export const ChatThoughts: React.FC<{
- reasoningMessages: BusterChatMessageReasoning[];
-}> = React.memo(({ reasoningMessages }) => {
- const lastMessage = last(reasoningMessages);
-
- const lastMessageTest = useMemo(() => {
- if (!lastMessage) return null;
- if (lastMessage.type === 'text') {
- return lastMessage;
- }
- return lastMessage.thought_title;
- }, [lastMessage]);
-
- const hasLastMessage = !!lastMessage || !!lastMessageTest;
-
- if (!hasLastMessage) return null;
-
- return ChatThoughts
;
-});
-
-ChatThoughts.displayName = 'ChatThoughts';
-
-const DEFAULT_THOUGHTS = [
- 'Thinking through next steps...',
- 'Looking through context...',
- 'Reflecting on the instructions...',
- 'Analyzing available actions',
- 'Reviewing the objective...',
- 'Deciding feasible options...',
- 'Sorting out some details...',
- 'Exploring other possibilities...',
- 'Confirming things....',
- 'Mapping information across files...',
- 'Making a few edits...',
- 'Filling out arguments...',
- 'Double-checking the logic...',
- 'Validating my approach...',
- 'Looking at a few edge cases...',
- 'Ensuring everything aligns...',
- 'Polishing the details...',
- 'Making some adjustments...',
- 'Writing out arguments...',
- 'Mapping trends and patterns...',
- 'Re-evaluating this step...',
- 'Updating parameters...',
- 'Evaluating available data...',
- 'Reviewing all parameters...',
- 'Processing relevant info...',
- 'Aligning with user request...',
- 'Gathering necessary details...',
- 'Sorting through options...',
- 'Editing my system logic...',
- 'Cross-checking references...',
- 'Validating my approach...',
- 'Rewriting operational details...',
- 'Mapping new information...',
- 'Adjusting priorities & approach...',
- 'Revisiting earlier inputs...',
- 'Finalizing plan details...'
-];
-
-const RandomThoughts = React.memo(() => {
- const randomThought = DEFAULT_THOUGHTS[Math.floor(Math.random() * DEFAULT_THOUGHTS.length)];
- return {randomThought}
;
-});
-
-RandomThoughts.displayName = 'RandomThoughts';
diff --git a/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/ChatLayoutContext.tsx b/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/ChatLayoutContext.tsx
index 75ba48aa6..21dd549c3 100644
--- a/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/ChatLayoutContext.tsx
+++ b/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/ChatLayoutContext.tsx
@@ -3,7 +3,7 @@ import {
createContext,
useContextSelector
} from '@fluentui/react-context-selector';
-import React, { PropsWithChildren, useState, useTransition } from 'react';
+import React, { PropsWithChildren, useTransition } from 'react';
import type { SelectedFile } from '../interfaces';
import type { ChatSplitterProps } from '../ChatLayout';
import { useMemoizedFn } from 'ahooks';
@@ -51,7 +51,7 @@ export const useChatLayout = ({
const fileType = file.type;
const fileId = file.id;
const route =
- isChatView && chatId
+ isChatView && chatId !== undefined
? createChatAssetRoute({ chatId, assetId: fileId, type: fileType })
: createFileRoute({ assetId: fileId, type: fileType });
diff --git a/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/publicHelpers.ts b/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/publicHelpers.ts
index 2752c921f..874b4f3f5 100644
--- a/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/publicHelpers.ts
+++ b/web/src/app/app/_layouts/ChatLayout/ChatLayoutContext/publicHelpers.ts
@@ -1,6 +1,7 @@
import type { ThoughtFileType, FileType } from '@/api/asset_interfaces';
-export const isOpenableFile = (type: ThoughtFileType): type is FileType => {
- const validTypes: FileType[] = ['metric', 'dashboard'];
- return validTypes.includes(type as FileType);
+const OPENABLE_FILES = new Set(['metric', 'dashboard', 'reasoning']);
+
+export const isOpenableFile = (type: ThoughtFileType): boolean => {
+ return OPENABLE_FILES.has(type);
};
diff --git a/web/src/app/app/_layouts/ChatLayout/hooks/useDefaultFile.ts b/web/src/app/app/_layouts/ChatLayout/hooks/useDefaultFile.ts
index c1b724892..35dbc93cf 100644
--- a/web/src/app/app/_layouts/ChatLayout/hooks/useDefaultFile.ts
+++ b/web/src/app/app/_layouts/ChatLayout/hooks/useDefaultFile.ts
@@ -36,7 +36,5 @@ export const useSelectedFileByParams = () => {
return 'chat';
}, [metricId, collectionId, datasetId, dashboardId, chatId]);
- console.log(selectedFile, selectedLayout, chatId);
-
return { selectedFile, selectedLayout, chatId };
};
diff --git a/web/src/context/Chats/ChatProvider/MOCK_CHAT.ts b/web/src/context/Chats/ChatProvider/MOCK_CHAT.ts
index a50bb8991..8c60b563f 100644
--- a/web/src/context/Chats/ChatProvider/MOCK_CHAT.ts
+++ b/web/src/context/Chats/ChatProvider/MOCK_CHAT.ts
@@ -90,6 +90,7 @@ export const MOCK_CHAT: BusterChat = {
response_messages: [
createMockResponseMessageText(),
createMockResponseMessageFile(),
+ createMockResponseMessageFile(),
createMockResponseMessageText(),
createMockResponseMessageText()
]
diff --git a/web/src/context/Chats/ChatProvider/useChatSubscriptions.ts b/web/src/context/Chats/ChatProvider/useChatSubscriptions.ts
index 60bee8a07..04956e49a 100644
--- a/web/src/context/Chats/ChatProvider/useChatSubscriptions.ts
+++ b/web/src/context/Chats/ChatProvider/useChatSubscriptions.ts
@@ -4,7 +4,13 @@ import { useMemoizedFn } from 'ahooks';
import { BusterChat } from '@/api/asset_interfaces';
import { IBusterChat } from '../interfaces';
import { chatUpgrader } from './helpers';
-import { MOCK_CHAT } from './MOCK_CHAT';
+import {
+ createMockResponseMessageFile,
+ createMockResponseMessageText,
+ createMockResponseMessageThought,
+ MOCK_CHAT
+} from './MOCK_CHAT';
+import { useHotkeys } from 'react-hotkeys-hook';
export const useChatSubscriptions = ({
chatsRef,
@@ -47,6 +53,73 @@ export const useChatSubscriptions = ({
// });
});
+ useHotkeys('t', () => {
+ const newThoughts = createMockResponseMessageThought();
+ const myChat = {
+ ...chatsRef.current[MOCK_CHAT.id]!,
+ messages: [
+ {
+ ...chatsRef.current[MOCK_CHAT.id]!.messages[0],
+ reasoning: [...chatsRef.current[MOCK_CHAT.id]!.messages[0].reasoning, newThoughts],
+ isCompletedStream: false
+ }
+ ]
+ };
+
+ chatsRef.current[MOCK_CHAT.id] = myChat;
+
+ startTransition(() => {
+ // Create a new reference to trigger React update
+ chatsRef.current = { ...chatsRef.current };
+ });
+ });
+
+ useHotkeys('m', () => {
+ const newTextMessage = createMockResponseMessageText();
+ const myChat = {
+ ...chatsRef.current[MOCK_CHAT.id]!,
+ messages: [
+ {
+ ...chatsRef.current[MOCK_CHAT.id]!.messages[0],
+ response_messages: [
+ ...chatsRef.current[MOCK_CHAT.id]!.messages[0]!.response_messages,
+ newTextMessage
+ ],
+ isCompletedStream: false
+ }
+ ]
+ };
+
+ chatsRef.current[MOCK_CHAT.id] = myChat;
+
+ startTransition(() => {
+ chatsRef.current = { ...chatsRef.current };
+ });
+ });
+
+ useHotkeys('f', () => {
+ const newFileMessage = createMockResponseMessageFile();
+ const myChat = {
+ ...chatsRef.current[MOCK_CHAT.id]!,
+ messages: [
+ {
+ ...chatsRef.current[MOCK_CHAT.id]!.messages[0],
+ response_messages: [
+ ...chatsRef.current[MOCK_CHAT.id]!.messages[0]!.response_messages,
+ newFileMessage
+ ],
+ isCompletedStream: false
+ }
+ ]
+ };
+
+ chatsRef.current[MOCK_CHAT.id] = myChat;
+
+ startTransition(() => {
+ chatsRef.current = { ...chatsRef.current };
+ });
+ });
+
return {
unsubscribeFromChat,
subscribeToChat
diff --git a/web/src/routes/busterRoutes/busterAppRoutes.ts b/web/src/routes/busterRoutes/busterAppRoutes.ts
index 9ae606cfd..e227623d9 100644
--- a/web/src/routes/busterRoutes/busterAppRoutes.ts
+++ b/web/src/routes/busterRoutes/busterAppRoutes.ts
@@ -24,7 +24,7 @@ export enum BusterAppRoutes {
//NEW CHAT
APP_CHAT_ID = '/app/chat/:chatId',
- APP_CHAT_ID_REASONING_ID = '/app/chat/:chatId/reasoning/:reasoningId',
+ APP_CHAT_ID_REASONING_ID = '/app/chat/:chatId/reasoning/:messageId',
APP_CHAT_ID_METRIC_ID = '/app/chat/:chatId/metric/:metricId',
APP_CHAT_ID_COLLECTION_ID = '/app/chat/:chatId/collection/:collectionId',
APP_CHAT_ID_DASHBOARD_ID = '/app/chat/:chatId/dashboard/:dashboardId',