From 4ba5c35b90e6ac8bc6467b88376eed36ea296dc0 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Tue, 8 Jul 2025 14:23:22 -0600 Subject: [PATCH 1/2] submitting chat debounce --- .../web/src/context/Chats/NewChatProvider.tsx | 6 +++--- .../ChatContent/ChatInput/ChatInput.tsx | 7 ++++--- .../ChatContent/ChatInput/useChatInputFlow.ts | 19 +++++++++++++++++-- 3 files changed, 24 insertions(+), 8 deletions(-) diff --git a/apps/web/src/context/Chats/NewChatProvider.tsx b/apps/web/src/context/Chats/NewChatProvider.tsx index 51d826dac..3a2e3a358 100644 --- a/apps/web/src/context/Chats/NewChatProvider.tsx +++ b/apps/web/src/context/Chats/NewChatProvider.tsx @@ -16,7 +16,7 @@ import { useAppLayoutContextSelector } from '../BusterAppLayout'; import { BusterRoutes } from '@/routes/busterRoutes'; export const useBusterNewChat = () => { - const { mutateAsync: startNewChat } = useStartNewChat(); + const { mutateAsync: startNewChat, isPending: isSubmittingChat } = useStartNewChat(); const onChangePage = useAppLayoutContextSelector((x) => x.onChangePage); const getChatMessageMemoized = useGetChatMessageMemoized(); const getChatMemoized = useGetChatMemoized(); @@ -57,7 +57,6 @@ export const useBusterNewChat = () => { message_id: messageId }); - initializeNewChat(res); } ); @@ -149,7 +148,8 @@ export const useBusterNewChat = () => { onFollowUpChat, onStartChatFromFile, onReplaceMessageInChat, - onStopChat + onStopChat, + isSubmittingChat }; }; diff --git a/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx b/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx index 084437af4..c311a9317 100644 --- a/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx +++ b/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/ChatInput.tsx @@ -11,8 +11,9 @@ const autoResizeConfig = { minRows: 2, maxRows: 16 }; export const ChatInput: React.FC = React.memo(() => { const textAreaRef = useRef(null); - const loading = useChatIndividualContextSelector((x) => x.isStreamingMessage); + const isStreamingMessage = useChatIndividualContextSelector((x) => x.isStreamingMessage); const hasChat = useChatIndividualContextSelector((x) => x.hasChat); + const [inputValue, setInputValue] = useState(''); const disableSubmit = useMemo(() => { @@ -23,7 +24,7 @@ export const ChatInput: React.FC = React.memo(() => { disableSubmit, inputValue, setInputValue, - loading, + loading: isStreamingMessage, textAreaRef }); @@ -42,7 +43,7 @@ export const ChatInput: React.FC = React.memo(() => { onSubmit={onSubmitPreflight} onChange={onChange} onStop={onStopChat} - loading={loading} + loading={isStreamingMessage} value={inputValue} disabled={!hasChat} disabledSubmit={disableSubmit} diff --git a/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/useChatInputFlow.ts b/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/useChatInputFlow.ts index 107ddfe7e..3b702bf48 100644 --- a/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/useChatInputFlow.ts +++ b/apps/web/src/layouts/ChatLayout/ChatContainer/ChatContent/ChatInput/useChatInputFlow.ts @@ -1,5 +1,5 @@ import type React from 'react'; -import { useMemo } from 'react'; +import { useMemo, useRef } from 'react'; import { useBusterNotifications } from '@/context/BusterNotifications'; import { useBusterNewChatContextSelector } from '@/context/Chats'; import { useMemoizedFn } from '@/hooks'; @@ -27,6 +27,7 @@ export const useChatInputFlow = ({ const selectedFileId = useChatIndividualContextSelector((x) => x.selectedFileId); const onStartNewChat = useBusterNewChatContextSelector((state) => state.onStartNewChat); const onFollowUpChat = useBusterNewChatContextSelector((state) => state.onFollowUpChat); + const isSubmittingChat = useBusterNewChatContextSelector((state) => state.isSubmittingChat); const onStartChatFromFile = useBusterNewChatContextSelector((state) => state.onStartChatFromFile); const onStopChatContext = useBusterNewChatContextSelector((state) => state.onStopChat); const currentMessageId = useChatIndividualContextSelector((x) => x.currentMessageId); @@ -34,6 +35,8 @@ export const useChatInputFlow = ({ const onResetToOriginal = useChatIndividualContextSelector((x) => x.onResetToOriginal); const { openConfirmModal } = useBusterNotifications(); + const submittingCooldown = useRef(isSubmittingChat); + const flow: FlowType = useMemo(() => { if (hasChat) return 'followup-chat'; if (selectedFileType === 'metric' && selectedFileId) return 'followup-metric'; @@ -42,7 +45,14 @@ export const useChatInputFlow = ({ }, [hasChat, selectedFileType, selectedFileId]); const onSubmitPreflight = useMemoizedFn(async () => { - if (disableSubmit || !chatId || !currentMessageId) return; + if ( + disableSubmit || + !chatId || + !currentMessageId || + submittingCooldown.current || + isSubmittingChat + ) + return; if (loading) { onStopChat(); @@ -52,6 +62,7 @@ export const useChatInputFlow = ({ const trimmedInputValue = inputValue.trim(); const method = async () => { + submittingCooldown.current = true; switch (flow) { case 'followup-chat': await onFollowUpChat({ prompt: trimmedInputValue, chatId }); @@ -89,6 +100,10 @@ export const useChatInputFlow = ({ setTimeout(() => { textAreaRef.current?.focus(); }, 50); + + setTimeout(() => { + submittingCooldown.current = false; + }, 350); }; if (!isFileChanged) { From da21b4fbb59fa1d0cd71e141cd0727e52a1afdcc Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Tue, 8 Jul 2025 14:33:15 -0600 Subject: [PATCH 2/2] prevent spam on new chat --- .../HomePage/HomePageController.tsx | 36 +++++++++++++++---- .../src/controllers/HomePage/NewChatInput.tsx | 9 +---- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/apps/web/src/controllers/HomePage/HomePageController.tsx b/apps/web/src/controllers/HomePage/HomePageController.tsx index f07644ad9..692aa5780 100644 --- a/apps/web/src/controllers/HomePage/HomePageController.tsx +++ b/apps/web/src/controllers/HomePage/HomePageController.tsx @@ -8,6 +8,13 @@ import { NewChatInput } from './NewChatInput'; import { NewChatWarning } from './NewChatWarning'; import { useNewChatWarning } from './useNewChatWarning'; +enum TimeOfDay { + MORNING = 'morning', + AFTERNOON = 'afternoon', + EVENING = 'evening', + NIGHT = 'night' +} + export const HomePageController: React.FC> = () => { const newChatWarningProps = useNewChatWarning(); const { showWarning } = newChatWarningProps; @@ -15,18 +22,35 @@ export const HomePageController: React.FC> = () => { const user = useUserConfigContextSelector((state) => state.user); const userName = user?.name; - const isMorning = useMemo(() => { + const timeOfDay = useMemo(() => { const now = new Date(); 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(() => { - if (isMorning) { - return `Good morning, ${userName}`; + switch (timeOfDay) { + 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}`; - }, [userName]); + }, [timeOfDay, userName]); return (
diff --git a/apps/web/src/controllers/HomePage/NewChatInput.tsx b/apps/web/src/controllers/HomePage/NewChatInput.tsx index dc895b58a..316c524f2 100644 --- a/apps/web/src/controllers/HomePage/NewChatInput.tsx +++ b/apps/web/src/controllers/HomePage/NewChatInput.tsx @@ -22,7 +22,7 @@ export const NewChatInput: React.FC> = () => { }, [inputValue]); const onSubmit = useMemoizedFn(async (value: string) => { - if (disabledSubmit) return; + if (disabledSubmit || loading) return; try { setLoading(true); const trimmedValue = value.trim(); @@ -32,12 +32,6 @@ export const NewChatInput: React.FC> = () => { } }); - const onStop = useMemoizedFn(() => { - setLoading(false); - textAreaRef.current?.focus(); - textAreaRef.current?.select(); - }); - const onChange = useMemoizedFn((e: ChangeEvent) => { setInputValue(e.target.value); }); @@ -56,7 +50,6 @@ export const NewChatInput: React.FC> = () => { autoResize={autoResizeConfig} onSubmit={onSubmit} onChange={onChange} - onStop={onStop} loading={loading} disabled={false} disabledSubmit={disabledSubmit}