From dfb796ec5e4dc49580263d627d4e8aac351d77f2 Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Wed, 15 Oct 2025 14:01:18 +0530 Subject: [PATCH] Improvement AI chat error handling (#15035) - Fixed AI chat failures when tool calls were present in conversation history - Improved error handling and user feedback for AI streaming errors https://github.com/user-attachments/assets/ca85820f-32c0-4f42-86ee-98f4543e6038 --- .../AIChatAssistantMessageRenderer.tsx | 36 +++--- .../ai/components/AIChatErrorMessage.tsx | 87 +++++++++++++ .../modules/ai/components/AIChatMessage.tsx | 21 +++ .../src/modules/ai/components/AIChatTab.tsx | 33 +++-- .../ai/components/AgentChatProvider.tsx | 58 +++++++-- .../components/AgentChatSessionProvider.tsx | 54 -------- .../ai/components/ErrorStepRenderer.tsx | 62 --------- .../AIChatErrorMessageWithRecordsContext.tsx | 14 ++ .../components/internal/SendMessageButton.tsx | 39 +++++- .../SendMessageWithRecordsContextButton.tsx | 8 +- .../modules/ai/contexts/AgentChatContext.ts | 20 +-- .../src/modules/ai/hooks/useAgentChat.ts | 121 ++---------------- .../ai/hooks/useAgentChatRequestBody.ts | 45 +++++++ .../ai/states/agentChatContextState.ts | 13 -- .../agent/utils/mapUIMessagePartsToDBParts.ts | 3 +- 15 files changed, 310 insertions(+), 304 deletions(-) create mode 100644 packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx delete mode 100644 packages/twenty-front/src/modules/ai/components/AgentChatSessionProvider.tsx delete mode 100644 packages/twenty-front/src/modules/ai/components/ErrorStepRenderer.tsx create mode 100644 packages/twenty-front/src/modules/ai/components/internal/AIChatErrorMessageWithRecordsContext.tsx create mode 100644 packages/twenty-front/src/modules/ai/hooks/useAgentChatRequestBody.ts delete mode 100644 packages/twenty-front/src/modules/ai/states/agentChatContextState.ts diff --git a/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx b/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx index 577030eaea9..c35c0b91b23 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx @@ -12,13 +12,13 @@ import { type UITools, } from 'ai'; -const StyledStepsContainer = styled.div` +const StyledMessagePartsContainer = styled.div` display: flex; flex-direction: column; gap: ${({ theme }) => theme.spacing(1)}; `; -const StyledDotsIconContainer = styled.div` +const StyledLoadingIconContainer = styled.div` align-items: center; border: ${({ theme }) => `1px solid ${theme.border.color.light}`}; border-radius: ${({ theme }) => theme.border.radius.md}; @@ -27,46 +27,48 @@ const StyledDotsIconContainer = styled.div` padding-inline: ${({ theme }) => theme.spacing(1)}; `; -const StyledDotsIcon = styled(IconDotsVertical)` +const StyledLoadingIcon = styled(IconDotsVertical)` color: ${({ theme }) => theme.font.color.light}; transform: rotate(90deg); `; -const dots = keyframes` +const streamingDotsAnimation = keyframes` 0% { content: ''; } 33% { content: '.'; } 66% { content: '..'; } 100% { content: '...'; } `; -const StyledToolCallContainer = styled.div` +const StyledStreamingIndicator = styled.div` &::after { display: inline-block; content: ''; - animation: ${dots} 750ms steps(3, end) infinite; + animation: ${streamingDotsAnimation} 750ms steps(3, end) infinite; width: 2ch; text-align: left; } `; -const LoadingDotsIcon = () => { +const InitialLoadingIndicator = () => { const theme = useTheme(); return ( - - - + + + ); }; export const AIChatAssistantMessageRenderer = ({ messageParts, isLastMessageStreaming, + hasError, }: { messageParts: UIMessagePart[]; isLastMessageStreaming: boolean; + hasError?: boolean; }) => { - const renderStep = ( + const renderMessagePart = ( part: UIMessagePart, index: number, ) => { @@ -99,16 +101,16 @@ export const AIChatAssistantMessageRenderer = ({ } }; - if (!messageParts.length) { - return ; + if (!messageParts.length && !hasError) { + return ; } return (
- - {messageParts.map(renderStep)} - - {isLastMessageStreaming && } + + {messageParts.map(renderMessagePart)} + + {isLastMessageStreaming && !hasError && }
); }; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx b/packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx new file mode 100644 index 00000000000..b41c15db8f3 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/components/AIChatErrorMessage.tsx @@ -0,0 +1,87 @@ +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'; +import { IconAlertCircle, IconRefresh } from 'twenty-ui/display'; +import { Button } from 'twenty-ui/input'; + +const StyledErrorContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.danger}; + border: 1px solid ${({ theme }) => theme.border.color.danger}; + border-radius: ${({ theme }) => theme.border.radius.md}; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + padding: ${({ theme }) => theme.spacing(2, 3)}; +`; + +const StyledErrorIcon = styled.div` + align-items: center; + color: ${({ theme }) => theme.color.red}; + display: flex; +`; + +const StyledErrorContent = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(0.5)}; + flex: 1; +`; + +const StyledErrorTitle = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; +`; + +const StyledErrorMessage = styled.div` + color: ${({ theme }) => theme.font.color.secondary}; + font-size: ${({ theme }) => theme.font.size.xs}; + word-break: break-word; +`; + +type AIChatErrorMessageProps = { + error: Error; + records?: ObjectRecord[]; +}; + +export const AIChatErrorMessage = ({ + error, + records, +}: AIChatErrorMessageProps) => { + const theme = useTheme(); + const { chat } = useAgentChatContextOrThrow(); + const { buildRequestBody } = useAgentChatRequestBody(); + const { regenerate, status } = useChat({ chat }); + + const handleRetry = () => { + regenerate({ + body: buildRequestBody(records), + }); + }; + + return ( + + + + + + {t`Failed to get response`} + + {error.message || t`An error occurred while processing your message`} + + +