From 2685f4a5b9fb179fa156c472cfc152d2900af789 Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Mon, 29 Sep 2025 17:01:55 +0530 Subject: [PATCH] Restructure agent chat messages with parts-based architecture (#14749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Félix Malfait --- packages/twenty-front/package.json | 2 + .../src/generated-metadata/graphql.ts | 62 ++- .../twenty-front/src/generated/graphql.ts | 32 +- .../AIChatAssistantMessageRenderer.tsx | 80 ++-- .../modules/ai/components/AIChatMessage.tsx | 44 +- .../src/modules/ai/components/AIChatTab.tsx | 35 +- .../ai/components/ToolStepRenderer.tsx | 52 +-- .../internal/AgentChatContextPreview.tsx | 29 +- .../internal/AgentChatFilePreview.tsx | 13 +- .../components/internal/SendMessageButton.tsx | 18 +- .../SendMessageWithRecordsContextButton.tsx | 14 - .../graphql/queries/getAgentChatMessages.ts | 28 +- .../modules/ai/hooks/useAIChatFileUpload.ts | 36 +- .../src/modules/ai/hooks/useAgentChat.ts | 173 ++----- .../ai/rest-api/agent-chat-apollo.api.ts | 46 -- .../agentChatUploadedFilesComponentState.ts | 4 +- .../ai/states/agentStreamingMessageState.ts | 6 - .../src/modules/ai/types/ParsedStep.ts | 7 - .../src/modules/ai/types/ToolInput.ts | 4 + .../src/modules/ai/types/ToolOutput.ts | 1 + .../modules/ai/types/UIMessageWithMetadata.ts | 7 + .../ai/utils/__tests__/parseStream.test.ts | 440 ------------------ .../ai/utils/mapDBMessagesToUIMessages.ts | 16 + .../ai/utils/mapDBPartToUIMessagePart.ts | 67 +++ .../src/modules/ai/utils/parseStream.ts | 189 -------- .../components/CommandMenuAskAIPage.tsx | 45 +- packages/twenty-server/@types/common.d.ts | 13 - .../1-5/1-5-migrate-views-to-core.command.ts | 40 +- ...ChatMessagePartTableAndRemoveRawContent.ts | 33 ++ .../core-modules/api-key/api-key.resolver.ts | 4 +- .../core-modules/api-key/api-key.service.ts | 3 +- .../api-key/controllers/api-key.controller.ts | 4 +- .../file/interfaces/file-folder.interface.ts | 4 + .../public-domain/public-domain.service.ts | 9 +- .../core-modules/webhook/webhook.resolver.ts | 4 +- .../core-modules/webhook/webhook.service.ts | 7 +- .../agent/agent-chat-message-part.entity.ts | 98 ++++ .../agent/agent-chat-message.entity.ts | 6 +- .../agent/agent-chat.controller.ts | 51 +- .../agent/agent-chat.service.ts | 45 +- .../agent/agent-execution.service.ts | 201 +++----- .../agent/agent-streaming.service.ts | 139 +++--- .../metadata-modules/agent/agent.module.ts | 3 + .../constants/agent-handoff-schema.const.ts | 13 +- .../agent/dtos/agent-chat-message-part.dto.ts | 84 ++++ .../agent/dtos/agent-chat-message.dto.ts | 6 +- ...structAssistantMessageContentFromStream.ts | 96 ---- .../agent/utils/mapUIMessagePartsToDBParts.ts | 81 ++++ .../remote-server/remote-server.service.ts | 1 + ...ld-update-remote-server-raw-query.utils.ts | 6 +- .../dev-seeder/core/utils/seed-agents.util.ts | 152 +++++- .../partial-field-metadata.interface.ts | 5 +- .../utils/sync-metadata.util.spec.ts | 1 + packages/twenty-shared/src/ai/index.ts | 11 - .../twenty-shared/src/ai/types/ErrorEvent.ts | 5 - .../src/ai/types/ReasoningDeltaEvent.ts | 4 - .../twenty-shared/src/ai/types/StreamEvent.ts | 21 - .../twenty-shared/src/ai/types/TextBlock.ts | 4 - .../src/ai/types/TextDeltaEvent.ts | 4 - .../src/ai/types/ToolCallEvent.ts | 9 - .../twenty-shared/src/ai/types/ToolEvent.ts | 4 - .../src/ai/types/ToolResultEvent.ts | 11 - .../src/ai/utils/parseStreamLine.ts | 9 - .../src/ai/utils/splitStreamIntoLines.ts | 3 - .../src/types/ExcludeFunctions.ts | 1 + packages/twenty-shared/src/types/index.ts | 1 + ...est.ts => ContentTypeValuesHttpRequest.ts} | 0 packages/twenty-shared/src/workflow/index.ts | 2 +- yarn.lock | 91 ++++ 69 files changed, 1200 insertions(+), 1539 deletions(-) delete mode 100644 packages/twenty-front/src/modules/ai/components/internal/SendMessageWithRecordsContextButton.tsx delete mode 100644 packages/twenty-front/src/modules/ai/rest-api/agent-chat-apollo.api.ts delete mode 100644 packages/twenty-front/src/modules/ai/states/agentStreamingMessageState.ts delete mode 100644 packages/twenty-front/src/modules/ai/types/ParsedStep.ts create mode 100644 packages/twenty-front/src/modules/ai/types/ToolInput.ts create mode 100644 packages/twenty-front/src/modules/ai/types/ToolOutput.ts create mode 100644 packages/twenty-front/src/modules/ai/types/UIMessageWithMetadata.ts delete mode 100644 packages/twenty-front/src/modules/ai/utils/__tests__/parseStream.test.ts create mode 100644 packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts create mode 100644 packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts delete mode 100644 packages/twenty-front/src/modules/ai/utils/parseStream.ts delete mode 100644 packages/twenty-server/@types/common.d.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1758767315179-createAgentChatMessagePartTableAndRemoveRawContent.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/agent-chat-message-part.entity.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/dtos/agent-chat-message-part.dto.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/utils/constructAssistantMessageContentFromStream.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/utils/mapUIMessagePartsToDBParts.ts delete mode 100644 packages/twenty-shared/src/ai/types/ErrorEvent.ts delete mode 100644 packages/twenty-shared/src/ai/types/ReasoningDeltaEvent.ts delete mode 100644 packages/twenty-shared/src/ai/types/StreamEvent.ts delete mode 100644 packages/twenty-shared/src/ai/types/TextBlock.ts delete mode 100644 packages/twenty-shared/src/ai/types/TextDeltaEvent.ts delete mode 100644 packages/twenty-shared/src/ai/types/ToolCallEvent.ts delete mode 100644 packages/twenty-shared/src/ai/types/ToolEvent.ts delete mode 100644 packages/twenty-shared/src/ai/types/ToolResultEvent.ts delete mode 100644 packages/twenty-shared/src/ai/utils/parseStreamLine.ts delete mode 100644 packages/twenty-shared/src/ai/utils/splitStreamIntoLines.ts create mode 100644 packages/twenty-shared/src/types/ExcludeFunctions.ts rename packages/twenty-shared/src/workflow/constants/{contentTypeValuesHttpRequest.ts => ContentTypeValuesHttpRequest.ts} (100%) diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index de3ce74683..e72b887c8d 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -29,6 +29,7 @@ "workerDirectory": "public" }, "dependencies": { + "@ai-sdk/react": "^2.0.51", "@apollo/client": "^3.7.17", "@blocknote/mantine": "^0.31.1", "@blocknote/react": "^0.31.1", @@ -67,6 +68,7 @@ "@tiptap/extensions": "^3.4.2", "@tiptap/react": "^3.4.2", "@xyflow/react": "^12.4.2", + "ai": "^5.0.49", "apollo-link-rest": "^0.9.0", "apollo-upload-client": "^17.0.0", "buffer": "^6.0.3", diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index ad3b4e8d39..86065a4b19 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -71,11 +71,40 @@ export type AgentChatMessage = { createdAt: Scalars['DateTime']; files: Array; id: Scalars['UUID']; - rawContent?: Maybe; + parts: Array; role: Scalars['String']; threadId: Scalars['UUID']; }; +export type AgentChatMessagePart = { + __typename?: 'AgentChatMessagePart'; + createdAt: Scalars['DateTime']; + errorDetails?: Maybe; + errorMessage?: Maybe; + fileFilename?: Maybe; + fileMediaType?: Maybe; + fileUrl?: Maybe; + id: Scalars['UUID']; + messageId: Scalars['UUID']; + orderIndex: Scalars['Int']; + providerMetadata?: Maybe; + reasoningContent?: Maybe; + sourceDocumentFilename?: Maybe; + sourceDocumentMediaType?: Maybe; + sourceDocumentSourceId?: Maybe; + sourceDocumentTitle?: Maybe; + sourceUrlSourceId?: Maybe; + sourceUrlTitle?: Maybe; + sourceUrlUrl?: Maybe; + state?: Maybe; + textContent?: Maybe; + toolCallId?: Maybe; + toolInput?: Maybe; + toolName?: Maybe; + toolOutput?: Maybe; + type: Scalars['String']; +}; + export type AgentChatThread = { __typename?: 'AgentChatThread'; agentId: Scalars['UUID']; @@ -1203,6 +1232,7 @@ export type File = { }; export enum FileFolder { + AgentChat = 'AgentChat', Attachment = 'Attachment', File = 'File', PersonPicture = 'PersonPicture', @@ -4308,7 +4338,7 @@ export type GetAgentChatMessagesQueryVariables = Exact<{ }>; -export type GetAgentChatMessagesQuery = { __typename?: 'Query', agentChatMessages: Array<{ __typename?: 'AgentChatMessage', id: string, threadId: string, role: string, createdAt: string, rawContent?: string | null, 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 }>, files: Array<{ __typename?: 'File', id: string, name: string, fullPath: string, size: number, type: string, createdAt: string }> }> }; export type GetAgentChatThreadsQueryVariables = Exact<{ agentId: Scalars['UUID']; @@ -6568,7 +6598,33 @@ export const GetAgentChatMessagesDocument = gql` threadId role createdAt - rawContent + parts { + id + messageId + orderIndex + type + textContent + reasoningContent + toolName + toolCallId + toolInput + toolOutput + state + errorMessage + errorDetails + sourceUrlSourceId + sourceUrlUrl + sourceUrlTitle + sourceDocumentSourceId + sourceDocumentMediaType + sourceDocumentTitle + sourceDocumentFilename + fileMediaType + fileFilename + fileUrl + providerMetadata + createdAt + } files { id name diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 88df56b83e..8175b1f006 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -71,11 +71,40 @@ export type AgentChatMessage = { createdAt: Scalars['DateTime']; files: Array; id: Scalars['UUID']; - rawContent?: Maybe; + parts: Array; role: Scalars['String']; threadId: Scalars['UUID']; }; +export type AgentChatMessagePart = { + __typename?: 'AgentChatMessagePart'; + createdAt: Scalars['DateTime']; + errorDetails?: Maybe; + errorMessage?: Maybe; + fileFilename?: Maybe; + fileMediaType?: Maybe; + fileUrl?: Maybe; + id: Scalars['UUID']; + messageId: Scalars['UUID']; + orderIndex: Scalars['Int']; + providerMetadata?: Maybe; + reasoningContent?: Maybe; + sourceDocumentFilename?: Maybe; + sourceDocumentMediaType?: Maybe; + sourceDocumentSourceId?: Maybe; + sourceDocumentTitle?: Maybe; + sourceUrlSourceId?: Maybe; + sourceUrlTitle?: Maybe; + sourceUrlUrl?: Maybe; + state?: Maybe; + textContent?: Maybe; + toolCallId?: Maybe; + toolInput?: Maybe; + toolName?: Maybe; + toolOutput?: Maybe; + type: Scalars['String']; +}; + export type AgentChatThread = { __typename?: 'AgentChatThread'; agentId: Scalars['UUID']; @@ -1167,6 +1196,7 @@ export type File = { }; export enum FileFolder { + AgentChat = 'AgentChat', Attachment = 'Attachment', File = 'File', PersonPicture = 'PersonPicture', diff --git a/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx b/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx index 27c47f753f..711a8d6bd8 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx @@ -1,16 +1,13 @@ -import { ErrorStepRenderer } from '@/ai/components/ErrorStepRenderer'; import { ReasoningSummaryDisplay } from '@/ai/components/ReasoningSummaryDisplay'; -import { ToolStepRenderer } from '@/ai/components/ToolStepRenderer'; -import type { ParsedStep } from '@/ai/types/ParsedStep'; -import { hasStructuredStreamData } from '@/ai/utils/hasStructuredStreamData'; -import { parseStream } from '@/ai/utils/parseStream'; import { IconDotsVertical } from 'twenty-ui/display'; import { LazyMarkdownRenderer } from '@/ai/components/LazyMarkdownRenderer'; -import { agentStreamingMessageState } from '@/ai/states/agentStreamingMessageState'; +import { ToolStepRenderer } from '@/ai/components/ToolStepRenderer'; +import { type ToolInput } from '@/ai/types/ToolInput'; +import { type ToolOutput } from '@/ai/types/ToolOutput'; import { keyframes, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; +import type { ToolUIPart, UIDataTypes, UIMessagePart, UITools } from 'ai'; const StyledStepsContainer = styled.div` display: flex; @@ -60,60 +57,55 @@ const LoadingDotsIcon = () => { }; export const AIChatAssistantMessageRenderer = ({ - streamData, + messageParts, + isLastMessageStreaming, }: { - streamData: string; + messageParts: UIMessagePart[]; + isLastMessageStreaming: boolean; }) => { - const agentStreamingMessage = useRecoilValue(agentStreamingMessageState); - const isStreaming = streamData === agentStreamingMessage; - - if (!streamData) { - return ; - } - - const hasStructuredData = hasStructuredStreamData(streamData); - - if (!hasStructuredData) { - return ; - } - - const steps = parseStream(streamData); - - if (!steps.length) { - return ; - } - - const renderStep = (step: ParsedStep, index: number) => { + const renderStep = ( + step: UIMessagePart, + index: number, + ) => { switch (step.type) { - case 'tool': - return ; case 'reasoning': return ( ); case 'text': - return ; - case 'error': - return ( - - ); + return ; default: + { + if (step.type.includes('tool-')) { + const { output, input, type } = step as ToolUIPart; + return ( + + ); + } + } return null; } }; + if (!messageParts.length) { + return ; + } + return (
- {steps.map(renderStep)} - {isStreaming && } + + {messageParts.map(renderStep)} + + {isLastMessageStreaming && }
); }; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx index 9696a017f9..54236df66a 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx @@ -5,10 +5,10 @@ import { Avatar, IconSparkles } from 'twenty-ui/display'; import { AgentChatFilePreview } from '@/ai/components/internal/AgentChatFilePreview'; import { AgentChatMessageRole } from '@/ai/constants/AgentChatMessageRole'; -import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton'; import { AIChatAssistantMessageRenderer } from '@/ai/components/AIChatAssistantMessageRenderer'; -import { type AgentChatMessage } from '~/generated/graphql'; +import { type UIMessageWithMetadata } from '@/ai/types/UIMessageWithMetadata'; +import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton'; import { dateLocaleState } from '~/localization/states/dateLocaleState'; import { beautifyPastDateRelativeToNow } from '~/utils/date-utils'; const StyledMessageBubble = styled.div<{ isUser?: boolean }>` @@ -74,7 +74,8 @@ const StyledMessageText = styled.div<{ isUser?: boolean }>` } p { - margin: ${({ theme }) => theme.spacing(1)} 0; + margin-block: ${({ isUser, theme }) => + isUser ? '0' : `${theme.spacing(1)}`}; line-height: 1.5; } @@ -137,10 +138,10 @@ const StyledFilesContainer = styled.div` export const AIChatMessage = ({ message, - agentStreamingMessage, + isLastMessageStreaming, }: { - message: AgentChatMessage; - agentStreamingMessage: string; + message: UIMessageWithMetadata; + isLastMessageStreaming: boolean; }) => { const theme = useTheme(); const { localeCatalog } = useRecoilValue(dateLocaleState); @@ -170,30 +171,33 @@ export const AIChatMessage = ({ - {message.role === AgentChatMessageRole.ASSISTANT ? ( - - ) : ( - message.rawContent - )} + - {message.files.length > 0 && ( + {message.parts.length > 0 && ( - {message.files.map((file) => ( - - ))} + {message.parts + .filter((part) => part.type === 'file') + .map((file) => ( + + ))} )} - {message.rawContent && ( + {message.parts.length > 0 && message.metadata?.createdAt && ( {beautifyPastDateRelativeToNow( - message.createdAt, + message.metadata?.createdAt, localeCatalog, )} - + part.type === 'text')?.text ?? '' + } + /> )} diff --git a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx index c452890817..bc8200f769 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx @@ -14,10 +14,8 @@ 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 { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload'; -import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { type UIMessageWithMetadata } from '@/ai/types/UIMessageWithMetadata'; import { t } from '@lingui/core/macro'; import { useState } from 'react'; import { Button } from 'twenty-ui/input'; @@ -61,24 +59,23 @@ const StyledButtonsContainer = styled.div` export const AIChatTab = ({ agentId, isWorkflowAgentNodeChat, + uiMessages, }: { agentId: string; isWorkflowAgentNodeChat?: boolean; + uiMessages: UIMessageWithMetadata[]; }) => { const [isDraggingFile, setIsDraggingFile] = useState(false); - const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue( - contextStoreCurrentObjectMetadataItemIdComponentState, - ); - const { - messages, isLoading, input, handleInputChange, - agentStreamingMessage, scrollWrapperId, - } = useAgentChat(agentId); + messages, + handleSendMessage, + isStreaming, + } = useAgentChat(agentId, uiMessages); const { uploadFiles } = useAIChatFileUpload({ agentId }); const { createAgentChatThread } = useCreateNewAIChatThread({ agentId }); @@ -101,14 +98,17 @@ export const AIChatTab = ({ {messages.map((message) => ( ))} )} - {messages.length === 0 && !isLoading && } + {messages.length === 0 && } {isLoading && messages.length === 0 && } @@ -145,11 +145,12 @@ export const AIChatTab = ({ )} - {contextStoreCurrentObjectMetadataItemId ? ( - - ) : ( - - )} + diff --git a/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx b/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx index 036f7c66a8..d91ca8cede 100644 --- a/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx +++ b/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx @@ -6,12 +6,9 @@ import { IconChevronDown, IconChevronUp } from 'twenty-ui/display'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; import { ShimmeringText } from '@/ai/components/ShimmeringText'; +import { type ToolInput } from '@/ai/types/ToolInput'; +import { type ToolOutput } from '@/ai/types/ToolOutput'; import { getToolIcon } from '@/ai/utils/getToolIcon'; -import type { - ToolCallEvent, - ToolEvent, - ToolResultEvent, -} from 'twenty-shared/ai'; import { isDefined } from 'twenty-shared/utils'; const StyledContainer = styled.div` @@ -70,32 +67,26 @@ const StyledIconTextContainer = styled.div` } `; -export const ToolStepRenderer = ({ events }: { events: ToolEvent[] }) => { +export const ToolStepRenderer = ({ + input, + output, + toolName, +}: { + input: ToolInput; + output: ToolOutput; + toolName: string; +}) => { const theme = useTheme(); const [isExpanded, setIsExpanded] = useState(false); - const toolCallEvent = events[0] as ToolCallEvent | undefined; - const toolResultEvent = events.find( - (event): event is ToolResultEvent => event.type === 'tool-result', - ); + const isExpandable = isDefined(output); - if (!toolCallEvent) { - return null; - } - - const toolOutput = - toolResultEvent?.output?.error ?? toolResultEvent?.output?.result; - - const isExpandable = isDefined(toolOutput); - - if (!toolResultEvent) { + if (!output) { return ( - - {toolCallEvent.input.loadingMessage} - + {input?.loadingMessage} @@ -103,13 +94,16 @@ export const ToolStepRenderer = ({ events }: { events: ToolEvent[] }) => { } const displayMessage = - toolResultEvent?.output && - typeof toolResultEvent.output === 'object' && - 'message' in toolResultEvent.output - ? (toolResultEvent.output as { message: string }).message + output && typeof output === 'object' && 'message' in output + ? (output as { message: string }).message : undefined; - const ToolIcon = getToolIcon(toolCallEvent.toolName); + const result = + output && typeof output === 'object' && 'result' in output + ? (output as { result: string }).result + : output; + + const ToolIcon = getToolIcon(toolName); return ( @@ -132,7 +126,7 @@ export const ToolStepRenderer = ({ events }: { events: ToolEvent[] }) => { {isExpandable && ( - {JSON.stringify(toolOutput, null, 2)} + {JSON.stringify(result, null, 2)} )} diff --git a/packages/twenty-front/src/modules/ai/components/internal/AgentChatContextPreview.tsx b/packages/twenty-front/src/modules/ai/components/internal/AgentChatContextPreview.tsx index 1a47acb82e..954c90e970 100644 --- a/packages/twenty-front/src/modules/ai/components/internal/AgentChatContextPreview.tsx +++ b/packages/twenty-front/src/modules/ai/components/internal/AgentChatContextPreview.tsx @@ -2,12 +2,9 @@ import { AgentChatContextRecordPreview } from '@/ai/components/internal/AgentCha import { agentChatSelectedFilesComponentState } from '@/ai/states/agentChatSelectedFilesComponentState'; import { agentChatUploadedFilesComponentState } from '@/ai/states/agentChatUploadedFilesComponentState'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; -import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { useRecoilComponentState } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentState'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import styled from '@emotion/styled'; -import { useLingui } from '@lingui/react/macro'; -import { useDeleteFileMutation } from '~/generated-metadata/graphql'; import { AgentChatFilePreview } from './AgentChatFilePreview'; const StyledContainer = styled.div` @@ -25,31 +22,15 @@ const StyledPreviewsContainer = styled.div` `; export const AgentChatContextPreview = ({ agentId }: { agentId: string }) => { - const { t } = useLingui(); const [agentChatSelectedFiles, setAgentChatSelectedFiles] = useRecoilComponentState(agentChatSelectedFilesComponentState, agentId); const [agentChatUploadedFiles, setAgentChatUploadedFiles] = useRecoilComponentState(agentChatUploadedFilesComponentState, agentId); - const { enqueueErrorSnackBar } = useSnackBar(); - - const [deleteFile] = useDeleteFileMutation(); - - const handleRemoveUploadedFile = async (fileId: string) => { - const originalFiles = agentChatUploadedFiles; - + const handleRemoveUploadedFile = async (fileIndex: number) => { setAgentChatUploadedFiles( - agentChatUploadedFiles.filter((f) => f.id !== fileId), + agentChatUploadedFiles.filter((f, index) => fileIndex !== index), ); - - try { - await deleteFile({ variables: { fileId } }); - } catch { - setAgentChatUploadedFiles(originalFiles); - enqueueErrorSnackBar({ - message: t`Failed to remove file`, - }); - } }; const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue( @@ -71,11 +52,11 @@ export const AgentChatContextPreview = ({ agentId }: { agentId: string }) => { isUploading /> ))} - {agentChatUploadedFiles.map((file) => ( + {agentChatUploadedFiles.map((file, index) => ( handleRemoveUploadedFile(file.id)} + key={index} + onRemove={() => handleRemoveUploadedFile(index)} isUploading={false} /> ))} diff --git a/packages/twenty-front/src/modules/ai/components/internal/AgentChatFilePreview.tsx b/packages/twenty-front/src/modules/ai/components/internal/AgentChatFilePreview.tsx index e01e76e2e4..4a3ac7daa2 100644 --- a/packages/twenty-front/src/modules/ai/components/internal/AgentChatFilePreview.tsx +++ b/packages/twenty-front/src/modules/ai/components/internal/AgentChatFilePreview.tsx @@ -1,34 +1,37 @@ import { getFileType } from '@/activities/files/utils/getFileType'; import { IconMapping, useFileTypeColors } from '@/file/utils/fileIconMappings'; import { useTheme } from '@emotion/react'; +import { type FileUIPart } from 'ai'; import { AvatarChip, Chip, ChipVariant } from 'twenty-ui/components'; import { IconX } from 'twenty-ui/display'; import { Loader } from 'twenty-ui/feedback'; -import { type File as FileDocument } from '~/generated-metadata/graphql'; export const AgentChatFilePreview = ({ file, onRemove, isUploading, }: { - file: File | FileDocument; + file: FileUIPart | File; onRemove?: () => void; isUploading?: boolean; }) => { const theme = useTheme(); const iconColors = useFileTypeColors(); + const fileName = + file instanceof File ? file.name : (file.filename ?? 'Unknown file'); + return ( ) : ( ) } 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 85ab541b88..4c54c81a83 100644 --- a/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx +++ b/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx @@ -1,20 +1,16 @@ -import { Button } from 'twenty-ui/input'; import { t } from '@lingui/core/macro'; -import { useAgentChat } from '@/ai/hooks/useAgentChat'; -import { type ObjectRecord } from '@/object-record/types/ObjectRecord'; +import { Button } from 'twenty-ui/input'; export const SendMessageButton = ({ - records, - agentId, + handleSendMessage, + input, + isLoading, }: { agentId: string; - records?: ObjectRecord[]; + handleSendMessage: () => void; + input: string; + isLoading: boolean; }) => { - const { isLoading, handleSendMessage, input } = useAgentChat( - agentId, - records, - ); - return (