fix: lagging issue in ask AI during message streaming (#16201)

## Changes

### 1. Fixed streaming lag with throttle parameter

**Problem:** The AI chat was experiencing lag during message streaming
because the messages array was being updated too frequently, causing all
messages to re-render too quickly.

**Solution:** Added the `experimental_throttle: 100` parameter to the
`useChat` hook configuration. This throttles message updates during
streaming to prevent excessive re-renders and improve performance.

### 2. Cleaned up useAgentChat hook return values

**Context:** The `useAgentChat` hook primarily returns values from the
underlying `useChat` hook, so there wasn't significant room for
improvement regarding the "umbrella hook" pattern. However, some
unnecessary values were being returned that weren't needed.

**Solution:**
- Removed `input` and `handleInputChange` from the `useAgentChat` hook
return. These weren't needed since input state is already managed
directly via Recoil state (`agentChatInputState`) in components.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Throttle message streaming updates and switch AI chat input management
from context to Recoil; minor scroll behavior tweak.
> 
> - **AI Chat performance**:
> - Add `experimental_throttle: 100` to `useChat` in `useAgentChat` to
reduce re-render frequency during streaming.
> - **State management**:
> - Migrate input handling to Recoil via `agentChatInputState`; remove
`input` and `handleInputChange` from `AgentChatContext` and
`useAgentChat` returns.
> - Update `AIChatTab` and `SendMessageButton` to read/write input from
Recoil and adjust hotkeys/disabled state accordingly.
> - **UX behavior**:
>   - Remove smooth scroll behavior in `useAgentChatScrollToBottom`.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
911b341ea1f7c63a4e1e27667da119d9a2960b78. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Abdul Rahman
2025-12-01 20:01:20 +05:30
committed by GitHub
parent b5ec6df62f
commit 19fc20173b
5 changed files with 16 additions and 13 deletions
@@ -19,10 +19,12 @@ import { AI_CHAT_INPUT_ID } from '@/ai/constants/AiChatInputId';
import { AI_CHAT_SCROLL_WRAPPER_ID } from '@/ai/constants/AiChatScrollWrapperId';
import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload';
import { useAgentChatContextOrThrow } from '@/ai/hooks/useAgentChatContextOrThrow';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
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 { useRecoilState } from 'recoil';
import { Button } from 'twenty-ui/input';
const StyledContainer = styled.div<{ isDraggingFile: boolean }>`
@@ -63,9 +65,12 @@ const StyledButtonsContainer = styled.div`
export const AIChatTab = () => {
const [isDraggingFile, setIsDraggingFile] = useState(false);
const { isLoading, input, handleInputChange, messages, isStreaming, error } =
const { isLoading, messages, isStreaming, error } =
useAgentChatContextOrThrow();
const [agentChatInput, setAgentChatInput] =
useRecoilState(agentChatInputState);
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue(
contextStoreCurrentObjectMetadataItemIdComponentState,
);
@@ -115,8 +120,8 @@ export const AIChatTab = () => {
<TextArea
textAreaId={AI_CHAT_INPUT_ID}
placeholder={t`Enter a question...`}
value={input}
onChange={handleInputChange}
value={agentChatInput}
onChange={(value) => setAgentChatInput(value)}
minRows={1}
maxRows={20}
/>
@@ -1,8 +1,10 @@
import { AI_CHAT_INPUT_ID } from '@/ai/constants/AiChatInputId';
import { useAgentChatContextOrThrow } from '@/ai/hooks/useAgentChatContextOrThrow';
import { agentChatInputState } from '@/ai/states/agentChatInputState';
import { type ObjectRecord } from '@/object-record/types/ObjectRecord';
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
import { t } from '@lingui/core/macro';
import { useRecoilValue } from 'recoil';
import { Key } from 'ts-key-enum';
import { Button } from 'twenty-ui/input';
@@ -11,7 +13,8 @@ export const SendMessageButton = ({
}: {
records?: ObjectRecord[];
}) => {
const { handleSendMessage, isLoading, input } = useAgentChatContextOrThrow();
const agentChatInput = useRecoilValue(agentChatInputState);
const { handleSendMessage, isLoading } = useAgentChatContextOrThrow();
useHotkeysOnFocusedElement({
keys: [Key.Enter],
@@ -22,7 +25,7 @@ export const SendMessageButton = ({
}
},
focusId: AI_CHAT_INPUT_ID,
dependencies: [input, isLoading],
dependencies: [agentChatInput, isLoading],
options: {
enableOnFormTags: true,
},
@@ -30,9 +33,9 @@ export const SendMessageButton = ({
return (
<Button
hotkeys={input && !isLoading ? ['⏎'] : undefined}
hotkeys={agentChatInput && !isLoading ? ['⏎'] : undefined}
onClick={() => handleSendMessage(records)}
disabled={!input || isLoading}
disabled={!agentChatInput || isLoading}
variant="primary"
accent="blue"
size="small"
@@ -8,9 +8,6 @@ export type AgentChatContextValue = {
isLoading: boolean;
error?: Error;
input: string;
handleInputChange: (value: string) => void;
handleSendMessage: (records?: ObjectRecord[]) => Promise<void>;
handleRetry: () => void;
@@ -110,6 +110,7 @@ export const useAgentChat = (uiMessages: ExtendedUIMessage[]) => {
}),
messages: uiMessages,
id: `${currentAIChatThread}-${uiMessages.length}`,
experimental_throttle: 100,
});
const isStreaming = status === 'streaming';
@@ -156,9 +157,7 @@ export const useAgentChat = (uiMessages: ExtendedUIMessage[]) => {
};
return {
handleInputChange: (value: string) => setAgentChatInput(value),
messages,
input: agentChatInput,
handleSendMessage,
isLoading,
isStreaming,
@@ -15,7 +15,6 @@ export const useAgentChatScrollToBottom = () => {
scrollWrapperElement.scrollTo({
top: scrollWrapperElement.scrollHeight,
behavior: 'smooth',
});
};