diff --git a/packages/twenty-front/src/modules/ai/components/AgentChatStreamSubscriptionEffect.tsx b/packages/twenty-front/src/modules/ai/components/AgentChatStreamSubscriptionEffect.tsx index 2df06292146..8a8a7378e9a 100644 --- a/packages/twenty-front/src/modules/ai/components/AgentChatStreamSubscriptionEffect.tsx +++ b/packages/twenty-front/src/modules/ai/components/AgentChatStreamSubscriptionEffect.tsx @@ -40,7 +40,6 @@ export const AgentChatStreamSubscriptionEffect = () => { useAgentChat(ensureThreadIdForSend); - // Subscribe to the thread's event stream const subscriptionThreadId = currentAIChatThread !== null && isValidUuid(currentAIChatThread) ? currentAIChatThread @@ -48,7 +47,6 @@ export const AgentChatStreamSubscriptionEffect = () => { useAgentChatSubscription(subscriptionThreadId); - // Sync fetched messages to the displayed messages atom when no stream is active const agentChatFetchedMessages = useAtomComponentFamilyStateValue( agentChatFetchedMessagesComponentFamilyState, { threadId: currentAIChatThread }, diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts index 0dd7aeb471b..ca8803deb24 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts @@ -89,9 +89,6 @@ export const useAgentChat = ( const browsingContext = getBrowsingContext(); - // Optimistic user message — always placed in the main messages atom. - // The next REFETCH_MESSAGES event will reconcile with server state, - // moving it to the queued list if the server queued it. const optimisticUserMessage: ExtendedUIMessage = { id: v4(), role: 'user', @@ -145,9 +142,6 @@ export const useAgentChat = ( const responseBody = await response.json(); - // If the server queued the message (stream was active), remove the - // optimistic entry from the main conversation — the refetch below will - // place it in the dedicated queue list instead. if (responseBody.queued) { const latestMessages = store.get( agentChatMessagesComponentFamilyState.atomFamily(atomKey), diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChatSubscription.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChatSubscription.ts index 686e65ea900..66973f9573f 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChatSubscription.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChatSubscription.ts @@ -162,8 +162,6 @@ export const useAgentChatSubscription = (threadId: string | null) => { agentChatMessagesComponentFamilyState.atomFamily(atomKey), ); - // Find and replace the streaming assistant message by ID, or append it. - // This preserves optimistic user messages that aren't yet in fetched data. const streamingMsgIndex = currentMessages.findIndex( (message) => message.id === messageToFlush.id, ); @@ -188,13 +186,10 @@ export const useAgentChatSubscription = (threadId: string | null) => { latestMessage = message; if (!isDefined(throttleTimer)) { - // Leading edge: flush immediately so the first chunk renders instantly flushToAtom(); - // Then suppress further flushes for THROTTLE_MS throttleTimer = setTimeout(() => { throttleTimer = null; - // Trailing edge: flush whatever accumulated during the window flushToAtom(); }, THROTTLE_MS); } @@ -206,7 +201,6 @@ export const useAgentChatSubscription = (threadId: string | null) => { for await (const message of messageStream) { const extendedMessage = message as ExtendedUIMessage; - // Extract usage and title from data parts const titlePart = extendedMessage.parts.find( (part) => part.type === 'data-thread-title', ); @@ -255,7 +249,6 @@ export const useAgentChatSubscription = (threadId: string | null) => { scheduleAtomUpdate(extendedMessage); } - // Stream finished -- flush last message immediately if (isDefined(throttleTimer)) { clearTimeout(throttleTimer); throttleTimer = null; @@ -299,7 +292,6 @@ export const useAgentChatSubscription = (threadId: string | null) => { } case 'message-persisted': { - // Close the current stream writer so readUIMessageStream finishes if (isDefined(writerRef.current)) { writerRef.current.close().catch(() => {}); writerRef.current = null; diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts index 7c20aeb29d7..09a50599052 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/controllers/agent-chat.controller.ts @@ -115,8 +115,6 @@ export class AgentChatController { ); } - // Server decides: if the thread has an active stream, queue the message; - // otherwise start streaming immediately. if (isDefined(thread.activeStreamId)) { const message = await this.agentChatService.queueMessage({ threadId, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/dtos/agent-chat-event.dto.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/dtos/agent-chat-event.dto.ts index 05dc78613f3..01aa5ea168d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/dtos/agent-chat-event.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/dtos/agent-chat-event.dto.ts @@ -2,14 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql'; import GraphQLJSON from 'graphql-type-json'; -// The `event` field is typed as GraphQLJSON because the payload is a -// discriminated union (AgentChatSubscriptionEvent from twenty-shared) -// whose variants carry different shapes: -// - { type: 'stream-chunk', chunk: Record } -// - { type: 'message-persisted', messageId: string } -// - { type: 'queue-updated' } -// - { type: 'stream-error', code: string, message: string } -// Clients should cast this field to AgentChatSubscriptionEvent. +// Typed as JSON because the payload is AgentChatSubscriptionEvent +// (a discriminated union defined in twenty-shared). @ObjectType('AgentChatEvent') export class AgentChatEventDTO { @Field(() => String) diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts index 742395caf60..023ab29da76 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-chat/services/agent-chat-streaming.service.ts @@ -68,8 +68,6 @@ export class AgentChatStreamingService { ); } - // Persist the user message before enqueuing the job so the frontend - // refetch (triggered when the controller returns) finds it in the DB. const savedUserMessage = await this.agentChatService.addMessage({ threadId, uiMessage: {