Skip to content

Commit

Permalink
fix: fix send message by enter when message is empty (#82)(#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
mikitabut authored Nov 28, 2023
1 parent f2d2838 commit 4cfcd1d
Showing 8 changed files with 107 additions and 104 deletions.
74 changes: 51 additions & 23 deletions src/components/Chat/ChatInput/ChatInputMessage.tsx
Original file line number Diff line number Diff line change
@@ -84,12 +84,35 @@ export const ChatInputMessage = ({
FilesSelectors.selectSelectedFilesIds,
);

const isMessageError = useAppSelector(
ConversationsSelectors.selectIsMessagesError,
);
const isLastAssistantMessageEmpty = useAppSelector(
ConversationsSelectors.selectIsLastAssistantMessageEmpty,
);
const notModelConversations = useAppSelector(
ConversationsSelectors.selectNotModelConversations,
);
const isModelsLoading = useAppSelector(ModelsSelectors.selectModelsIsLoading);
const isError =
isLastAssistantMessageEmpty || (isMessageError && notModelConversations);

const [filteredPrompts, setFilteredPrompts] = useState(() =>
prompts.filter((prompt) =>
prompt.name.toLowerCase().includes(promptInputValue.toLowerCase()),
),
);

const isInputEmpty = useMemo(() => {
return (
(!content || content.trim().length === 0) && selectedFiles.length === 0
);
}, [content, selectedFiles.length]);
const isSendDisabled =
messageIsStreaming ||
isReplay ||
isError ||
isInputEmpty ||
isModelsLoading;
const maxLength = useMemo(() => {
const maxLengthArray = selectedConversations.map(
({ model }) =>
@@ -134,18 +157,13 @@ export const ChatInputMessage = ({
);

const handleSend = useCallback(() => {
if (messageIsStreaming) {
return;
}

if (!content) {
alert(t('Please enter a message'));
if (isSendDisabled) {
return;
}

onSend({
role: Role.User,
content,
content: content!,
...getUserCustomContent(selectedFiles),
});
dispatch(FilesActions.resetSelectedFiles());
@@ -154,15 +172,7 @@ export const ChatInputMessage = ({
if (window.innerWidth < 640 && textareaRef && textareaRef.current) {
textareaRef.current.blur();
}
}, [
content,
dispatch,
selectedFiles,
messageIsStreaming,
onSend,
t,
textareaRef,
]);
}, [isSendDisabled, onSend, content, selectedFiles, dispatch, textareaRef]);

const parseVariables = useCallback((content: string) => {
const regex = /{{(.*?)}}/g;
@@ -302,14 +312,14 @@ export const ChatInputMessage = ({

const handleUnselectFile = useCallback(
(fileId: string) => {
return () => dispatch(FilesActions.unselectFiles({ ids: [fileId] }));
dispatch(FilesActions.unselectFiles({ ids: [fileId] }));
},
[dispatch],
);

const handleRetry = useCallback(
(fileId: string) => {
return () => dispatch(FilesActions.reuploadFile({ fileId }));
dispatch(FilesActions.reuploadFile({ fileId }));
},
[dispatch],
);
@@ -353,6 +363,26 @@ export const ChatInputMessage = ({
[dispatch],
);

const tooltipContent = (): string => {
if (messageIsStreaming) {
return t(
'Please wait for full assistant answer to continue working with chat',
);
}
if (isModelsLoading) {
return t(
'Please wait for models will be loaded to continue working with chat',
);
}
if (isReplay) {
return t('Please continue replay to continue working with chat');
}
if (isError) {
return t('Please regenerate response to continue working with chat');
}
return t('Please type a message');
};

return (
<div className="mx-2 mb-2 flex flex-row gap-3 md:mx-4 md:mb-0 md:last:mb-6 lg:mx-auto lg:max-w-3xl">
<div
@@ -388,10 +418,8 @@ export const ChatInputMessage = ({

<SendMessageButton
handleSend={handleSend}
isInputEmpty={
(!content || content.trim().length === 0) &&
selectedFiles.length === 0
}
isDisabled={isSendDisabled}
tooltip={tooltipContent()}
/>

{displayAttachFunctionality && (
65 changes: 19 additions & 46 deletions src/components/Chat/ChatInput/SendMessageButton.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { IconSend } from '@tabler/icons-react';
import { ReactNode } from 'react';

import { useTranslation } from 'next-i18next';

import { ConversationsSelectors } from '@/src/store/conversations/conversations.reducers';
import { useAppSelector } from '@/src/store/hooks';
import { ModelsSelectors } from '@/src/store/models/models.reducers';
@@ -15,11 +13,6 @@ import {

import { Spinner } from '../../Common/Spinner';

interface Props {
handleSend: () => void;
isInputEmpty: boolean;
}

interface SendIconTooltipProps {
isShowTooltip?: boolean;
tooltipContent?: string;
@@ -45,56 +38,36 @@ const SendIconTooltip = ({
);
};

export const SendMessageButton = ({ handleSend, isInputEmpty }: Props) => {
const { t } = useTranslation('chat');
interface Props {
handleSend: () => void;
isDisabled: boolean;
tooltip?: string;
}

export const SendMessageButton = ({
handleSend,
isDisabled,
tooltip,
}: Props) => {
const isModelsLoading = useAppSelector(ModelsSelectors.selectModelsIsLoading);
const isMessageError = useAppSelector(
ConversationsSelectors.selectIsMessagesError,
);
const isLastAssistantMessageEmpty = useAppSelector(
ConversationsSelectors.selectIsLastAssistantMessageEmpty,
);
const notModelConversations = useAppSelector(
ConversationsSelectors.selectNotModelConversations,
);

const messageIsStreaming = useAppSelector(
ConversationsSelectors.selectIsConversationsStreaming,
);
const isReplay = useAppSelector(
ConversationsSelectors.selectIsReplaySelectedConversations,
);

const isError =
isLastAssistantMessageEmpty || (isMessageError && notModelConversations);

const tooltipContent = (): string => {
if (isReplay) {
return t('Please continue replay to continue working with chat');
}
if (isError) {
return t('Please regenerate response to continue working with chat');
}
return t('Please type a message');
};

const isShowDisabled = isError || isInputEmpty || isReplay;

return (
<button
className="absolute right-4 top-[calc(50%_-_12px)] rounded hover:text-blue-500 disabled:cursor-not-allowed disabled:text-gray-400 disabled:dark:text-gray-600"
onClick={handleSend}
disabled={messageIsStreaming || isModelsLoading || isShowDisabled}
disabled={isDisabled}
>
{messageIsStreaming || isModelsLoading ? (
<Spinner size={20} />
) : (
<SendIconTooltip
isShowTooltip={isShowDisabled}
tooltipContent={tooltipContent()}
>
<SendIconTooltip isShowTooltip={isDisabled} tooltipContent={tooltip}>
{messageIsStreaming || isModelsLoading ? (
<Spinner size={20} />
) : (
<IconSend size={24} stroke="1.5" />
</SendIconTooltip>
)}
)}
</SendIconTooltip>
</button>
);
};
16 changes: 10 additions & 6 deletions src/components/Files/FileItem.tsx
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@ import {
IconReload,
IconX,
} from '@tabler/icons-react';
import { useCallback, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';

import { useTranslation } from 'next-i18next';

@@ -41,11 +41,7 @@ export const FileItem = ({
}: Props) => {
const { t } = useTranslation('files');

const selectedFilesIds: string[] =
(additionalItemData?.selectedFilesIds as string[]) || [];
const [isSelected, setIsSelected] = useState(
selectedFilesIds.includes(item.id),
);
const [isSelected, setIsSelected] = useState(false);
const handleCancelFile = useCallback(() => {
onEvent?.(FileItemEventIds.Cancel, item.id);
}, [item.id, onEvent]);
@@ -63,6 +59,14 @@ export const FileItem = ({
onEvent?.(FileItemEventIds.Remove, item.id);
}, [item.id, onEvent]);

useEffect(() => {
setIsSelected(
((additionalItemData?.selectedFilesIds as string[]) || []).includes(
item.id,
),
);
}, [additionalItemData?.selectedFilesIds, item.id]);

return (
<div
className="group/file-item flex justify-between gap-3 rounded px-3 py-1.5 hover:bg-blue-500/20"
23 changes: 10 additions & 13 deletions src/components/Files/FileManagerModal.tsx
Original file line number Diff line number Diff line change
@@ -28,7 +28,6 @@ import { DialFile } from '@/src/types/files';

import { FilesActions, FilesSelectors } from '@/src/store/files/files.reducers';
import { useAppDispatch, useAppSelector } from '@/src/store/hooks';
import { UIActions } from '@/src/store/ui/ui.reducers';

import FolderPlus from '../../../public/images/icons/folder-plus.svg';
import { ErrorMessage } from '../Common/ErrorMessage';
@@ -74,11 +73,9 @@ export const FileManagerModal = ({

const folders = useAppSelector(FilesSelectors.selectFolders);
const files = useAppSelector(FilesSelectors.selectFiles);
const newFolderId = useAppSelector(FilesSelectors.selectNewAddedFolderId);
const foldersStatus = useAppSelector(FilesSelectors.selectFoldersStatus);
const loadingFolderId = useAppSelector(FilesSelectors.selectLoadingFolderId);
const newAddedFolderId = useAppSelector(
FilesSelectors.selectNewAddedFolderId,
);
const [errorMessage, setErrorMessage] = useState<string | undefined>();
const [openedFoldersIds, setOpenedFoldersIds] = useState<string[]>([]);
const [isAllFilesOpened, setIsAllFilesOpened] = useState(true);
@@ -161,16 +158,17 @@ export const FileManagerModal = ({
);
const handleRenameFolder = useCallback(
(newName: string, folderId: string) => {
const renamingFolder = folders.find((folder) => folder.id === folderId);
const folderWithSameName = folders.find(
(folder) => folder.name === newName && folderId !== folder.id,
(folder) =>
folder.name === newName.trim() &&
folderId !== folder.id &&
folder.folderId === renamingFolder?.folderId,
);

if (folderWithSameName) {
dispatch(
UIActions.showToast({
message: t(`Not allowed to have folders with same names`),
type: 'error',
}),
setErrorMessage(
t(`Not allowed to have folders with same names`) as string,
);
return;
}
@@ -379,9 +377,8 @@ export const FileManagerModal = ({
allFolders={folders}
highlightColor={HighlightColor.Blue}
highlightedFolders={[]}
isInitialRename={
newAddedFolderId === folder.id
}
isInitialRenameEnabled
newAddedFolderId={newFolderId}
displayCaretAlways={true}
loadingFolderId={loadingFolderId}
openedFoldersIds={openedFoldersIds}
9 changes: 3 additions & 6 deletions src/components/Files/SelectFolderModal.tsx
Original file line number Diff line number Diff line change
@@ -53,6 +53,7 @@ export const SelectFolderModal = ({
const headingId = useId();

const folders = useAppSelector(FilesSelectors.selectFolders);
const newFolderId = useAppSelector(FilesSelectors.selectNewAddedFolderId);
const [searchQuery, setSearchQuery] = useState('');
const filteredFolders = useMemo(() => {
return folders.filter(({ name }) =>
@@ -61,9 +62,6 @@ export const SelectFolderModal = ({
}, [folders, searchQuery]);
const foldersStatus = useAppSelector(FilesSelectors.selectFoldersStatus);
const loadingFolderId = useAppSelector(FilesSelectors.selectLoadingFolderId);
const newAddedFolderId = useAppSelector(
FilesSelectors.selectNewAddedFolderId,
);
const [openedFoldersIds, setOpenedFoldersIds] = useState<string[]>([]);
const [isAllFilesOpened, setIsAllFilesOpened] = useState(true);
const [errorMessage, setErrorMessage] = useState<string | undefined>();
@@ -238,9 +236,8 @@ export const SelectFolderModal = ({
allFolders={folders}
highlightColor={HighlightColor.Blue}
highlightedFolders={highlightedFolders}
isInitialRename={
newAddedFolderId === folder.id
}
isInitialRenameEnabled
newAddedFolderId={newFolderId}
loadingFolderId={loadingFolderId}
openedFoldersIds={openedFoldersIds}
onClickFolder={handleFolderSelect}
Loading

0 comments on commit 4cfcd1d

Please sign in to comment.