diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx index c2e47f58b..9744e63a4 100644 --- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx +++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatContent.tsx @@ -2,16 +2,17 @@ import React from 'react'; import { useChatContextSelector } from '../../ChatContext'; import { ChatMessageBlock } from './ChatMessageBlock'; -export const ChatContent: React.FC<{ chatContentRef: React.RefObject }> = ({ - chatContentRef -}) => { - const chatMessages = useChatContextSelector((state) => state.chatMessages); +export const ChatContent: React.FC<{ chatContentRef: React.RefObject }> = + React.memo(({ chatContentRef }) => { + const chatMessages = useChatContextSelector((state) => state.chatMessages); - return ( -
-
- {chatMessages?.map((message) => )} + return ( +
+
+ {chatMessages?.map((message) => )} +
-
- ); -}; + ); + }); + +ChatContent.displayName = 'ChatContent'; diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought.tsx deleted file mode 100644 index e41193cea..000000000 --- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { - BusterChatMessage_thought, - BusterChatMessage_thoughtPill -} from '@/api/buster_socket/chats'; -import React, { useState } from 'react'; -import { ChatResponseMessageProps } from './ChatResponseMessages'; -import { AnimatePresence, motion } from 'framer-motion'; -import { animationConfig } from './animationConfig'; -import { CircleSpinnerLoader } from '@/components/loaders/CircleSpinnerLoader'; -import { Text } from '@/components/text'; -import { createStyles } from 'antd-style'; -import { AppMaterialIcons } from '@/components'; -import { PillContainer } from './ChatResponseMessage_ThoughtPills'; - -export const ChatResponseMessage_Thought: React.FC = React.memo( - ({ responseMessage: responseMessageProp, isCompletedStream }) => { - const responseMessage = responseMessageProp as BusterChatMessage_thought; - const { thought_title, thought_secondary_title, thought_pills, in_progress } = responseMessage; - const { styles, cx } = useStyles(); - const hasPills = thought_pills && thought_pills.length > 0; - - return ( - - -
- - -
-
-
- - {thought_title} - - - {thought_secondary_title} - -
- - -
-
-
- ); - } -); - -ChatResponseMessage_Thought.displayName = 'ChatResponseMessage_Thought'; - -const StatusIndicator: React.FC<{ inProgress?: boolean }> = ({ inProgress }) => { - const { styles, cx } = useStyles(); - return ( -
-
- {inProgress ? ( - - ) : ( - - )} -
-
- ); -}; - -const VerticalBar: React.FC<{ inProgress?: boolean; hasPills?: boolean }> = ({ - inProgress, - hasPills -}) => { - const { styles, cx } = useStyles(); - return ( -
-
-
- ); -}; - -const useStyles = createStyles(({ token, css }) => ({ - container: css` - position: relative; - `, - verticalBar: css` - width: 0.5px; - height: 100%; - background-color: ${token.colorTextPlaceholder}; - `, - indicatorContainer: css` - width: 10px; - height: 10px; - background-color: ${token.colorTextPlaceholder}; - border-radius: 100%; - - &.in-progress { - background-color: transparent; - `, - indicator: css` - color: white; - padding: 1px; - border-radius: 100%; - background-color: ${token.colorTextPlaceholder}; - box-shadow: 0 0 0 0.7px white inset; - - &.in-progress { - background-color: transparent; - box-shadow: none; - } - ` -})); diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/ChatResponseMessage_Thought.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/ChatResponseMessage_Thought.tsx new file mode 100644 index 000000000..1305202a4 --- /dev/null +++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/ChatResponseMessage_Thought.tsx @@ -0,0 +1,48 @@ +import { BusterChatMessage_thought } from '@/api/buster_socket/chats'; +import React from 'react'; +import { ChatResponseMessageProps } from '../ChatResponseMessages'; +import { AnimatePresence, motion } from 'framer-motion'; +import { animationConfig } from '../animationConfig'; +import { Text } from '@/components/text'; +import { createStyles } from 'antd-style'; +import { PillContainer } from './ChatResponseMessage_ThoughtPills'; +import { StatusIndicator } from './StatusIndicator'; +import { VerticalBar } from './VerticalBar'; + +export const ChatResponseMessage_Thought: React.FC = React.memo( + ({ responseMessage: responseMessageProp, isCompletedStream, isLastMessageItem }) => { + const responseMessage = responseMessageProp as BusterChatMessage_thought; + const { thought_title, thought_secondary_title, thought_pills, in_progress } = responseMessage; + const { styles, cx } = useStyles(); + const hasPills = thought_pills && thought_pills.length > 0; + + const showLoadingIndicator = in_progress ?? (isLastMessageItem && !isCompletedStream); + + return ( + + +
+ + +
+
+
+ + {thought_title} + + + {thought_secondary_title} + +
+ + +
+
+
+ ); + } +); + +ChatResponseMessage_Thought.displayName = 'ChatResponseMessage_Thought'; + +const useStyles = createStyles(({ token, css }) => ({})); diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_ThoughtPills.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/ChatResponseMessage_ThoughtPills.tsx similarity index 98% rename from web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_ThoughtPills.tsx rename to web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/ChatResponseMessage_ThoughtPills.tsx index f8f1d668b..79d6fa171 100644 --- a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_ThoughtPills.tsx +++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/ChatResponseMessage_ThoughtPills.tsx @@ -8,7 +8,7 @@ import { AnimatePresence, motion } from 'framer-motion'; import { calculateTextWidth } from '@/utils'; import { useDebounce, useMemoizedFn, useSize } from 'ahooks'; import { AppPopover } from '@/components'; -import { useChatLayoutContextSelector } from '../../../ChatLayoutContext'; +import { useChatLayoutContextSelector } from '../../../../ChatLayoutContext'; const duration = 0.25; diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/StatusIndicator.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/StatusIndicator.tsx new file mode 100644 index 000000000..dcd8def22 --- /dev/null +++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/StatusIndicator.tsx @@ -0,0 +1,68 @@ +import React from 'react'; +import { AnimatePresence, motion } from 'framer-motion'; +import { createStyles } from 'antd-style'; +import { CircleSpinnerLoader } from '@/components/loaders/CircleSpinnerLoader'; +import { AppMaterialIcons } from '@/components/icons'; + +const animationConfig = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, + transition: { duration: 0.25 } +}; + +export const StatusIndicator: React.FC<{ inProgress?: boolean }> = React.memo(({ inProgress }) => { + const { styles, cx } = useStyles(); + return ( +
+ + + {inProgress ? ( + + ) : ( + + )} + + +
+ ); +}); + +StatusIndicator.displayName = 'StatusIndicator'; + +const useStyles = createStyles(({ token, css }) => ({ + indicatorContainer: css` + width: 10px; + height: 10px; + background-color: ${token.colorTextPlaceholder}; + border-radius: 100%; + + &.in-progress { + background-color: transparent; + } + `, + indicator: css` + color: white; + padding: 1px; + border-radius: 100%; + background-color: ${token.colorTextPlaceholder}; + box-shadow: 0 0 0 0.7px white inset; + + &.in-progress { + background-color: transparent; + box-shadow: none; + } + ` +})); diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/VerticalBar.tsx b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/VerticalBar.tsx new file mode 100644 index 000000000..912fa7986 --- /dev/null +++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/VerticalBar.tsx @@ -0,0 +1,31 @@ +import { createStyles } from 'antd-style'; +import React from 'react'; + +export const VerticalBar: React.FC<{ inProgress?: boolean; hasPills?: boolean }> = ({ + inProgress, + hasPills +}) => { + const { styles, cx } = useStyles(); + return ( +
+
+
+ ); +}; + +const useStyles = createStyles(({ token, css }) => ({ + container: css` + position: relative; + `, + verticalBar: css` + width: 0.5px; + height: 100%; + background-color: ${token.colorTextPlaceholder}; + ` +})); diff --git a/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/index.ts b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/index.ts new file mode 100644 index 000000000..ee2df9f52 --- /dev/null +++ b/web/src/app/app/_layouts/ChatLayout/ChatContainer/ChatContent/ChatResponseMessages/ChatResponseMessage_Thought/index.ts @@ -0,0 +1 @@ +export * from './ChatResponseMessage_Thought'; 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 0f8d3b804..d8346caa3 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,15 +1,14 @@ -import React, { useEffect, useRef, useState } from 'react'; -import type { BusterChatMessage_text, BusterChatMessageResponse } from '@/api/buster_socket/chats'; +import React from 'react'; +import type { BusterChatMessageResponse } from '@/api/buster_socket/chats'; import { MessageContainer } from '../MessageContainer'; import { ChatResponseMessage_File } from './ChatResponseMessage_File'; import { ChatResponseMessage_Text } from './ChatResponseMessage_Text'; import { ChatResponseMessage_Thought } from './ChatResponseMessage_Thought'; -import { useHotkeys } from 'react-hotkeys-hook'; -import { faker } from '@faker-js/faker'; export interface ChatResponseMessageProps { responseMessage: BusterChatMessageResponse; isCompletedStream: boolean; + isLastMessageItem: boolean; } const ChatResponseMessageRecord: Record< @@ -28,55 +27,18 @@ interface ChatResponseMessagesProps { export const ChatResponseMessages: React.FC = React.memo( ({ responseMessages, isCompletedStream }) => { - // const [testMessages, setMessages] = useState(responseMessages); - // const replicaOfMessages = useRef(''); - - // useEffect(() => { - // setMessages(responseMessages); - // replicaOfMessages.current = - // (responseMessages as BusterChatMessage_text[])[0]?.message || - // (responseMessages as BusterChatMessage_text[])[0]?.message_chunk || - // ''; - // }, [responseMessages]); - - // const firstMessageId = testMessages[0]?.id; - // useHotkeys('x', () => { - // const threeRandomWords = ' ' + faker.lorem.words(6) + ' swag'; - // setMessages((prevMessages) => { - // return prevMessages.map((message) => { - // if (message.id === firstMessageId) { - // replicaOfMessages.current = replicaOfMessages.current + threeRandomWords; - // return { - // ...message, - // message_chunk: threeRandomWords - // }; - // } - // return message; - // }); - // }); - // }); - - // useHotkeys('z', () => { - // setMessages((prevMessages) => { - // return prevMessages.map((message) => { - // if (message.id === firstMessageId) { - // return { ...message, message: replicaOfMessages.current }; - // } - - // return message; - // }); - // }); - // }); + const lastMessageIndex = responseMessages.length - 1; return ( - {responseMessages.map((responseMessage) => { + {responseMessages.map((responseMessage, index) => { const ChatResponseMessage = ChatResponseMessageRecord[responseMessage.type]; return ( ); })} diff --git a/web/src/context/Chats/ChatProvider.tsx b/web/src/context/Chats/ChatProvider.tsx index 347021c3f..c000245f5 100644 --- a/web/src/context/Chats/ChatProvider.tsx +++ b/web/src/context/Chats/ChatProvider.tsx @@ -5,12 +5,14 @@ import { useContextSelector } from '@fluentui/react-context-selector'; import { useBusterWebSocket } from '../BusterWebSocket'; -import type { BusterChatAsset, BusterChat } from '@/api/buster_socket/chats'; +import type { BusterChatAsset, BusterChat, BusterChatMessage } from '@/api/buster_socket/chats'; import { useMemoizedFn, useUnmount } from 'ahooks'; import type { FileType } from '@/api/buster_socket/chats'; -import { MOCK_CHAT } from './MOCK_CHAT'; +import { createMockResponseMessageThought, MOCK_CHAT } from './MOCK_CHAT'; import { IBusterChat } from './interfaces'; -import { chatUpgrader } from './helpers'; +import { chatMessageUpgrader, chatUpgrader } from './helpers'; +import { useHotkeys } from 'react-hotkeys-hook'; +import { fi } from '@faker-js/faker'; export const useBusterChat = () => { const busterSocket = useBusterWebSocket(); @@ -93,6 +95,25 @@ export const useBusterChat = () => { } ); + useHotkeys('z', () => { + const chatId = Object.keys(chatsRef.current)[0]; + if (chatId) { + const chat = chatsRef.current[chatId]; + const mockMessage = createMockResponseMessageThought(); + const newChat = { ...chat }; + const firstMessage = { + ...newChat.messages[0], + isCompletedStream: false, + response_messages: [...newChat.messages[0].response_messages, mockMessage] + }; + newChat.messages = [firstMessage]; + chatsRef.current[chatId] = newChat; + startTransition(() => { + //just used to trigger UI update + }); + } + }); + return { chats: chatsRef.current, unsubscribeFromChat, diff --git a/web/src/context/Chats/MOCK_CHAT.ts b/web/src/context/Chats/MOCK_CHAT.ts index 9a9574045..d12784245 100644 --- a/web/src/context/Chats/MOCK_CHAT.ts +++ b/web/src/context/Chats/MOCK_CHAT.ts @@ -26,7 +26,7 @@ const createMockResponseMessageText = (): BusterChatMessage_text => ({ message_chunk: faker.lorem.sentence() }); -const createMockResponseMessageThought = (): BusterChatMessage_thought => { +export const createMockResponseMessageThought = (): BusterChatMessage_thought => { const randomPillCount = faker.number.int(7); const fourRandomPills: BusterChatMessage_thoughtPill[] = Array.from( { length: randomPillCount }, @@ -45,7 +45,7 @@ const createMockResponseMessageThought = (): BusterChatMessage_thought => { thought_secondary_title: faker.lorem.word(), thought_pills: fourRandomPills, hidden: false, - in_progress: false + in_progress: undefined }; }; @@ -70,12 +70,12 @@ export const MOCK_CHAT: BusterChat = { request_message: createMockUserMessage(), response_messages: [ createMockResponseMessageText(), - createMockResponseMessageThought(), + createMockResponseMessageThought() // createMockResponseMessageThought(), // createMockResponseMessageThought(), // createMockResponseMessageThought(), - createMockResponseMessageFile(), - createMockResponseMessageFile() + // createMockResponseMessageFile(), + // createMockResponseMessageFile() ] } ],