From 0648dc5c65e2b9ffaa2ac9799541a7802032ec4a Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Fri, 3 Oct 2025 16:07:41 +0530 Subject: [PATCH] Fix AI chat streaming persistence when command menu closes (#14854) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes [#1583](https://github.com/twentyhq/core-team-issues/issues/1583) - Removed `messageId` column from `File` table and its references in code (unrelated to this PR) - Updated AI chat to use React Context API with persistent provider in `CommandMenuContainer` - Converted all AI chat component states to regular Recoil atoms (no instance context needed) --------- Co-authored-by: Félix Malfait --- .../src/generated-metadata/graphql.ts | 12 +-- .../twenty-front/src/generated/graphql.ts | 2 - .../src/modules/ai/components/AIChatTab.tsx | 41 +++++---- .../ai/components/AIChatThreadGroup.tsx | 13 +-- .../ai/components/AIChatThreadsList.tsx | 3 +- .../ai/components/AgentChatProvider.tsx | 57 +++++++++++++ .../components/AgentChatSessionProvider.tsx | 54 ++++++++++++ .../modules/ai/components/LazyAIChatTab.tsx | 23 ----- .../internal/AgentChatContextPreview.tsx | 19 +++-- .../AgentChatContextRecordPreview.tsx | 6 +- .../internal/AgentChatFileUploadButton.tsx | 13 ++- .../components/internal/SendMessageButton.tsx | 37 +++++--- .../SendMessageWithRecordsContextButton.tsx | 10 +++ .../src/modules/ai/constants/AiChatInputId.ts | 1 + .../modules/ai/contexts/AgentChatContext.ts | 26 ++++++ .../graphql/queries/getAgentChatMessages.ts | 8 -- .../modules/ai/hooks/useAIChatFileUpload.ts | 18 ++-- .../src/modules/ai/hooks/useAgentChat.ts | 85 ++++++------------- .../ai/hooks/useAgentChatContextOrThrow.ts | 14 +++ .../src/modules/ai/hooks/useAgentChatData.ts | 37 ++++++++ .../ai/hooks/useCreateNewAIChatThread.ts | 11 +-- .../ai/states/agentChatContextState.ts | 13 +++ ...ChatObjectMetadataAndRecordContextState.ts | 14 --- .../agentChatSelectedFilesComponentState.ts | 10 --- .../ai/states/agentChatSelectedFilesState.ts | 6 ++ .../agentChatUploadedFilesComponentState.ts | 11 --- .../ai/states/agentChatUploadedFilesState.ts | 7 ++ .../currentAIChatThreadComponentState.ts | 10 --- .../ai/states/currentAIChatThreadState.ts | 6 ++ .../isAgentChatCurrentContextActiveState.ts | 16 ++-- .../components/CommandMenuContainer.tsx | 19 +++-- .../components/CommandMenuAskAIPage.tsx | 43 +--------- .../commands/cron-register-all.command.ts | 6 -- ...9378531410-removeMessageIdFromFileTable.ts | 23 +++++ .../cleanup-orphaned-files.cron.command.ts | 32 ------- .../jobs/cleanup-orphaned-files.cron.job.ts | 67 --------------- .../engine/core-modules/file/dtos/file.dto.ts | 3 - .../core-modules/file/entities/file.entity.ts | 10 --- .../engine/core-modules/file/file.module.ts | 6 +- .../agent/agent-chat-message.entity.ts | 4 - .../agent/dtos/agent-chat-message.dto.ts | 4 - 41 files changed, 385 insertions(+), 415 deletions(-) create mode 100644 packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx create mode 100644 packages/twenty-front/src/modules/ai/components/AgentChatSessionProvider.tsx delete mode 100644 packages/twenty-front/src/modules/ai/components/LazyAIChatTab.tsx create mode 100644 packages/twenty-front/src/modules/ai/components/internal/SendMessageWithRecordsContextButton.tsx create mode 100644 packages/twenty-front/src/modules/ai/constants/AiChatInputId.ts create mode 100644 packages/twenty-front/src/modules/ai/contexts/AgentChatContext.ts create mode 100644 packages/twenty-front/src/modules/ai/hooks/useAgentChatContextOrThrow.ts create mode 100644 packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts create mode 100644 packages/twenty-front/src/modules/ai/states/agentChatContextState.ts delete mode 100644 packages/twenty-front/src/modules/ai/states/agentChatObjectMetadataAndRecordContextState.ts delete mode 100644 packages/twenty-front/src/modules/ai/states/agentChatSelectedFilesComponentState.ts create mode 100644 packages/twenty-front/src/modules/ai/states/agentChatSelectedFilesState.ts delete mode 100644 packages/twenty-front/src/modules/ai/states/agentChatUploadedFilesComponentState.ts create mode 100644 packages/twenty-front/src/modules/ai/states/agentChatUploadedFilesState.ts delete mode 100644 packages/twenty-front/src/modules/ai/states/currentAIChatThreadComponentState.ts create mode 100644 packages/twenty-front/src/modules/ai/states/currentAIChatThreadState.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1759378531410-removeMessageIdFromFileTable.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/file/crons/commands/cleanup-orphaned-files.cron.command.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/file/crons/jobs/cleanup-orphaned-files.cron.job.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index c6c9d1f49cc..45df30ff69b 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -70,7 +70,6 @@ export type Agent = { export type AgentChatMessage = { __typename?: 'AgentChatMessage'; createdAt: Scalars['DateTime']; - files: Array; id: Scalars['UUID']; parts: Array; role: Scalars['String']; @@ -1317,7 +1316,6 @@ export type File = { createdAt: Scalars['DateTime']; fullPath: Scalars['String']; id: Scalars['UUID']; - messageId?: Maybe; name: Scalars['String']; size: Scalars['Float']; type: Scalars['String']; @@ -4674,7 +4672,7 @@ export type GetAgentChatMessagesQueryVariables = Exact<{ }>; -export type GetAgentChatMessagesQuery = { __typename?: 'Query', agentChatMessages: Array<{ __typename?: 'AgentChatMessage', id: string, threadId: string, role: string, createdAt: string, parts: Array<{ __typename?: 'AgentChatMessagePart', id: string, messageId: string, orderIndex: number, type: string, textContent?: string | null, reasoningContent?: string | null, toolName?: string | null, toolCallId?: string | null, toolInput?: any | null, toolOutput?: any | null, state?: string | null, errorMessage?: string | null, errorDetails?: any | null, sourceUrlSourceId?: string | null, sourceUrlUrl?: string | null, sourceUrlTitle?: string | null, sourceDocumentSourceId?: string | null, sourceDocumentMediaType?: string | null, sourceDocumentTitle?: string | null, sourceDocumentFilename?: string | null, fileMediaType?: string | null, fileFilename?: string | null, fileUrl?: string | null, providerMetadata?: any | null, createdAt: string }>, files: Array<{ __typename?: 'File', id: string, name: string, fullPath: string, size: number, type: string, createdAt: string }> }> }; +export type GetAgentChatMessagesQuery = { __typename?: 'Query', agentChatMessages: Array<{ __typename?: 'AgentChatMessage', id: string, threadId: string, role: string, createdAt: string, parts: Array<{ __typename?: 'AgentChatMessagePart', id: string, messageId: string, orderIndex: number, type: string, textContent?: string | null, reasoningContent?: string | null, toolName?: string | null, toolCallId?: string | null, toolInput?: any | null, toolOutput?: any | null, state?: string | null, errorMessage?: string | null, errorDetails?: any | null, sourceUrlSourceId?: string | null, sourceUrlUrl?: string | null, sourceUrlTitle?: string | null, sourceDocumentSourceId?: string | null, sourceDocumentMediaType?: string | null, sourceDocumentTitle?: string | null, sourceDocumentFilename?: string | null, fileMediaType?: string | null, fileFilename?: string | null, fileUrl?: string | null, providerMetadata?: any | null, createdAt: string }> }> }; export type GetAgentChatThreadsQueryVariables = Exact<{ agentId: Scalars['UUID']; @@ -6963,14 +6961,6 @@ export const GetAgentChatMessagesDocument = gql` providerMetadata createdAt } - files { - id - name - fullPath - size - type - createdAt - } } } `; diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 89f86748c0f..435d5b4204d 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -70,7 +70,6 @@ export type Agent = { export type AgentChatMessage = { __typename?: 'AgentChatMessage'; createdAt: Scalars['DateTime']; - files: Array; id: Scalars['UUID']; parts: Array; role: Scalars['String']; @@ -1281,7 +1280,6 @@ export type File = { createdAt: Scalars['DateTime']; fullPath: Scalars['String']; id: Scalars['UUID']; - messageId?: Maybe; name: Scalars['String']; size: Scalars['Float']; type: Scalars['String']; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx index 60fb33c92f1..2676cef8fb4 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx @@ -14,12 +14,15 @@ import { AIChatMessage } from '@/ai/components/AIChatMessage'; import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoader'; import { AgentChatContextPreview } from '@/ai/components/internal/AgentChatContextPreview'; 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 { type UIMessageWithMetadata } from '@/ai/types/UIMessageWithMetadata'; +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'; import { useState } from 'react'; import { Button } from 'twenty-ui/input'; -import { useAgentChat } from '../hooks/useAgentChat'; const StyledContainer = styled.div<{ isDraggingFile: boolean }>` background: ${({ theme }) => theme.background.primary}; @@ -56,13 +59,7 @@ const StyledButtonsContainer = styled.div` gap: ${({ theme }) => theme.spacing(2)}; `; -export const AIChatTab = ({ - agentId, - uiMessages, -}: { - agentId: string; - uiMessages: UIMessageWithMetadata[]; -}) => { +export const AIChatTab = ({ agentId }: { agentId: string }) => { const [isDraggingFile, setIsDraggingFile] = useState(false); const { @@ -71,11 +68,14 @@ export const AIChatTab = ({ handleInputChange, scrollWrapperId, messages, - handleSendMessage, isStreaming, - } = useAgentChat(agentId, uiMessages); - const { uploadFiles } = useAIChatFileUpload({ agentId }); + } = useAgentChatContextOrThrow(); + const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue( + contextStoreCurrentObjectMetadataItemIdComponentState, + ); + + const { uploadFiles } = useAIChatFileUpload(); const { createAgentChatThread } = useCreateNewAIChatThread({ agentId }); const { navigateCommandMenu } = useCommandMenu(); @@ -110,9 +110,9 @@ export const AIChatTab = ({ {isLoading && messages.length === 0 && } - +