mirror of https://github.com/buster-so/buster.git
Merge pull request #444 from buster-so/big-nate/bus-1327-prevent-spam-on-button-click
Big nate/bus 1327 prevent spam on button click
This commit is contained in:
commit
356e870145
|
@ -16,7 +16,7 @@ import { useAppLayoutContextSelector } from '../BusterAppLayout';
|
||||||
import { BusterRoutes } from '@/routes/busterRoutes';
|
import { BusterRoutes } from '@/routes/busterRoutes';
|
||||||
|
|
||||||
export const useBusterNewChat = () => {
|
export const useBusterNewChat = () => {
|
||||||
const { mutateAsync: startNewChat } = useStartNewChat();
|
const { mutateAsync: startNewChat, isPending: isSubmittingChat } = useStartNewChat();
|
||||||
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage);
|
||||||
const getChatMessageMemoized = useGetChatMessageMemoized();
|
const getChatMessageMemoized = useGetChatMessageMemoized();
|
||||||
const getChatMemoized = useGetChatMemoized();
|
const getChatMemoized = useGetChatMemoized();
|
||||||
|
@ -57,7 +57,6 @@ export const useBusterNewChat = () => {
|
||||||
message_id: messageId
|
message_id: messageId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
initializeNewChat(res);
|
initializeNewChat(res);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -149,7 +148,8 @@ export const useBusterNewChat = () => {
|
||||||
onFollowUpChat,
|
onFollowUpChat,
|
||||||
onStartChatFromFile,
|
onStartChatFromFile,
|
||||||
onReplaceMessageInChat,
|
onReplaceMessageInChat,
|
||||||
onStopChat
|
onStopChat,
|
||||||
|
isSubmittingChat
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,13 @@ import { NewChatInput } from './NewChatInput';
|
||||||
import { NewChatWarning } from './NewChatWarning';
|
import { NewChatWarning } from './NewChatWarning';
|
||||||
import { useNewChatWarning } from './useNewChatWarning';
|
import { useNewChatWarning } from './useNewChatWarning';
|
||||||
|
|
||||||
|
enum TimeOfDay {
|
||||||
|
MORNING = 'morning',
|
||||||
|
AFTERNOON = 'afternoon',
|
||||||
|
EVENING = 'evening',
|
||||||
|
NIGHT = 'night'
|
||||||
|
}
|
||||||
|
|
||||||
export const HomePageController: React.FC<Record<string, never>> = () => {
|
export const HomePageController: React.FC<Record<string, never>> = () => {
|
||||||
const newChatWarningProps = useNewChatWarning();
|
const newChatWarningProps = useNewChatWarning();
|
||||||
const { showWarning } = newChatWarningProps;
|
const { showWarning } = newChatWarningProps;
|
||||||
|
@ -15,18 +22,35 @@ export const HomePageController: React.FC<Record<string, never>> = () => {
|
||||||
const user = useUserConfigContextSelector((state) => state.user);
|
const user = useUserConfigContextSelector((state) => state.user);
|
||||||
const userName = user?.name;
|
const userName = user?.name;
|
||||||
|
|
||||||
const isMorning = useMemo(() => {
|
const timeOfDay = useMemo(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const hours = now.getHours();
|
const hours = now.getHours();
|
||||||
return hours < 12;
|
|
||||||
|
if (hours >= 5 && hours < 12) {
|
||||||
|
return TimeOfDay.MORNING;
|
||||||
|
} else if (hours >= 12 && hours < 17) {
|
||||||
|
return TimeOfDay.AFTERNOON;
|
||||||
|
} else if (hours >= 17 && hours < 21) {
|
||||||
|
return TimeOfDay.EVENING;
|
||||||
|
} else {
|
||||||
|
return TimeOfDay.NIGHT;
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const greeting = useMemo(() => {
|
const greeting = useMemo(() => {
|
||||||
if (isMorning) {
|
switch (timeOfDay) {
|
||||||
return `Good morning, ${userName}`;
|
case TimeOfDay.MORNING:
|
||||||
|
return `Good morning, ${userName}`;
|
||||||
|
case TimeOfDay.AFTERNOON:
|
||||||
|
return `Good afternoon, ${userName}`;
|
||||||
|
case TimeOfDay.EVENING:
|
||||||
|
return `Good evening, ${userName}`;
|
||||||
|
case TimeOfDay.NIGHT:
|
||||||
|
return `Good night, ${userName}`;
|
||||||
|
default:
|
||||||
|
return `Hello, ${userName}`;
|
||||||
}
|
}
|
||||||
return `Good afternoon, ${userName}`;
|
}, [timeOfDay, userName]);
|
||||||
}, [userName]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center p-4.5">
|
<div className="flex flex-col items-center justify-center p-4.5">
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const NewChatInput: React.FC<Record<string, never>> = () => {
|
||||||
}, [inputValue]);
|
}, [inputValue]);
|
||||||
|
|
||||||
const onSubmit = useMemoizedFn(async (value: string) => {
|
const onSubmit = useMemoizedFn(async (value: string) => {
|
||||||
if (disabledSubmit) return;
|
if (disabledSubmit || loading) return;
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const trimmedValue = value.trim();
|
const trimmedValue = value.trim();
|
||||||
|
@ -32,12 +32,6 @@ export const NewChatInput: React.FC<Record<string, never>> = () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const onStop = useMemoizedFn(() => {
|
|
||||||
setLoading(false);
|
|
||||||
textAreaRef.current?.focus();
|
|
||||||
textAreaRef.current?.select();
|
|
||||||
});
|
|
||||||
|
|
||||||
const onChange = useMemoizedFn((e: ChangeEvent<HTMLTextAreaElement>) => {
|
const onChange = useMemoizedFn((e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setInputValue(e.target.value);
|
setInputValue(e.target.value);
|
||||||
});
|
});
|
||||||
|
@ -56,7 +50,6 @@ export const NewChatInput: React.FC<Record<string, never>> = () => {
|
||||||
autoResize={autoResizeConfig}
|
autoResize={autoResizeConfig}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onStop={onStop}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={false}
|
disabled={false}
|
||||||
disabledSubmit={disabledSubmit}
|
disabledSubmit={disabledSubmit}
|
||||||
|
|
|
@ -11,8 +11,9 @@ const autoResizeConfig = { minRows: 2, maxRows: 16 };
|
||||||
|
|
||||||
export const ChatInput: React.FC = React.memo(() => {
|
export const ChatInput: React.FC = React.memo(() => {
|
||||||
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
const textAreaRef = useRef<HTMLTextAreaElement>(null);
|
||||||
const loading = useChatIndividualContextSelector((x) => x.isStreamingMessage);
|
const isStreamingMessage = useChatIndividualContextSelector((x) => x.isStreamingMessage);
|
||||||
const hasChat = useChatIndividualContextSelector((x) => x.hasChat);
|
const hasChat = useChatIndividualContextSelector((x) => x.hasChat);
|
||||||
|
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
const disableSubmit = useMemo(() => {
|
const disableSubmit = useMemo(() => {
|
||||||
|
@ -23,7 +24,7 @@ export const ChatInput: React.FC = React.memo(() => {
|
||||||
disableSubmit,
|
disableSubmit,
|
||||||
inputValue,
|
inputValue,
|
||||||
setInputValue,
|
setInputValue,
|
||||||
loading,
|
loading: isStreamingMessage,
|
||||||
textAreaRef
|
textAreaRef
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ export const ChatInput: React.FC = React.memo(() => {
|
||||||
onSubmit={onSubmitPreflight}
|
onSubmit={onSubmitPreflight}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onStop={onStopChat}
|
onStop={onStopChat}
|
||||||
loading={loading}
|
loading={isStreamingMessage}
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
disabled={!hasChat}
|
disabled={!hasChat}
|
||||||
disabledSubmit={disableSubmit}
|
disabledSubmit={disableSubmit}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo, useRef } from 'react';
|
||||||
import { useBusterNotifications } from '@/context/BusterNotifications';
|
import { useBusterNotifications } from '@/context/BusterNotifications';
|
||||||
import { useBusterNewChatContextSelector } from '@/context/Chats';
|
import { useBusterNewChatContextSelector } from '@/context/Chats';
|
||||||
import { useMemoizedFn } from '@/hooks';
|
import { useMemoizedFn } from '@/hooks';
|
||||||
|
@ -27,6 +27,7 @@ export const useChatInputFlow = ({
|
||||||
const selectedFileId = useChatIndividualContextSelector((x) => x.selectedFileId);
|
const selectedFileId = useChatIndividualContextSelector((x) => x.selectedFileId);
|
||||||
const onStartNewChat = useBusterNewChatContextSelector((state) => state.onStartNewChat);
|
const onStartNewChat = useBusterNewChatContextSelector((state) => state.onStartNewChat);
|
||||||
const onFollowUpChat = useBusterNewChatContextSelector((state) => state.onFollowUpChat);
|
const onFollowUpChat = useBusterNewChatContextSelector((state) => state.onFollowUpChat);
|
||||||
|
const isSubmittingChat = useBusterNewChatContextSelector((state) => state.isSubmittingChat);
|
||||||
const onStartChatFromFile = useBusterNewChatContextSelector((state) => state.onStartChatFromFile);
|
const onStartChatFromFile = useBusterNewChatContextSelector((state) => state.onStartChatFromFile);
|
||||||
const onStopChatContext = useBusterNewChatContextSelector((state) => state.onStopChat);
|
const onStopChatContext = useBusterNewChatContextSelector((state) => state.onStopChat);
|
||||||
const currentMessageId = useChatIndividualContextSelector((x) => x.currentMessageId);
|
const currentMessageId = useChatIndividualContextSelector((x) => x.currentMessageId);
|
||||||
|
@ -34,6 +35,8 @@ export const useChatInputFlow = ({
|
||||||
const onResetToOriginal = useChatIndividualContextSelector((x) => x.onResetToOriginal);
|
const onResetToOriginal = useChatIndividualContextSelector((x) => x.onResetToOriginal);
|
||||||
const { openConfirmModal } = useBusterNotifications();
|
const { openConfirmModal } = useBusterNotifications();
|
||||||
|
|
||||||
|
const submittingCooldown = useRef(isSubmittingChat);
|
||||||
|
|
||||||
const flow: FlowType = useMemo(() => {
|
const flow: FlowType = useMemo(() => {
|
||||||
if (hasChat) return 'followup-chat';
|
if (hasChat) return 'followup-chat';
|
||||||
if (selectedFileType === 'metric' && selectedFileId) return 'followup-metric';
|
if (selectedFileType === 'metric' && selectedFileId) return 'followup-metric';
|
||||||
|
@ -42,7 +45,14 @@ export const useChatInputFlow = ({
|
||||||
}, [hasChat, selectedFileType, selectedFileId]);
|
}, [hasChat, selectedFileType, selectedFileId]);
|
||||||
|
|
||||||
const onSubmitPreflight = useMemoizedFn(async () => {
|
const onSubmitPreflight = useMemoizedFn(async () => {
|
||||||
if (disableSubmit || !chatId || !currentMessageId) return;
|
if (
|
||||||
|
disableSubmit ||
|
||||||
|
!chatId ||
|
||||||
|
!currentMessageId ||
|
||||||
|
submittingCooldown.current ||
|
||||||
|
isSubmittingChat
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
onStopChat();
|
onStopChat();
|
||||||
|
@ -52,6 +62,7 @@ export const useChatInputFlow = ({
|
||||||
const trimmedInputValue = inputValue.trim();
|
const trimmedInputValue = inputValue.trim();
|
||||||
|
|
||||||
const method = async () => {
|
const method = async () => {
|
||||||
|
submittingCooldown.current = true;
|
||||||
switch (flow) {
|
switch (flow) {
|
||||||
case 'followup-chat':
|
case 'followup-chat':
|
||||||
await onFollowUpChat({ prompt: trimmedInputValue, chatId });
|
await onFollowUpChat({ prompt: trimmedInputValue, chatId });
|
||||||
|
@ -89,6 +100,10 @@ export const useChatInputFlow = ({
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
textAreaRef.current?.focus();
|
textAreaRef.current?.focus();
|
||||||
}, 50);
|
}, 50);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
submittingCooldown.current = false;
|
||||||
|
}, 350);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isFileChanged) {
|
if (!isFileChanged) {
|
||||||
|
|
Loading…
Reference in New Issue