From cc7343a8f29387e945309ca7cba7f980441435f3 Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Wed, 5 Nov 2025 15:20:50 +0530 Subject: [PATCH] Revert: Agent chat umbrella hook refactoring due to streaming issue on thread switch (#15621) ## Summary Reverts commits afc518a, ae22e64, and 800b5b5 that refactored `useAgentChat` to address umbrella hook pattern feedback. ## Issues Introduced by Refactoring The refactoring broke several critical functionalities: 1. **Streaming fails on thread switch** - Messages don't stream properly when switching between threads 2. **Messages lost on tab close** - When the Ask AI tab is closed, the request is lost instead of continuing in the background 3. **Blank chat requiring force-reload** - Chats often appear blank and require switching to another chat to force a reload (closes [#1771](https://github.com/twentyhq/core-team-issues/issues/1771)) ## Root Cause After extensive debugging, it appears **multiple instances of `useChat` don't work well together**. The refactored architecture inadvertently created scenarios where multiple `useChat` instances interfere with each other. ## Resolution Reverting to restore functionality. The umbrella hook pattern optimization needs a different architectural approach that doesn't rely on multiple `useChat` instances. ## Follow-up While the umbrella hook feedback is valid, we need to rethink the implementation strategy: - Find an alternative to multiple `useChat` instances - Possibly consolidate chat state management differently --- .../ai/components/AIChatErrorMessage.tsx | 20 +--- .../src/modules/ai/components/AIChatTab.tsx | 4 +- .../ai/components/AgentChatProvider.tsx | 59 ++--------- .../components/internal/SendMessageButton.tsx | 37 +------ .../modules/ai/contexts/AgentChatContext.ts | 16 ++- .../src/modules/ai/hooks/useAgentChat.ts | 98 +++++++++++++++++-- .../ai/hooks/useAgentChatRequestBody.ts | 45 --------- 7 files changed, 120 insertions(+), 159 deletions(-) delete mode 100644 packages/twenty-front/src/modules/ai/hooks/useAgentChatRequestBody.ts diff --git a/packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx b/packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx index b41c15db8f..e10c880fb1 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx @@ -1,7 +1,5 @@ import { useAgentChatContextOrThrow } from '@/ai/hooks/useAgentChatContextOrThrow'; -import { useAgentChatRequestBody } from '@/ai/hooks/useAgentChatRequestBody'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; -import { useChat } from '@ai-sdk/react'; import { useTheme } from '@emotion/react'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; @@ -46,22 +44,12 @@ const StyledErrorMessage = styled.div` type AIChatErrorMessageProps = { error: Error; records?: ObjectRecord[]; + isRetrying?: boolean; }; -export const AIChatErrorMessage = ({ - error, - records, -}: AIChatErrorMessageProps) => { +export const AIChatErrorMessage = ({ error }: AIChatErrorMessageProps) => { const theme = useTheme(); - const { chat } = useAgentChatContextOrThrow(); - const { buildRequestBody } = useAgentChatRequestBody(); - const { regenerate, status } = useChat({ chat }); - - const handleRetry = () => { - regenerate({ - body: buildRequestBody(records), - }); - }; + const { handleRetry, isStreaming } = useAgentChatContextOrThrow(); return ( @@ -79,7 +67,7 @@ export const AIChatErrorMessage = ({ size="small" Icon={IconRefresh} onClick={handleRetry} - disabled={status === 'streaming'} + disabled={isStreaming} title={t`Retry`} /> diff --git a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx index 362692cfb1..a95b922d7b 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx @@ -17,7 +17,7 @@ import { SendMessageButton } from '@/ai/components/internal/SendMessageButton'; import { SendMessageWithRecordsContextButton } from '@/ai/components/internal/SendMessageWithRecordsContextButton'; import { AI_CHAT_INPUT_ID } from '@/ai/constants/AiChatInputId'; import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload'; -import { useAgentChat } from '@/ai/hooks/useAgentChat'; +import { useAgentChatContextOrThrow } from '@/ai/hooks/useAgentChatContextOrThrow'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { t } from '@lingui/core/macro'; @@ -70,7 +70,7 @@ export const AIChatTab = () => { messages, isStreaming, error, - } = useAgentChat(); + } = useAgentChatContextOrThrow(); const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue( contextStoreCurrentObjectMetadataItemIdComponentState, diff --git a/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx b/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx index f22d5d9b20..66f04ec9d4 100644 --- a/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx +++ b/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx @@ -1,50 +1,25 @@ import { AgentChatContext } from '@/ai/contexts/AgentChatContext'; +import { useAgentChat } from '@/ai/hooks/useAgentChat'; import { useAgentChatData } from '@/ai/hooks/useAgentChatData'; -import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState'; -import { REST_API_BASE_URL } from '@/apollo/constant/rest-api-base-url'; -import { getTokenPair } from '@/apollo/utils/getTokenPair'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; -import { Chat } from '@ai-sdk/react'; -import { DefaultChatTransport } from 'ai'; import { Suspense } from 'react'; -import { useRecoilValue } from 'recoil'; -import { type ExtendedUIMessage } from 'twenty-shared/ai'; import { FeatureFlagKey } from '~/generated/graphql'; -const createLoadingChat = () => - new Chat({ - transport: new DefaultChatTransport({ - api: `${REST_API_BASE_URL}/agent-chat/stream`, - headers: () => ({}), - }), - messages: [], - id: 'loading', - }); - const AgentChatProviderContent = ({ children, }: { children: React.ReactNode; }) => { const { uiMessages, isLoading } = useAgentChatData(); - const currentAIChatThread = useRecoilValue(currentAIChatThreadState); - - const chatConfig = isLoading - ? createLoadingChat() - : new Chat({ - transport: new DefaultChatTransport({ - api: `${REST_API_BASE_URL}/agent-chat/stream`, - headers: () => ({ - Authorization: `Bearer ${getTokenPair()?.accessOrWorkspaceAgnosticToken.token}`, - }), - }), - messages: uiMessages, - id: `${currentAIChatThread}-${uiMessages.length}`, - }); + const chatState = useAgentChat(uiMessages); + const combinedIsLoading = chatState.isLoading || isLoading; return ( {children} @@ -60,30 +35,14 @@ export const AgentChatProvider = ({ if (!isAiEnabled) { return ( - + {children} ); } return ( - - {children} - - } - > + {children} ); diff --git a/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx b/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx index 2e820012d7..392c4aa617 100644 --- a/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx +++ b/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx @@ -1,13 +1,8 @@ import { AI_CHAT_INPUT_ID } from '@/ai/constants/AiChatInputId'; -import { useAgentChat } from '@/ai/hooks/useAgentChat'; import { useAgentChatContextOrThrow } from '@/ai/hooks/useAgentChatContextOrThrow'; -import { useAgentChatRequestBody } from '@/ai/hooks/useAgentChatRequestBody'; -import { agentChatUploadedFilesState } from '@/ai/states/agentChatUploadedFilesState'; import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; -import { useChat } from '@ai-sdk/react'; import { t } from '@lingui/core/macro'; -import { useRecoilState } from 'recoil'; import { Key } from 'ts-key-enum'; import { Button } from 'twenty-ui/input'; @@ -16,40 +11,14 @@ export const SendMessageButton = ({ }: { records?: ObjectRecord[]; }) => { - const { input, isLoading, handleInputChange } = useAgentChat(); - const { chat } = useAgentChatContextOrThrow(); - const { buildRequestBody } = useAgentChatRequestBody(); - const { sendMessage } = useChat({ chat }); - - const [agentChatUploadedFiles, setAgentChatUploadedFiles] = useRecoilState( - agentChatUploadedFilesState, - ); - - const handleSendMessage = () => { - if (input.trim() === '' || isLoading) { - return; - } - - sendMessage( - { - text: input, - files: agentChatUploadedFiles, - }, - { - body: buildRequestBody(records), - }, - ); - - handleInputChange(''); - setAgentChatUploadedFiles([]); - }; + const { handleSendMessage, isLoading, input } = useAgentChatContextOrThrow(); useHotkeysOnFocusedElement({ keys: [Key.Enter], callback: (event: KeyboardEvent) => { if (!event.ctrlKey && !event.metaKey) { event.preventDefault(); - handleSendMessage(); + handleSendMessage(records); } }, focusId: AI_CHAT_INPUT_ID, @@ -62,7 +31,7 @@ export const SendMessageButton = ({ return (