mirror of https://github.com/buster-so/buster.git
thought transitions complete
This commit is contained in:
parent
2d8d2baa6f
commit
f41c4da9ff
|
@ -2,9 +2,8 @@ import React from 'react';
|
|||
import { useChatContextSelector } from '../../ChatContext';
|
||||
import { ChatMessageBlock } from './ChatMessageBlock';
|
||||
|
||||
export const ChatContent: React.FC<{ chatContentRef: React.RefObject<HTMLDivElement> }> = ({
|
||||
chatContentRef
|
||||
}) => {
|
||||
export const ChatContent: React.FC<{ chatContentRef: React.RefObject<HTMLDivElement> }> =
|
||||
React.memo(({ chatContentRef }) => {
|
||||
const chatMessages = useChatContextSelector((state) => state.chatMessages);
|
||||
|
||||
return (
|
||||
|
@ -14,4 +13,6 @@ export const ChatContent: React.FC<{ chatContentRef: React.RefObject<HTMLDivElem
|
|||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
ChatContent.displayName = 'ChatContent';
|
||||
|
|
|
@ -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<ChatResponseMessageProps> = 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 (
|
||||
<AnimatePresence initial={!isCompletedStream}>
|
||||
<motion.div className={cx(styles.container, 'flex space-x-1.5')} {...animationConfig}>
|
||||
<div className="flex w-4 min-w-4 flex-col items-center pt-0.5">
|
||||
<StatusIndicator inProgress={in_progress} />
|
||||
<VerticalBar inProgress={in_progress} hasPills={hasPills} />
|
||||
</div>
|
||||
<div className="flex w-full flex-col space-y-2">
|
||||
<div className="flex w-full items-center space-x-1.5 overflow-hidden">
|
||||
<Text size="sm" className="truncate">
|
||||
{thought_title}
|
||||
</Text>
|
||||
<Text size="sm" type="tertiary">
|
||||
{thought_secondary_title}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<PillContainer pills={thought_pills} isCompletedStream={isCompletedStream} />
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ChatResponseMessage_Thought.displayName = 'ChatResponseMessage_Thought';
|
||||
|
||||
const StatusIndicator: React.FC<{ inProgress?: boolean }> = ({ inProgress }) => {
|
||||
const { styles, cx } = useStyles();
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
styles.indicatorContainer,
|
||||
inProgress && 'in-progress',
|
||||
'flex items-center justify-center'
|
||||
)}>
|
||||
<div
|
||||
className={cx(
|
||||
styles.indicator,
|
||||
inProgress && 'in-progress',
|
||||
'flex items-center justify-center'
|
||||
)}>
|
||||
{inProgress ? (
|
||||
<CircleSpinnerLoader size={8} />
|
||||
) : (
|
||||
<AppMaterialIcons className="" icon="check" size={6} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const VerticalBar: React.FC<{ inProgress?: boolean; hasPills?: boolean }> = ({
|
||||
inProgress,
|
||||
hasPills
|
||||
}) => {
|
||||
const { styles, cx } = useStyles();
|
||||
return (
|
||||
<div
|
||||
className={cx(
|
||||
'flex w-full flex-1 items-center justify-center overflow-hidden',
|
||||
// 'opacity-0',
|
||||
'transition-opacity duration-300',
|
||||
hasPills && 'opacity-100'
|
||||
)}>
|
||||
<div className={cx(styles.verticalBar, 'mt-1 overflow-hidden')} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
`
|
||||
}));
|
|
@ -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<ChatResponseMessageProps> = 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 (
|
||||
<AnimatePresence initial={!isCompletedStream}>
|
||||
<motion.div className={cx('relative flex space-x-1.5')} {...animationConfig}>
|
||||
<div className="flex w-4 min-w-4 flex-col items-center pt-0.5">
|
||||
<StatusIndicator inProgress={showLoadingIndicator} />
|
||||
<VerticalBar inProgress={in_progress} hasPills={hasPills} />
|
||||
</div>
|
||||
<div className="flex w-full flex-col space-y-2">
|
||||
<div className="flex w-full items-center space-x-1.5 overflow-hidden">
|
||||
<Text size="sm" className="truncate">
|
||||
{thought_title}
|
||||
</Text>
|
||||
<Text size="sm" type="tertiary">
|
||||
{thought_secondary_title}
|
||||
</Text>
|
||||
</div>
|
||||
|
||||
<PillContainer pills={thought_pills} isCompletedStream={isCompletedStream} />
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ChatResponseMessage_Thought.displayName = 'ChatResponseMessage_Thought';
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({}));
|
|
@ -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;
|
||||
|
|
@ -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 (
|
||||
<div
|
||||
className={cx(
|
||||
styles.indicatorContainer,
|
||||
inProgress && 'in-progress',
|
||||
'flex items-center justify-center transition-all delay-100 duration-300'
|
||||
)}>
|
||||
<AnimatePresence mode="wait">
|
||||
<motion.div
|
||||
key={inProgress ? 'in-progress' : 'completed'}
|
||||
{...animationConfig}
|
||||
className={cx(
|
||||
styles.indicator,
|
||||
inProgress && 'in-progress',
|
||||
'flex items-center justify-center transition-all duration-300'
|
||||
)}>
|
||||
{inProgress ? (
|
||||
<CircleSpinnerLoader size={8} />
|
||||
) : (
|
||||
<AppMaterialIcons className="" icon="check" size={6} />
|
||||
)}
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
`
|
||||
}));
|
|
@ -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 (
|
||||
<div
|
||||
className={cx(
|
||||
'flex w-full flex-1 items-center justify-center overflow-hidden',
|
||||
// 'opacity-0',
|
||||
'transition-opacity duration-300',
|
||||
hasPills && 'opacity-100'
|
||||
)}>
|
||||
<div className={cx(styles.verticalBar, 'mt-1 overflow-hidden')} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const useStyles = createStyles(({ token, css }) => ({
|
||||
container: css`
|
||||
position: relative;
|
||||
`,
|
||||
verticalBar: css`
|
||||
width: 0.5px;
|
||||
height: 100%;
|
||||
background-color: ${token.colorTextPlaceholder};
|
||||
`
|
||||
}));
|
|
@ -0,0 +1 @@
|
|||
export * from './ChatResponseMessage_Thought';
|
|
@ -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<ChatResponseMessagesProps> = React.memo(
|
||||
({ responseMessages, isCompletedStream }) => {
|
||||
// const [testMessages, setMessages] = useState<BusterChatMessageResponse[]>(responseMessages);
|
||||
// const replicaOfMessages = useRef<string>('');
|
||||
|
||||
// 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 (
|
||||
<MessageContainer className="flex w-full flex-col space-y-1">
|
||||
{responseMessages.map((responseMessage) => {
|
||||
{responseMessages.map((responseMessage, index) => {
|
||||
const ChatResponseMessage = ChatResponseMessageRecord[responseMessage.type];
|
||||
return (
|
||||
<ChatResponseMessage
|
||||
key={responseMessage.id}
|
||||
responseMessage={responseMessage}
|
||||
isCompletedStream={isCompletedStream}
|
||||
isLastMessageItem={index === lastMessageIndex}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue