Skip to content

Commit

Permalink
Merge pull request #72 from airtai/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
harishmohanraj authored Jul 31, 2024
2 parents 8606c2c + d74c31a commit c8f487f
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 119 deletions.
3 changes: 2 additions & 1 deletion app/src/client/app/ChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const chatPage = ({ user }: { user: User }) => {
useEffect(() => {
if (formSubmitMsg && currentChatDetails) {
if (!currentChatDetails.userRespondedWithNextAction) {
setTriggerChatFormSubmitMsg(formSubmitMsg);
setTriggerChatFormSubmitMsg(decodeURIComponent(formSubmitMsg));
}
removeQueryParameters();
}
Expand Down Expand Up @@ -145,6 +145,7 @@ const chatPage = ({ user }: { user: User }) => {
triggerChatFormSubmitMsg={triggerChatFormSubmitMsg}
refetchAllChatDetails={refetchAllChatDetails}
triggerScrollBarMove={triggerScrollBarMove}
setNotificationErrorMessage={setNotificationErrorMessage}
>
<div className='flex h-full flex-col'>
{currentChatDetails ? (
Expand Down
3 changes: 3 additions & 0 deletions app/src/client/app/layout/ChatLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface Props {
triggerChatFormSubmitMsg?: string | null;
refetchAllChatDetails: boolean;
triggerScrollBarMove: boolean;
setNotificationErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
}

const ChatLayout: FC<Props> = ({
Expand All @@ -25,6 +26,7 @@ const ChatLayout: FC<Props> = ({
triggerChatFormSubmitMsg,
refetchAllChatDetails,
triggerScrollBarMove,
setNotificationErrorMessage,
}) => {
const [sidebarOpen, setSidebarOpen] = useState(false);
const [shouldAutoScroll, setShouldAutoScroll] = useState<boolean>(false);
Expand Down Expand Up @@ -75,6 +77,7 @@ const ChatLayout: FC<Props> = ({
handleFormSubmit={handleFormSubmit}
currentChatDetails={currentChatDetails}
triggerChatFormSubmitMsg={triggerChatFormSubmitMsg}
setNotificationErrorMessage={setNotificationErrorMessage}
/>
</div>

Expand Down
203 changes: 88 additions & 115 deletions app/src/client/components/ChatForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,150 +10,123 @@ interface ChatFormProps {
handleFormSubmit: (userQuery: string, isUserRespondedWithNextAction?: boolean, retrySameChat?: boolean) => void;
currentChatDetails: Chat | null | undefined;
triggerChatFormSubmitMsg?: string | null;
setNotificationErrorMessage: React.Dispatch<React.SetStateAction<string | null>>;
}

export default function ChatForm({ handleFormSubmit, currentChatDetails, triggerChatFormSubmitMsg }: ChatFormProps) {
const [formInputValue, setFormInputValue] = useState('');
const [disableFormSubmit, setDisableFormSubmit] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [toggleTextAreaFocus, setToggleTextAreaFocus] = useState(false);
const isProcessing = useRef(false);
const textAreaRef = React.useRef<HTMLTextAreaElement>();
const isEmptyMessage = formInputValue.trim().length === 0;
export default function ChatForm({
handleFormSubmit,
currentChatDetails,
triggerChatFormSubmitMsg,
setNotificationErrorMessage,
}: ChatFormProps) {
const [message, setMessage] = useState<string>('');
const textAreaRef = useRef<HTMLTextAreaElement | null>(null);
const history = useHistory();
const hasTriggerSubmitted = useRef(false);

const formRef = useCallback(
async (node: any) => {
if (node !== null && triggerChatFormSubmitMsg) {
await handleFormSubmit(triggerChatFormSubmitMsg, true);
}
},
[triggerChatFormSubmitMsg]
);
const isInputDisabled = useCallback(() => {
return (
hasTriggerSubmitted.current || currentChatDetails?.team_status === 'inprogress' || currentChatDetails?.showLoader
);
}, [currentChatDetails]);

const setFocusOnTextArea = () => {
if (textAreaRef.current) {
textAreaRef.current.focus();
}
};
useEffect(() => {
const responseCompleted = !disableFormSubmit || !isSubmitting || !isProcessing.current;
if (toggleTextAreaFocus && responseCompleted) {
setFocusOnTextArea();
if (!isInputDisabled()) {
textAreaRef.current?.focus();
}
}, [disableFormSubmit, isSubmitting, isProcessing.current, toggleTextAreaFocus]);

useSocketListener('streamFromTeamFinished', () => {
setToggleTextAreaFocus(true);
});
}, [isInputDisabled]);

useEffect(() => {
if (currentChatDetails) {
setDisableFormSubmit(currentChatDetails.team_status === 'inprogress');
} else {
setDisableFormSubmit(false);
if (currentChatDetails && currentChatDetails.isChatTerminated) {
return;
}
setFocusOnTextArea();
textAreaRef.current?.focus();
}, [currentChatDetails]);

const submitForm = async () => {
if (isSubmitting || disableFormSubmit || isProcessing.current || isEmptyMessage) return;
useSocketListener('streamFromTeamFinished', () => {
textAreaRef.current?.focus();
hasTriggerSubmitted.current = false;
});

const formRef = useCallback(
async (node: HTMLFormElement | null) => {
if (node !== null && triggerChatFormSubmitMsg && !hasTriggerSubmitted.current) {
hasTriggerSubmitted.current = true;
await handleFormSubmit(triggerChatFormSubmitMsg, true);
}
},
[triggerChatFormSubmitMsg, handleFormSubmit]
);

const msgToSubmit = formInputValue.trim();
const submitMessage = async () => {
if (isInputDisabled() || !message.trim()) return;

setIsSubmitting(true);
setToggleTextAreaFocus(false);
isProcessing.current = true;
hasTriggerSubmitted.current = true;

try {
if (!currentChatDetails) {
const chat: Chat = await createNewChat();
history.push(`/chat/${chat.uuid}?initiateChatMsg=${msgToSubmit}`);
setFormInputValue('');
} else if (
currentChatDetails &&
!currentChatDetails.showLoader &&
currentChatDetails.team_status !== 'inprogress'
) {
setFormInputValue('');
handleFormSubmit(msgToSubmit);
const chat = await createNewChat();
history.push(`/chat/${chat.uuid}?initiateChatMsg=${encodeURIComponent(message.trim())}`);
hasTriggerSubmitted.current = false;
} else {
await handleFormSubmit(message.trim());
}
} catch (err: any) {
console.log('Error: ' + err.message);
window.alert('Error: Something went wrong. Please try again later.');
setMessage('');
} catch (err) {
console.error('Error submitting message:', err);
setNotificationErrorMessage('Oops! Something went wrong while sending your message. Please try again later.');
} finally {
setIsSubmitting(false);
isProcessing.current = false;
hasTriggerSubmitted.current = false;
}
};

const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
await submitForm();
};

const handleButtonClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
await submitForm();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
submitMessage();
};

const handleKeyDown = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
await submitForm();
submitMessage();
}
};

return (
<div className='mt-2 mb-2'>
<form data-testid='chat-form' onSubmit={handleSubmit} className='' ref={formRef}>
<label htmlFor='search' className='mb-2 text-sm font-medium text-captn-dark-blue sr-only dark:text-white'>
Search
</label>
<div className='relative bottom-0 left-0 right-0 flex items-center justify-between m-1'>
<TextareaAutosize
autoFocus
minRows={1}
maxRows={4}
style={{
lineHeight: 2,
resize: 'none',
}}
id='userQuery'
name='search'
className='block rounded-lg w-full h-12 text-sm text-white bg-primary focus:outline-none focus:ring-0 focus:border-captn-light-blue'
placeholder='Enter your message...'
required
value={formInputValue}
onChange={(e) => setFormInputValue(e.target.value)}
disabled={disableFormSubmit || isSubmitting}
ref={textAreaRef}
onKeyDown={handleKeyDown}
/>
<button
type='button'
disabled={disableFormSubmit || isSubmitting || isEmptyMessage}
onClick={handleButtonClick}
className={`text-primary bg-secondary hover:opacity-90 absolute right-2 font-medium rounded-lg text-sm px-1.5 py-1.5 ${
disableFormSubmit || isSubmitting || isEmptyMessage
? 'cursor-not-allowed bg-white opacity-70 hover:opacity-70'
: 'cursor-pointer'
}`}
>
<span className=''>
<svg width='20' height='20' viewBox='0 0 24 24' fill='none' className='text-primary'>
<path
d='M7 11L12 6L17 11M12 18V7'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
></path>
</svg>
</span>
</button>
</div>
</form>
</div>
<form data-testid='chat-form' onSubmit={handleSubmit} className='mt-2 mb-2' ref={formRef}>
<div className='relative flex items-center m-1'>
<TextareaAutosize
ref={textAreaRef}
value={message}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setMessage(e.target.value)}
onKeyDown={handleKeyDown}
placeholder='Enter your message...'
minRows={1}
maxRows={4}
className='w-full p-2 text-sm text-white bg-primary rounded-lg focus:outline-none focus:ring-0'
style={{ resize: 'none', lineHeight: '1.5' }}
/>
<button
type='submit'
disabled={isInputDisabled() || !message.trim()}
className={`absolute right-2 p-1.5 rounded-lg ${
isInputDisabled() || !message.trim()
? 'bg-gray-300 cursor-not-allowed'
: 'bg-secondary hover:opacity-90 cursor-pointer'
}`}
aria-label='Send message'
>
<svg width='20' height='20' viewBox='0 0 24 24' fill='none' className='text-primary'>
<path
d='M7 11L12 6L17 11M12 18V7'
stroke='currentColor'
strokeWidth='2'
strokeLinecap='round'
strokeLinejoin='round'
/>
</svg>
</button>
</div>
</form>
);
}
12 changes: 9 additions & 3 deletions app/src/client/tests/ChatForm.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const renderChatForm = (props = {}) => {
handleFormSubmit: vi.fn(),
currentChatDetails: null,
triggerChatFormSubmitMsg: null,
setNotificationErrorMessage: vi.fn(),
};
return renderInContext(<ChatForm {...defaultProps} {...props} />);
};
Expand All @@ -56,7 +57,12 @@ describe('ChatForm', () => {
test('creates a new chat when currentChatDetails is null', async () => {
renderInContext(
<Router history={history}>
<ChatForm handleFormSubmit={vi.fn()} currentChatDetails={null} triggerChatFormSubmitMsg={null} />
<ChatForm
handleFormSubmit={vi.fn()}
currentChatDetails={null}
triggerChatFormSubmitMsg={null}
setNotificationErrorMessage={vi.fn()}
/>
</Router>
);

Expand All @@ -70,7 +76,7 @@ describe('ChatForm', () => {
fireEvent.submit(form);
});

expect(pushSpy).toHaveBeenCalledWith('/chat/new-chat-uuid?initiateChatMsg=New chat message');
expect(pushSpy).toHaveBeenCalledWith('/chat/new-chat-uuid?initiateChatMsg=New%20chat%20message');
expect(mockCreateNewChat).toHaveBeenCalled();
});
});
Expand Down Expand Up @@ -142,7 +148,7 @@ describe('ChatForm', () => {
const input = screen.getByPlaceholderText('Enter your message...');
const submitButton = screen.getByRole('button');

expect(input).toBeDisabled();
expect(input).not.toBeDisabled();
expect(submitButton).toBeDisabled();
});
});
Expand Down

0 comments on commit c8f487f

Please sign in to comment.