From 32558673c65e9a72b9c68745270ef6677c0972b9 Mon Sep 17 00:00:00 2001 From: Abdul Rahman <81605929+abdulrahmancodes@users.noreply.github.com> Date: Wed, 22 Oct 2025 18:32:41 +0530 Subject: [PATCH] feat: Implement AI Router for Dynamic Agent Selection (#15227) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds intelligent routing system that automatically selects the best agent for user queries based on conversation context. ### Changes: - Added `routerModel` column to workspace table for configurable router LLM selection - Implemented `RouterService` with conversation history analysis and agent matching logic - Created router settings UI in AI Settings page with model dropdown - Removed agent-specific thread associations - threads are now agent-agnostic - Added real-time routing status notification in chat UI with shimmer effect - Removed automatic default assistant agent creation - Renamed GraphQL operations from agent-specific to generic (e.g., `agentChatThreads` → `chatThreads`) --------- Co-authored-by: Félix Malfait Co-authored-by: Félix Malfait --- .../src/generated-metadata/graphql.ts | 206 ++-- .../twenty-front/src/generated/graphql.ts | 46 +- .../AIChatAssistantMessageRenderer.tsx | 18 +- .../modules/ai/components/AIChatMessage.tsx | 20 +- .../src/modules/ai/components/AIChatTab.tsx | 12 +- .../ai/components/AIChatThreadsList.tsx | 21 +- .../ai/components/AgentChatProvider.tsx | 19 +- .../ai/components/RoutingStatusDisplay.tsx | 60 ++ .../ai/components/ToolStepRenderer.tsx | 71 +- .../components/internal/SendMessageButton.tsx | 4 +- .../SendMessageWithRecordsContextButton.tsx | 8 +- .../modules/ai/contexts/AgentChatContext.ts | 4 +- .../mutations/createAgentChatThread.ts | 13 - .../ai/graphql/mutations/createChatThread.ts | 12 + .../ai/graphql/queries/getAgentChatThreads.ts | 13 - ...gentChatMessages.ts => getChatMessages.ts} | 6 +- .../ai/graphql/queries/getChatThreads.ts | 12 + .../src/modules/ai/hooks/useAgentChat.ts | 4 +- .../src/modules/ai/hooks/useAgentChatData.ts | 19 +- .../ai/hooks/useCreateNewAIChatThread.ts | 11 +- .../modules/ai/types/UIMessageWithMetadata.ts | 7 - .../__tests__/groupThreadsByDate.test.ts | 1 - .../ai/utils/mapDBMessagesToUIMessages.ts | 6 +- .../ai/utils/mapDBPartToUIMessagePart.ts | 18 +- .../services/__tests__/apollo.factory.test.ts | 1 + .../modules/app/components/SettingsRoutes.tsx | 12 - .../auth/states/currentWorkspaceState.ts | 2 +- .../CommandMenuAIChatThreadsPage.tsx | 24 +- .../components/CommandMenuAskAIPage.tsx | 25 +- ...ColumnDefinitionsFromFieldMetadata.test.ts | 1 + .../constants/SettingsIntegrationMcp.ts | 20 - .../hooks/useSettingsIntegrationCategories.ts | 6 - .../components/UserAndViewsProviderEffect.tsx | 2 - .../graphql/fragments/userQueryFragment.ts | 4 +- .../components/WorkflowEditActionAiAgent.tsx | 15 +- .../src/pages/settings/ai/SettingsAI.tsx | 83 +- .../pages/settings/ai/SettingsAgentForm.tsx | 69 +- .../ai/components/SettingsAIAgentsTable.tsx | 28 +- .../settings/ai/components/SettingsAIMCP.tsx} | 122 +-- .../components/SettingsAIRouterSettings.tsx | 105 +++ .../forms/components/SettingsAIAgentForm.tsx | 3 +- .../SettingsApplicationDetailContentTab.tsx | 16 +- .../SettingsIntegrationMCPPage.tsx | 36 - .../src/testing/mock-data/users.ts | 3 +- ...1760985484643-AddRouterModelToWorkspace.ts | 19 + ...4826-RemoveDefaultAgentAndThreadAgentId.ts | 37 + .../ai/constants/ai-models.const.ts | 87 ++ .../ai/controllers/ai.controller.spec.ts | 6 +- .../ai/controllers/ai.controller.ts | 3 +- .../ai/services/ai-model-registry.service.ts | 26 +- .../core-modules/ai/services/ai.service.ts | 2 +- .../core-modules/ai/services/tool.service.ts | 29 +- .../types/create-record-params.type.ts | 22 +- .../types/delete-record-params.type.ts | 14 +- .../types/execution-context.type.ts | 11 + .../types/find-records-params.type.ts | 30 +- .../types/record-crud-input.type.ts | 43 + .../types/update-record-params.type.ts | 13 +- .../types/upsert-record-params.type.ts | 12 +- .../bulk-delete-tool.zod-schema.ts | 25 + .../zod-schemas/field-filters.zod-schema.ts | 511 ++++++++++ .../zod-schemas/find-one-tool.zod-schema.ts | 15 + .../zod-schemas/find-tool.zod-schema.ts | 52 ++ .../zod-schemas/order-by.zod-schema.ts | 25 + .../zod-schemas/record-input.zod-schema.ts | 23 + .../record-properties.zod-schema.ts | 249 +++++ .../soft-delete-tool.zod-schema.ts | 18 + .../twenty-config/config-variables.ts | 13 +- .../workspace/dtos/update-workspace-input.ts | 5 + .../workspace/workspace.entity.ts | 13 +- .../workspace/workspace.resolver.ts | 31 +- .../agent/agent-chat-thread.entity.ts | 12 - .../agent/agent-chat.controller.ts | 29 - .../agent/agent-chat.resolver.ts | 19 +- .../agent/agent-chat.service.ts | 11 +- .../agent/agent-execution.service.ts | 26 + .../agent/agent-streaming.service.ts | 59 +- .../agent/agent-title-generation.service.ts | 2 +- .../metadata-modules/agent/agent.entity.ts | 4 - .../metadata-modules/agent/agent.module.ts | 2 + .../agent/dtos/agent-chat-thread.dto.ts | 3 - .../dtos/create-agent-chat-thread.input.ts | 12 - .../services/agent-actor-context.service.ts | 10 +- .../agent/utils/agent-tool-schema.utils.ts | 876 ------------------ .../agent/utils/mapUIMessagePartsToDBParts.ts | 20 +- .../agent/utils/repair-tool-call.util.ts | 72 ++ .../ai-router/ai-router.module.ts | 15 + .../ai-router/ai-router.service.ts | 158 ++++ .../flat-agent/types/flat-agent.type.ts | 1 - .../dev-seeder/core/utils/seed-agents.util.ts | 77 +- .../workspace-manager.service.ts | 30 - .../services/workspace-sync-agent.service.ts | 125 --- .../agents/data-navigator-agent.ts | 14 + .../workflow-record-crud-action-input.type.ts | 49 +- .../agent/utils/agent-tool-test-utils.ts | 1 - packages/twenty-shared/src/ai/index.ts | 4 + .../src/ai/types/DataMessagePart.ts | 6 + .../src/ai/types/ExtendedUIMessage.ts | 8 + .../src/ai/types/ExtendedUIMessagePart.ts | 4 + .../twenty-shared/src/types/SettingsPath.ts | 1 - .../display/icon/components/TablerIcons.ts | 1 + packages/twenty-ui/src/display/index.ts | 1 + 102 files changed, 2262 insertions(+), 1912 deletions(-) create mode 100644 packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx delete mode 100644 packages/twenty-front/src/modules/ai/graphql/mutations/createAgentChatThread.ts create mode 100644 packages/twenty-front/src/modules/ai/graphql/mutations/createChatThread.ts delete mode 100644 packages/twenty-front/src/modules/ai/graphql/queries/getAgentChatThreads.ts rename packages/twenty-front/src/modules/ai/graphql/queries/{getAgentChatMessages.ts => getChatMessages.ts} (82%) create mode 100644 packages/twenty-front/src/modules/ai/graphql/queries/getChatThreads.ts delete mode 100644 packages/twenty-front/src/modules/ai/types/UIMessageWithMetadata.ts delete mode 100644 packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts rename packages/twenty-front/src/{modules/settings/integrations/components/SettingsIntegrationMCP.tsx => pages/settings/ai/components/SettingsAIMCP.tsx} (58%) create mode 100644 packages/twenty-front/src/pages/settings/ai/components/SettingsAIRouterSettings.tsx delete mode 100644 packages/twenty-front/src/pages/settings/integrations/SettingsIntegrationMCPPage.tsx create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1760985484643-AddRouterModelToWorkspace.ts create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1760994964826-RemoveDefaultAgentAndThreadAgentId.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/types/execution-context.type.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/types/record-crud-input.type.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/bulk-delete-tool.zod-schema.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/field-filters.zod-schema.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/find-one-tool.zod-schema.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/find-tool.zod-schema.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/order-by.zod-schema.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/record-input.zod-schema.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/record-properties.zod-schema.ts create mode 100644 packages/twenty-server/src/engine/core-modules/record-crud/zod-schemas/soft-delete-tool.zod-schema.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/dtos/create-agent-chat-thread.input.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/utils/agent-tool-schema.utils.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/agent/utils/repair-tool-call.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-router/ai-router.module.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/ai-router/ai-router.service.ts create mode 100644 packages/twenty-shared/src/ai/types/DataMessagePart.ts create mode 100644 packages/twenty-shared/src/ai/types/ExtendedUIMessage.ts create mode 100644 packages/twenty-shared/src/ai/types/ExtendedUIMessagePart.ts diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 3fc9510208..9002294dd3 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -108,7 +108,6 @@ export type AgentChatMessagePart = { export type AgentChatThread = { __typename?: 'AgentChatThread'; - agentId: Scalars['UUID']; createdAt: Scalars['DateTime']; id: Scalars['UUID']; title?: Maybe; @@ -728,10 +727,6 @@ export type CoreViewSort = { workspaceId: Scalars['UUID']; }; -export type CreateAgentChatThreadInput = { - agentId: Scalars['UUID']; -}; - export type CreateAgentHandoffInput = { description?: InputMaybe; fromAgentId: Scalars['UUID']; @@ -1724,10 +1719,10 @@ export type Mutation = { checkPublicDomainValidRecords?: Maybe; checkoutSession: BillingSessionOutput; computeStepOutputSchema: Scalars['JSON']; - createAgentChatThread: AgentChatThread; createAgentHandoff: Scalars['Boolean']; createApiKey: ApiKey; createApprovedAccessDomain: ApprovedAccessDomain; + createChatThread: AgentChatThread; createCoreView: CoreView; createCoreViewField: CoreViewField; createCoreViewFilter: CoreViewFilter; @@ -1942,11 +1937,6 @@ export type MutationComputeStepOutputSchemaArgs = { }; -export type MutationCreateAgentChatThreadArgs = { - input: CreateAgentChatThreadInput; -}; - - export type MutationCreateAgentHandoffArgs = { input: CreateAgentHandoffInput; }; @@ -3106,12 +3096,12 @@ export type PublishServerlessFunctionInput = { export type Query = { __typename?: 'Query'; - agentChatMessages: Array; - agentChatThread: AgentChatThread; - agentChatThreads: Array; apiKey?: Maybe; apiKeys: Array; billingPortalSession: BillingSessionOutput; + chatMessages: Array; + chatThread: AgentChatThread; + chatThreads: Array; checkUserExists: CheckUserExistOutput; checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValidOutput; currentUser: User; @@ -3193,21 +3183,6 @@ export type Query = { }; -export type QueryAgentChatMessagesArgs = { - threadId: Scalars['UUID']; -}; - - -export type QueryAgentChatThreadArgs = { - id: Scalars['UUID']; -}; - - -export type QueryAgentChatThreadsArgs = { - agentId: Scalars['UUID']; -}; - - export type QueryApiKeyArgs = { input: GetApiKeyInput; }; @@ -3218,6 +3193,16 @@ export type QueryBillingPortalSessionArgs = { }; +export type QueryChatMessagesArgs = { + threadId: Scalars['UUID']; +}; + + +export type QueryChatThreadArgs = { + id: Scalars['UUID']; +}; + + export type QueryCheckUserExistsArgs = { captchaToken?: InputMaybe; email: Scalars['String']; @@ -4346,6 +4331,7 @@ export type UpdateWorkspaceInput = { isPublicInviteLinkEnabled?: InputMaybe; isTwoFactorAuthenticationEnforced?: InputMaybe; logo?: InputMaybe; + routerModel?: InputMaybe; subdomain?: InputMaybe; trashRetentionDays?: InputMaybe; }; @@ -4649,7 +4635,6 @@ export type Workspace = { customDomain?: Maybe; databaseSchema: Scalars['String']; databaseUrl: Scalars['String']; - defaultAgent?: Maybe; defaultRole?: Maybe; deletedAt?: Maybe; displayName?: Maybe; @@ -4665,6 +4650,7 @@ export type Workspace = { isTwoFactorAuthenticationEnforced: Scalars['Boolean']; logo?: Maybe; metadataVersion: Scalars['Float']; + routerModel: Scalars['String']; subdomain: Scalars['String']; trashRetentionDays: Scalars['Float']; updatedAt: Scalars['DateTime']; @@ -4788,13 +4774,6 @@ export type AssignRoleToAgentMutationVariables = Exact<{ export type AssignRoleToAgentMutation = { __typename?: 'Mutation', assignRoleToAgent: boolean }; -export type CreateAgentChatThreadMutationVariables = Exact<{ - input: CreateAgentChatThreadInput; -}>; - - -export type CreateAgentChatThreadMutation = { __typename?: 'Mutation', createAgentChatThread: { __typename?: 'AgentChatThread', id: string, agentId: string, title?: string | null, createdAt: string, updatedAt: string } }; - export type CreateAgentHandoffMutationVariables = Exact<{ input: CreateAgentHandoffInput; }>; @@ -4802,6 +4781,11 @@ export type CreateAgentHandoffMutationVariables = Exact<{ export type CreateAgentHandoffMutation = { __typename?: 'Mutation', createAgentHandoff: boolean }; +export type CreateChatThreadMutationVariables = Exact<{ [key: string]: never; }>; + + +export type CreateChatThreadMutation = { __typename?: 'Mutation', createChatThread: { __typename?: 'AgentChatThread', id: string, title?: string | null, createdAt: string, updatedAt: string } }; + export type CreateOneAgentMutationVariables = Exact<{ input: CreateAgentInput; }>; @@ -4863,19 +4847,17 @@ export type FindOneAgentQueryVariables = Exact<{ export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string } }; -export type GetAgentChatMessagesQueryVariables = Exact<{ +export type GetChatMessagesQueryVariables = Exact<{ threadId: Scalars['UUID']; }>; -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 GetChatMessagesQuery = { __typename?: 'Query', chatMessages: 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']; -}>; +export type GetChatThreadsQueryVariables = Exact<{ [key: string]: never; }>; -export type GetAgentChatThreadsQuery = { __typename?: 'Query', agentChatThreads: Array<{ __typename?: 'AgentChatThread', id: string, agentId: string, title?: string | null, createdAt: string, updatedAt: string }> }; +export type GetChatThreadsQuery = { __typename?: 'Query', chatThreads: Array<{ __typename?: 'AgentChatThread', id: string, title?: string | null, createdAt: string, updatedAt: string }> }; export type TrackAnalyticsMutationVariables = Exact<{ type: AnalyticsType; @@ -5808,7 +5790,7 @@ export type BillingSubscriptionFragmentFragment = { __typename?: 'BillingSubscri export type CurrentBillingSubscriptionFragmentFragment = { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null }; -export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: string } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; +export type UserQueryFragmentFragment = { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, routerModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } }; export type WorkspaceUrlsFragmentFragment = { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }; @@ -5827,7 +5809,7 @@ export type UploadProfilePictureMutation = { __typename?: 'Mutation', uploadProf export type GetCurrentUserQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null, defaultAgent?: { __typename?: 'Agent', id: string } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; +export type GetCurrentUserQuery = { __typename?: 'Query', currentUser: { __typename?: 'User', id: string, firstName: string, lastName: string, email: string, canAccessFullAdminPanel: boolean, canImpersonate: boolean, supportUserHash?: string | null, onboardingStatus?: OnboardingStatus | null, userVars?: any | null, workspaceMember?: { __typename?: 'WorkspaceMember', id: string, colorScheme: string, avatarUrl?: string | null, locale?: string | null, userEmail: string, timeZone?: string | null, dateFormat?: WorkspaceMemberDateFormatEnum | null, timeFormat?: WorkspaceMemberTimeFormatEnum | null, calendarStartDay?: number | null, numberFormat?: WorkspaceMemberNumberFormatEnum | null, name: { __typename?: 'FullName', firstName: string, lastName: string } } | null, workspaceMembers?: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, deletedWorkspaceMembers?: Array<{ __typename?: 'DeletedWorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }> | null, currentUserWorkspace?: { __typename?: 'UserWorkspace', permissionFlags?: Array | null, objectsPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, twoFactorAuthenticationMethodSummary?: Array<{ __typename?: 'TwoFactorAuthenticationMethodDTO', twoFactorAuthenticationMethodId: string, status: string, strategy: string }> | null } | null, currentWorkspace?: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, inviteHash?: string | null, allowImpersonation: boolean, activationStatus: WorkspaceActivationStatus, isPublicInviteLinkEnabled: boolean, isGoogleAuthEnabled: boolean, isMicrosoftAuthEnabled: boolean, isPasswordAuthEnabled: boolean, subdomain: string, hasValidEnterpriseKey: boolean, customDomain?: string | null, isCustomDomainEnabled: boolean, metadataVersion: number, workspaceMembersCount?: number | null, routerModel: string, isTwoFactorAuthenticationEnforced: boolean, trashRetentionDays: number, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, featureFlags?: Array<{ __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean }> | null, currentBillingSubscription?: { __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, interval?: SubscriptionInterval | null, metadata: any, currentPeriodEnd?: string | null, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }>, billingSubscriptionItems?: Array<{ __typename?: 'BillingSubscriptionItemDTO', id: string, hasReachedCurrentPeriodCap: boolean, quantity?: number | null, stripePriceId: string, billingProduct: { __typename?: 'BillingLicensedProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } | { __typename?: 'BillingMeteredProduct', name: string, description: string, images?: Array | null, metadata: { __typename?: 'BillingProductMetadata', productKey: BillingProductKey, planKey: BillingPlanKey, priceUsageBased: BillingUsageType } } }> | null } | null, billingSubscriptions: Array<{ __typename?: 'BillingSubscription', id: string, status: SubscriptionStatus, metadata: any, phases: Array<{ __typename?: 'BillingSubscriptionSchedulePhase', start_date: number, end_date: number, items: Array<{ __typename?: 'BillingSubscriptionSchedulePhaseItem', price: string, quantity?: number | null }> }> }>, defaultRole?: { __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean } | null } | null, availableWorkspaces: { __typename?: 'AvailableWorkspaces', availableWorkspacesForSignIn: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }>, availableWorkspacesForSignUp: Array<{ __typename?: 'AvailableWorkspace', id: string, displayName?: string | null, loginToken?: string | null, inviteHash?: string | null, personalInviteToken?: string | null, logo?: string | null, workspaceUrls: { __typename?: 'WorkspaceUrls', subdomainUrl: string, customUrl?: string | null }, sso: Array<{ __typename?: 'SSOConnection', type: IdentityProviderType, id: string, issuer: string, name: string, status: SsoIdentityProviderStatus }> }> } } }; export type ViewFieldFragmentFragment = { __typename?: 'CoreViewField', id: string, fieldMetadataId: string, viewId: string, isVisible: boolean, position: number, size: number, aggregateOperation?: AggregateOperations | null, createdAt: string, updatedAt: string, deletedAt?: string | null }; @@ -6792,9 +6774,7 @@ export const UserQueryFragmentFragmentDoc = gql` defaultRole { ...RoleFragment } - defaultAgent { - id - } + routerModel isTwoFactorAuthenticationEnforced trashRetentionDays } @@ -6946,43 +6926,6 @@ export function useAssignRoleToAgentMutation(baseOptions?: Apollo.MutationHookOp export type AssignRoleToAgentMutationHookResult = ReturnType; export type AssignRoleToAgentMutationResult = Apollo.MutationResult; export type AssignRoleToAgentMutationOptions = Apollo.BaseMutationOptions; -export const CreateAgentChatThreadDocument = gql` - mutation CreateAgentChatThread($input: CreateAgentChatThreadInput!) { - createAgentChatThread(input: $input) { - id - agentId - title - createdAt - updatedAt - } -} - `; -export type CreateAgentChatThreadMutationFn = Apollo.MutationFunction; - -/** - * __useCreateAgentChatThreadMutation__ - * - * To run a mutation, you first call `useCreateAgentChatThreadMutation` within a React component and pass it any options that fit your needs. - * When your component renders, `useCreateAgentChatThreadMutation` returns a tuple that includes: - * - A mutate function that you can call at any time to execute the mutation - * - An object with fields that represent the current status of the mutation's execution - * - * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; - * - * @example - * const [createAgentChatThreadMutation, { data, loading, error }] = useCreateAgentChatThreadMutation({ - * variables: { - * input: // value for 'input' - * }, - * }); - */ -export function useCreateAgentChatThreadMutation(baseOptions?: Apollo.MutationHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(CreateAgentChatThreadDocument, options); - } -export type CreateAgentChatThreadMutationHookResult = ReturnType; -export type CreateAgentChatThreadMutationResult = Apollo.MutationResult; -export type CreateAgentChatThreadMutationOptions = Apollo.BaseMutationOptions; export const CreateAgentHandoffDocument = gql` mutation CreateAgentHandoff($input: CreateAgentHandoffInput!) { createAgentHandoff(input: $input) @@ -7014,6 +6957,41 @@ export function useCreateAgentHandoffMutation(baseOptions?: Apollo.MutationHookO export type CreateAgentHandoffMutationHookResult = ReturnType; export type CreateAgentHandoffMutationResult = Apollo.MutationResult; export type CreateAgentHandoffMutationOptions = Apollo.BaseMutationOptions; +export const CreateChatThreadDocument = gql` + mutation CreateChatThread { + createChatThread { + id + title + createdAt + updatedAt + } +} + `; +export type CreateChatThreadMutationFn = Apollo.MutationFunction; + +/** + * __useCreateChatThreadMutation__ + * + * To run a mutation, you first call `useCreateChatThreadMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateChatThreadMutation` returns a tuple that includes: + * - A mutate function that you can call at any time to execute the mutation + * - An object with fields that represent the current status of the mutation's execution + * + * @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2; + * + * @example + * const [createChatThreadMutation, { data, loading, error }] = useCreateChatThreadMutation({ + * variables: { + * }, + * }); + */ +export function useCreateChatThreadMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateChatThreadDocument, options); + } +export type CreateChatThreadMutationHookResult = ReturnType; +export type CreateChatThreadMutationResult = Apollo.MutationResult; +export type CreateChatThreadMutationOptions = Apollo.BaseMutationOptions; export const CreateOneAgentDocument = gql` mutation CreateOneAgent($input: CreateAgentInput!) { createOneAgent(input: $input) { @@ -7336,9 +7314,9 @@ export function useFindOneAgentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio export type FindOneAgentQueryHookResult = ReturnType; export type FindOneAgentLazyQueryHookResult = ReturnType; export type FindOneAgentQueryResult = Apollo.QueryResult; -export const GetAgentChatMessagesDocument = gql` - query GetAgentChatMessages($threadId: UUID!) { - agentChatMessages(threadId: $threadId) { +export const GetChatMessagesDocument = gql` + query GetChatMessages($threadId: UUID!) { + chatMessages(threadId: $threadId) { id threadId role @@ -7375,37 +7353,36 @@ export const GetAgentChatMessagesDocument = gql` `; /** - * __useGetAgentChatMessagesQuery__ + * __useGetChatMessagesQuery__ * - * To run a query within a React component, call `useGetAgentChatMessagesQuery` and pass it any options that fit your needs. - * When your component renders, `useGetAgentChatMessagesQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useGetChatMessagesQuery` and pass it any options that fit your needs. + * When your component renders, `useGetChatMessagesQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useGetAgentChatMessagesQuery({ + * const { data, loading, error } = useGetChatMessagesQuery({ * variables: { * threadId: // value for 'threadId' * }, * }); */ -export function useGetAgentChatMessagesQuery(baseOptions: Apollo.QueryHookOptions) { +export function useGetChatMessagesQuery(baseOptions: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetAgentChatMessagesDocument, options); + return Apollo.useQuery(GetChatMessagesDocument, options); } -export function useGetAgentChatMessagesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetChatMessagesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetAgentChatMessagesDocument, options); + return Apollo.useLazyQuery(GetChatMessagesDocument, options); } -export type GetAgentChatMessagesQueryHookResult = ReturnType; -export type GetAgentChatMessagesLazyQueryHookResult = ReturnType; -export type GetAgentChatMessagesQueryResult = Apollo.QueryResult; -export const GetAgentChatThreadsDocument = gql` - query GetAgentChatThreads($agentId: UUID!) { - agentChatThreads(agentId: $agentId) { +export type GetChatMessagesQueryHookResult = ReturnType; +export type GetChatMessagesLazyQueryHookResult = ReturnType; +export type GetChatMessagesQueryResult = Apollo.QueryResult; +export const GetChatThreadsDocument = gql` + query GetChatThreads { + chatThreads { id - agentId title createdAt updatedAt @@ -7414,32 +7391,31 @@ export const GetAgentChatThreadsDocument = gql` `; /** - * __useGetAgentChatThreadsQuery__ + * __useGetChatThreadsQuery__ * - * To run a query within a React component, call `useGetAgentChatThreadsQuery` and pass it any options that fit your needs. - * When your component renders, `useGetAgentChatThreadsQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useGetChatThreadsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetChatThreadsQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useGetAgentChatThreadsQuery({ + * const { data, loading, error } = useGetChatThreadsQuery({ * variables: { - * agentId: // value for 'agentId' * }, * }); */ -export function useGetAgentChatThreadsQuery(baseOptions: Apollo.QueryHookOptions) { +export function useGetChatThreadsQuery(baseOptions?: Apollo.QueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetAgentChatThreadsDocument, options); + return Apollo.useQuery(GetChatThreadsDocument, options); } -export function useGetAgentChatThreadsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetChatThreadsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetAgentChatThreadsDocument, options); + return Apollo.useLazyQuery(GetChatThreadsDocument, options); } -export type GetAgentChatThreadsQueryHookResult = ReturnType; -export type GetAgentChatThreadsLazyQueryHookResult = ReturnType; -export type GetAgentChatThreadsQueryResult = Apollo.QueryResult; +export type GetChatThreadsQueryHookResult = ReturnType; +export type GetChatThreadsLazyQueryHookResult = ReturnType; +export type GetChatThreadsQueryResult = Apollo.QueryResult; export const TrackAnalyticsDocument = gql` mutation TrackAnalytics($type: AnalyticsType!, $event: String, $name: String, $properties: JSON) { trackAnalytics(type: $type, event: $event, name: $name, properties: $properties) { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 4e2ca072f7..3bac4c43fb 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -108,7 +108,6 @@ export type AgentChatMessagePart = { export type AgentChatThread = { __typename?: 'AgentChatThread'; - agentId: Scalars['UUID']; createdAt: Scalars['DateTime']; id: Scalars['UUID']; title?: Maybe; @@ -728,10 +727,6 @@ export type CoreViewSort = { workspaceId: Scalars['UUID']; }; -export type CreateAgentChatThreadInput = { - agentId: Scalars['UUID']; -}; - export type CreateAgentHandoffInput = { description?: InputMaybe; fromAgentId: Scalars['UUID']; @@ -1681,10 +1676,10 @@ export type Mutation = { checkPublicDomainValidRecords?: Maybe; checkoutSession: BillingSessionOutput; computeStepOutputSchema: Scalars['JSON']; - createAgentChatThread: AgentChatThread; createAgentHandoff: Scalars['Boolean']; createApiKey: ApiKey; createApprovedAccessDomain: ApprovedAccessDomain; + createChatThread: AgentChatThread; createCoreView: CoreView; createCoreViewField: CoreViewField; createCoreViewFilter: CoreViewFilter; @@ -1893,11 +1888,6 @@ export type MutationComputeStepOutputSchemaArgs = { }; -export type MutationCreateAgentChatThreadArgs = { - input: CreateAgentChatThreadInput; -}; - - export type MutationCreateAgentHandoffArgs = { input: CreateAgentHandoffInput; }; @@ -3017,12 +3007,12 @@ export type PublishServerlessFunctionInput = { export type Query = { __typename?: 'Query'; - agentChatMessages: Array; - agentChatThread: AgentChatThread; - agentChatThreads: Array; apiKey?: Maybe; apiKeys: Array; billingPortalSession: BillingSessionOutput; + chatMessages: Array; + chatThread: AgentChatThread; + chatThreads: Array; checkUserExists: CheckUserExistOutput; checkWorkspaceInviteHashIsValid: WorkspaceInviteHashValidOutput; currentUser: User; @@ -3101,21 +3091,6 @@ export type Query = { }; -export type QueryAgentChatMessagesArgs = { - threadId: Scalars['UUID']; -}; - - -export type QueryAgentChatThreadArgs = { - id: Scalars['UUID']; -}; - - -export type QueryAgentChatThreadsArgs = { - agentId: Scalars['UUID']; -}; - - export type QueryApiKeyArgs = { input: GetApiKeyInput; }; @@ -3126,6 +3101,16 @@ export type QueryBillingPortalSessionArgs = { }; +export type QueryChatMessagesArgs = { + threadId: Scalars['UUID']; +}; + + +export type QueryChatThreadArgs = { + id: Scalars['UUID']; +}; + + export type QueryCheckUserExistsArgs = { captchaToken?: InputMaybe; email: Scalars['String']; @@ -4184,6 +4169,7 @@ export type UpdateWorkspaceInput = { isPublicInviteLinkEnabled?: InputMaybe; isTwoFactorAuthenticationEnforced?: InputMaybe; logo?: InputMaybe; + routerModel?: InputMaybe; subdomain?: InputMaybe; trashRetentionDays?: InputMaybe; }; @@ -4477,7 +4463,6 @@ export type Workspace = { customDomain?: Maybe; databaseSchema: Scalars['String']; databaseUrl: Scalars['String']; - defaultAgent?: Maybe; defaultRole?: Maybe; deletedAt?: Maybe; displayName?: Maybe; @@ -4493,6 +4478,7 @@ export type Workspace = { isTwoFactorAuthenticationEnforced: Scalars['Boolean']; logo?: Maybe; metadataVersion: Scalars['Float']; + routerModel: Scalars['String']; subdomain: Scalars['String']; trashRetentionDays: Scalars['Float']; updatedAt: Scalars['DateTime']; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx b/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx index c35c0b91b2..5c5680924f 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 { ReasoningSummaryDisplay } from '@/ai/components/ReasoningSummaryDisplay'; +import { RoutingStatusDisplay } from '@/ai/components/RoutingStatusDisplay'; import { IconDotsVertical } from 'twenty-ui/display'; import { LazyMarkdownRenderer } from '@/ai/components/LazyMarkdownRenderer'; import { ToolStepRenderer } from '@/ai/components/ToolStepRenderer'; import { keyframes, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import { - isToolUIPart, - type UIDataTypes, - type UIMessagePart, - type UITools, -} from 'ai'; +import { isToolUIPart } from 'ai'; +import { type ExtendedUIMessagePart } from 'twenty-shared/ai'; const StyledMessagePartsContainer = styled.div` display: flex; @@ -64,14 +61,11 @@ export const AIChatAssistantMessageRenderer = ({ isLastMessageStreaming, hasError, }: { - messageParts: UIMessagePart[]; + messageParts: ExtendedUIMessagePart[]; isLastMessageStreaming: boolean; hasError?: boolean; }) => { - const renderMessagePart = ( - part: UIMessagePart, - index: number, - ) => { + const renderMessagePart = (part: ExtendedUIMessagePart, index: number) => { switch (part.type) { case 'reasoning': return ( @@ -83,6 +77,8 @@ export const AIChatAssistantMessageRenderer = ({ ); case 'text': return ; + case 'data-routing-status': + return ; default: { if (isToolUIPart(part)) { diff --git a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx index bd7acb7239..02b4de3ce2 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx @@ -9,10 +9,10 @@ import { AgentChatMessageRole } from '@/ai/constants/AgentChatMessageRole'; import { AIChatAssistantMessageRenderer } from '@/ai/components/AIChatAssistantMessageRenderer'; import { AIChatErrorMessage } from '@/ai/components/AIChatErrorMessage'; import { AIChatErrorMessageWithRecordsContext } from '@/ai/components/internal/AIChatErrorMessageWithRecordsContext'; -import { type UIMessageWithMetadata } from '@/ai/types/UIMessageWithMetadata'; import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState'; import { LightCopyIconButton } from '@/object-record/record-field/ui/components/LightCopyIconButton'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { type ExtendedUIMessage } from 'twenty-shared/ai'; import { isDefined } from 'twenty-shared/utils'; import { dateLocaleState } from '~/localization/states/dateLocaleState'; import { beautifyPastDateRelativeToNow } from '~/utils/date-utils'; @@ -51,7 +51,9 @@ const StyledMessageText = styled.div<{ isUser?: boolean }>` max-width: 100%; word-wrap: break-word; overflow-wrap: break-word; - white-space: pre-wrap; + /* Pre-wrap within the whole container turns every newline between block + elements into extra spacing; keep normal flow and only pre-wrap code. */ + white-space: normal; code { overflow: auto; @@ -146,7 +148,7 @@ export const AIChatMessage = ({ isLastMessageStreaming, error, }: { - message: UIMessageWithMetadata; + message: ExtendedUIMessage; isLastMessageStreaming: boolean; error?: Error | null; }) => { @@ -160,6 +162,8 @@ export const AIChatMessage = ({ const showError = isDefined(error) && message.role === AgentChatMessageRole.ASSISTANT; + const fileParts = message.parts.filter((part) => part.type === 'file'); + return ( - {message.parts.length > 0 && ( + {fileParts.length > 0 && ( - {message.parts - .filter((part) => part.type === 'file') - .map((file) => ( - - ))} + {fileParts.map((file) => ( + + ))} )} {showError && diff --git a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx index 6248bfd346..362692cfb1 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatTab.tsx @@ -59,7 +59,7 @@ const StyledButtonsContainer = styled.div` gap: ${({ theme }) => theme.spacing(2)}; `; -export const AIChatTab = ({ agentId }: { agentId: string }) => { +export const AIChatTab = () => { const [isDraggingFile, setIsDraggingFile] = useState(false); const { @@ -70,14 +70,14 @@ export const AIChatTab = ({ agentId }: { agentId: string }) => { messages, isStreaming, error, - } = useAgentChat(agentId); + } = useAgentChat(); const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValue( contextStoreCurrentObjectMetadataItemIdComponentState, ); const { uploadFiles } = useAIChatFileUpload(); - const { createAgentChatThread } = useCreateNewAIChatThread({ agentId }); + const { createChatThread } = useCreateNewAIChatThread(); const { navigateCommandMenu } = useCommandMenu(); return ( @@ -141,13 +141,13 @@ export const AIChatTab = ({ agentId }: { agentId: string }) => { variant="secondary" size="small" Icon={IconMessageCirclePlus} - onClick={() => createAgentChatThread()} + onClick={() => createChatThread()} /> {contextStoreCurrentObjectMetadataItemId ? ( - + ) : ( - + )} diff --git a/packages/twenty-front/src/modules/ai/components/AIChatThreadsList.tsx b/packages/twenty-front/src/modules/ai/components/AIChatThreadsList.tsx index 05dfec6ecc..7b3515172b 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatThreadsList.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatThreadsList.tsx @@ -10,7 +10,7 @@ import { Key } from 'ts-key-enum'; import { capitalize } from 'twenty-shared/utils'; import { Button } from 'twenty-ui/input'; import { getOsControlSymbol } from 'twenty-ui/utilities'; -import { useGetAgentChatThreadsQuery } from '~/generated-metadata/graphql'; +import { useGetChatThreadsQuery } from '~/generated-metadata/graphql'; const StyledContainer = styled.div` background: ${({ theme }) => theme.background.secondary}; @@ -33,24 +33,21 @@ const StyledButtonsContainer = styled.div` border-top: 1px solid ${({ theme }) => theme.border.color.medium}; `; -export const AIChatThreadsList = ({ agentId }: { agentId: string }) => { - const { createAgentChatThread } = useCreateNewAIChatThread({ agentId }); +export const AIChatThreadsList = () => { + const { createChatThread } = useCreateNewAIChatThread(); - const focusId = `${agentId}-threads-list`; + const focusId = 'threads-list'; useHotkeysOnFocusedElement({ keys: [`${Key.Control}+${Key.Enter}`, `${Key.Meta}+${Key.Enter}`], - callback: () => createAgentChatThread(), + callback: () => createChatThread(), focusId, - dependencies: [createAgentChatThread, agentId], + dependencies: [createChatThread], }); - const { data: { agentChatThreads = [] } = {}, loading } = - useGetAgentChatThreadsQuery({ - variables: { agentId }, - }); + const { data: { chatThreads = [] } = {}, loading } = useGetChatThreadsQuery(); - const groupedThreads = groupThreadsByDate(agentChatThreads); + const groupedThreads = groupThreadsByDate(chatThreads); if (loading === true) { return ; @@ -75,7 +72,7 @@ export const AIChatThreadsList = ({ agentId }: { agentId: string }) => { accent="blue" size="medium" title="New chat" - onClick={() => createAgentChatThread()} + onClick={() => createChatThread()} hotkeys={[getOsControlSymbol(), '⏎']} /> diff --git a/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx b/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx index 45e74526d6..f22d5d9b20 100644 --- a/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx +++ b/packages/twenty-front/src/modules/ai/components/AgentChatProvider.tsx @@ -1,19 +1,18 @@ import { AgentChatContext } from '@/ai/contexts/AgentChatContext'; import { useAgentChatData } from '@/ai/hooks/useAgentChatData'; import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState'; -import { type UIMessageWithMetadata } from '@/ai/types/UIMessageWithMetadata'; import { REST_API_BASE_URL } from '@/apollo/constant/rest-api-base-url'; import { getTokenPair } from '@/apollo/utils/getTokenPair'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { Chat } from '@ai-sdk/react'; import { DefaultChatTransport } from 'ai'; import { Suspense } from 'react'; import { useRecoilValue } from 'recoil'; +import { type ExtendedUIMessage } from 'twenty-shared/ai'; import { FeatureFlagKey } from '~/generated/graphql'; const createLoadingChat = () => - new Chat({ + new Chat({ transport: new DefaultChatTransport({ api: `${REST_API_BASE_URL}/agent-chat/stream`, headers: () => ({}), @@ -23,18 +22,16 @@ const createLoadingChat = () => }); const AgentChatProviderContent = ({ - agentId, children, }: { - agentId: string; children: React.ReactNode; }) => { - const { uiMessages, isLoading } = useAgentChatData(agentId); + const { uiMessages, isLoading } = useAgentChatData(); const currentAIChatThread = useRecoilValue(currentAIChatThreadState); const chatConfig = isLoading ? createLoadingChat() - : new Chat({ + : new Chat({ transport: new DefaultChatTransport({ api: `${REST_API_BASE_URL}/agent-chat/stream`, headers: () => ({ @@ -59,11 +56,9 @@ export const AgentChatProvider = ({ }: { children: React.ReactNode; }) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const agentId = currentWorkspace?.defaultAgent?.id; const isAiEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED); - if (!isAiEnabled || !agentId) { + if (!isAiEnabled) { return ( } > - - {children} - + {children} ); }; diff --git a/packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx b/packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx new file mode 100644 index 0000000000..468e55276a --- /dev/null +++ b/packages/twenty-front/src/modules/ai/components/RoutingStatusDisplay.tsx @@ -0,0 +1,60 @@ +import { ShimmeringText } from '@/ai/components/ShimmeringText'; +import { keyframes } from '@emotion/react'; +import styled from '@emotion/styled'; +import { type DataMessagePart } from 'twenty-shared/ai'; +import { IconCpu, IconSparkles } from 'twenty-ui/display'; + +const pulseAnimation = keyframes` + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +`; + +const StyledRoutingContainer = styled.div` + align-items: center; + background: ${({ theme }) => theme.background.transparent.lighter}; + border: ${({ theme }) => `1px dashed ${theme.border.color.medium}`}; + border-radius: ${({ theme }) => theme.border.radius.md}; + display: flex; + font-size: ${({ theme }) => theme.font.size.sm}; + gap: ${({ theme }) => theme.spacing(2)}; + margin-bottom: ${({ theme }) => theme.spacing(2)}; + padding: ${({ theme }) => theme.spacing(2, 3)}; + width: fit-content; +`; + +const StyledIconContainer = styled.div<{ isLoading: boolean }>` + align-items: center; + animation: ${({ isLoading }) => + isLoading ? `${pulseAnimation} 2s ease-in-out infinite` : 'none'}; + color: ${({ theme }) => theme.color.blue}; + display: flex; +`; + +const StyledText = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + +export const RoutingStatusDisplay = ({ + data, +}: { + data: DataMessagePart['routing-status']; +}) => { + const isLoading = data.state === 'loading'; + + if (data.state === 'error') { + return null; + } + + return ( + + + {isLoading ? : } + + {isLoading ? ( + {data.text} + ) : ( + {data.text} + )} + + ); +}; diff --git a/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx b/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx index 12a593e778..a954e7d07f 100644 --- a/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx +++ b/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx @@ -4,13 +4,17 @@ import { useState } from 'react'; import { IconChevronDown, IconChevronUp } from 'twenty-ui/display'; import { AnimatedExpandableContainer } from 'twenty-ui/layout'; +import { JsonTree } from 'twenty-ui/json-visualizer'; import { ShimmeringText } from '@/ai/components/ShimmeringText'; import { type ToolInput } from '@/ai/types/ToolInput'; import { getToolIcon } from '@/ai/utils/getToolIcon'; import { getToolDisplayMessage } from '@/ai/utils/getWebSearchToolDisplayMessage'; +import { useLingui } from '@lingui/react/macro'; import { type ToolUIPart } from 'ai'; import { isDefined } from 'twenty-shared/utils'; +import { type JsonValue } from 'type-fest'; +import { useCopyToClipboard } from '~/hooks/useCopyToClipboard'; const StyledContainer = styled.div` display: flex; @@ -53,11 +57,6 @@ const StyledDisplayMessage = styled.span` font-weight: ${({ theme }) => theme.font.weight.medium}; `; -const StyledPre = styled.pre` - margin-top: ${({ theme }) => theme.spacing(1)}; - white-space: pre-wrap; -`; - const StyledIconTextContainer = styled.div` display: flex; align-items: center; @@ -68,6 +67,35 @@ const StyledIconTextContainer = styled.div` } `; +const StyledTabContainer = styled.div` + border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; + display: flex; + gap: ${({ theme }) => theme.spacing(3)}; + margin-bottom: ${({ theme }) => theme.spacing(3)}; +`; + +const StyledTab = styled.div<{ isActive: boolean }>` + color: ${({ theme, isActive }) => + isActive ? theme.font.color.primary : theme.font.color.tertiary}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme, isActive }) => + isActive ? theme.font.weight.medium : theme.font.weight.regular}; + cursor: pointer; + transition: color ${({ theme }) => theme.animation.duration.normal}s; + padding-bottom: ${({ theme }) => theme.spacing(2)}; + + &:hover { + color: ${({ theme }) => theme.font.color.secondary}; + } +`; + +const StyledJsonContainer = styled.div` + max-height: 400px; + overflow: auto; +`; + +type TabType = 'output' | 'input'; + export const ToolStepRenderer = ({ input, output, @@ -77,11 +105,16 @@ export const ToolStepRenderer = ({ output: ToolUIPart['output']; toolName: string; }) => { + const { t } = useLingui(); const theme = useTheme(); + const { copyToClipboard } = useCopyToClipboard(); const [isExpanded, setIsExpanded] = useState(false); + const [activeTab, setActiveTab] = useState('output'); const isExpandable = isDefined(output); + const isTwoFirstDepths = ({ depth }: { depth: number }) => depth < 2; + if (!output) { return ( @@ -132,7 +165,33 @@ export const ToolStepRenderer = ({ {isExpandable && ( - {JSON.stringify(result, null, 2)} + + setActiveTab('output')} + > + Output + + setActiveTab('input')} + > + Input + + + + + + )} 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 cff2d61880..2e820012d7 100644 --- a/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx +++ b/packages/twenty-front/src/modules/ai/components/internal/SendMessageButton.tsx @@ -12,13 +12,11 @@ import { Key } from 'ts-key-enum'; import { Button } from 'twenty-ui/input'; export const SendMessageButton = ({ - agentId, records, }: { - agentId: string; records?: ObjectRecord[]; }) => { - const { input, isLoading, handleInputChange } = useAgentChat(agentId); + const { input, isLoading, handleInputChange } = useAgentChat(); const { chat } = useAgentChatContextOrThrow(); const { buildRequestBody } = useAgentChatRequestBody(); const { sendMessage } = useChat({ chat }); diff --git a/packages/twenty-front/src/modules/ai/components/internal/SendMessageWithRecordsContextButton.tsx b/packages/twenty-front/src/modules/ai/components/internal/SendMessageWithRecordsContextButton.tsx index 06319f824d..26e7f77732 100644 --- a/packages/twenty-front/src/modules/ai/components/internal/SendMessageWithRecordsContextButton.tsx +++ b/packages/twenty-front/src/modules/ai/components/internal/SendMessageWithRecordsContextButton.tsx @@ -1,14 +1,10 @@ import { SendMessageButton } from '@/ai/components/internal/SendMessageButton'; import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore'; -export const SendMessageWithRecordsContextButton = ({ - agentId, -}: { - agentId: string; -}) => { +export const SendMessageWithRecordsContextButton = () => { const { records } = useFindManyRecordsSelectedInContextStore({ limit: 10, }); - return ; + return ; }; diff --git a/packages/twenty-front/src/modules/ai/contexts/AgentChatContext.ts b/packages/twenty-front/src/modules/ai/contexts/AgentChatContext.ts index d25a6ee6d9..d5151b78da 100644 --- a/packages/twenty-front/src/modules/ai/contexts/AgentChatContext.ts +++ b/packages/twenty-front/src/modules/ai/contexts/AgentChatContext.ts @@ -1,9 +1,9 @@ -import { type UIMessageWithMetadata } from '@/ai/types/UIMessageWithMetadata'; import { type Chat } from '@ai-sdk/react'; import { createContext } from 'react'; +import { type ExtendedUIMessage } from 'twenty-shared/ai'; export type AgentChatContextValue = { - chat: Chat; + chat: Chat; isLoadingData: boolean; }; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/createAgentChatThread.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/createAgentChatThread.ts deleted file mode 100644 index 5f9775c54d..0000000000 --- a/packages/twenty-front/src/modules/ai/graphql/mutations/createAgentChatThread.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { gql } from '@apollo/client'; - -export const CREATE_AGENT_CHAT_THREAD = gql` - mutation CreateAgentChatThread($input: CreateAgentChatThreadInput!) { - createAgentChatThread(input: $input) { - id - agentId - title - createdAt - updatedAt - } - } -`; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/createChatThread.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/createChatThread.ts new file mode 100644 index 0000000000..4f8b34baca --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/mutations/createChatThread.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +export const CREATE_CHAT_THREAD = gql` + mutation CreateChatThread { + createChatThread { + id + title + createdAt + updatedAt + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/queries/getAgentChatThreads.ts b/packages/twenty-front/src/modules/ai/graphql/queries/getAgentChatThreads.ts deleted file mode 100644 index d398e8f5f0..0000000000 --- a/packages/twenty-front/src/modules/ai/graphql/queries/getAgentChatThreads.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { gql } from '@apollo/client'; - -export const GET_AGENT_CHAT_THREADS = gql` - query GetAgentChatThreads($agentId: UUID!) { - agentChatThreads(agentId: $agentId) { - id - agentId - title - createdAt - updatedAt - } - } -`; diff --git a/packages/twenty-front/src/modules/ai/graphql/queries/getAgentChatMessages.ts b/packages/twenty-front/src/modules/ai/graphql/queries/getChatMessages.ts similarity index 82% rename from packages/twenty-front/src/modules/ai/graphql/queries/getAgentChatMessages.ts rename to packages/twenty-front/src/modules/ai/graphql/queries/getChatMessages.ts index 54152f0893..11bc878af4 100644 --- a/packages/twenty-front/src/modules/ai/graphql/queries/getAgentChatMessages.ts +++ b/packages/twenty-front/src/modules/ai/graphql/queries/getChatMessages.ts @@ -1,8 +1,8 @@ import { gql } from '@apollo/client'; -export const GET_AGENT_CHAT_MESSAGES = gql` - query GetAgentChatMessages($threadId: UUID!) { - agentChatMessages(threadId: $threadId) { +export const GET_CHAT_MESSAGES = gql` + query GetChatMessages($threadId: UUID!) { + chatMessages(threadId: $threadId) { id threadId role diff --git a/packages/twenty-front/src/modules/ai/graphql/queries/getChatThreads.ts b/packages/twenty-front/src/modules/ai/graphql/queries/getChatThreads.ts new file mode 100644 index 0000000000..6e25990790 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/queries/getChatThreads.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +export const GET_CHAT_THREADS = gql` + query GetChatThreads { + chatThreads { + id + title + createdAt + updatedAt + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts index 6774063262..93d7410de2 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChat.ts @@ -6,7 +6,7 @@ import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState'; import { useChat } from '@ai-sdk/react'; import { agentChatInputState } from '../states/agentChatInputState'; -export const useAgentChat = (agentId: string) => { +export const useAgentChat = () => { const { chat, isLoadingData } = useAgentChatContextOrThrow(); const agentChatSelectedFiles = useRecoilValue(agentChatSelectedFilesState); @@ -16,7 +16,7 @@ export const useAgentChat = (agentId: string) => { const [agentChatInput, setAgentChatInput] = useRecoilState(agentChatInputState); - const scrollWrapperId = `scroll-wrapper-ai-chat-${agentId}`; + const scrollWrapperId = `scroll-wrapper-ai-chat-${currentAIChatThread}`; const { messages, status, error } = useChat({ chat, diff --git a/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts b/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts index d5a30e9d6c..3a1f646846 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useAgentChatData.ts @@ -3,31 +3,30 @@ import { mapDBMessagesToUIMessages } from '@/ai/utils/mapDBMessagesToUIMessages' import { useRecoilState } from 'recoil'; import { isDefined } from 'twenty-shared/utils'; import { - useGetAgentChatMessagesQuery, - useGetAgentChatThreadsQuery, + useGetChatMessagesQuery, + useGetChatThreadsQuery, } from '~/generated-metadata/graphql'; -export const useAgentChatData = (agentId: string) => { +export const useAgentChatData = () => { const [currentAIChatThread, setCurrentAIChatThread] = useRecoilState( currentAIChatThreadState, ); - const { loading: threadsLoading } = useGetAgentChatThreadsQuery({ - variables: { agentId }, - skip: isDefined(currentAIChatThread) || !isDefined(agentId), + const { loading: threadsLoading } = useGetChatThreadsQuery({ + skip: isDefined(currentAIChatThread), onCompleted: (data) => { - if (data.agentChatThreads.length > 0) { - setCurrentAIChatThread(data.agentChatThreads[0].id); + if (data.chatThreads.length > 0) { + setCurrentAIChatThread(data.chatThreads[0].id); } }, }); - const { loading: messagesLoading, data } = useGetAgentChatMessagesQuery({ + const { loading: messagesLoading, data } = useGetChatMessagesQuery({ variables: { threadId: currentAIChatThread! }, skip: !isDefined(currentAIChatThread), }); - const uiMessages = mapDBMessagesToUIMessages(data?.agentChatMessages || []); + const uiMessages = mapDBMessagesToUIMessages(data?.chatMessages || []); const isLoading = messagesLoading || threadsLoading; return { diff --git a/packages/twenty-front/src/modules/ai/hooks/useCreateNewAIChatThread.ts b/packages/twenty-front/src/modules/ai/hooks/useCreateNewAIChatThread.ts index 949c49b895..4c158fc6f9 100644 --- a/packages/twenty-front/src/modules/ai/hooks/useCreateNewAIChatThread.ts +++ b/packages/twenty-front/src/modules/ai/hooks/useCreateNewAIChatThread.ts @@ -1,19 +1,18 @@ import { currentAIChatThreadState } from '@/ai/states/currentAIChatThreadState'; import { useOpenAskAIPageInCommandMenu } from '@/command-menu/hooks/useOpenAskAIPageInCommandMenu'; import { useRecoilState } from 'recoil'; -import { useCreateAgentChatThreadMutation } from '~/generated-metadata/graphql'; +import { useCreateChatThreadMutation } from '~/generated-metadata/graphql'; -export const useCreateNewAIChatThread = ({ agentId }: { agentId: string }) => { +export const useCreateNewAIChatThread = () => { const [, setCurrentAIChatThread] = useRecoilState(currentAIChatThreadState); const { openAskAIPage } = useOpenAskAIPageInCommandMenu(); - const [createAgentChatThread] = useCreateAgentChatThreadMutation({ - variables: { input: { agentId } }, + const [createChatThread] = useCreateChatThreadMutation({ onCompleted: (data) => { - setCurrentAIChatThread(data.createAgentChatThread.id); + setCurrentAIChatThread(data.createChatThread.id); openAskAIPage(); }, }); - return { createAgentChatThread }; + return { createChatThread }; }; diff --git a/packages/twenty-front/src/modules/ai/types/UIMessageWithMetadata.ts b/packages/twenty-front/src/modules/ai/types/UIMessageWithMetadata.ts deleted file mode 100644 index 8634d690ea..0000000000 --- a/packages/twenty-front/src/modules/ai/types/UIMessageWithMetadata.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { type UIMessage } from 'ai'; - -export type UIMessageWithMetadata = UIMessage & { - metadata: { - createdAt: string; - }; -}; diff --git a/packages/twenty-front/src/modules/ai/utils/__tests__/groupThreadsByDate.test.ts b/packages/twenty-front/src/modules/ai/utils/__tests__/groupThreadsByDate.test.ts index bfaff6ee3b..64642250ba 100644 --- a/packages/twenty-front/src/modules/ai/utils/__tests__/groupThreadsByDate.test.ts +++ b/packages/twenty-front/src/modules/ai/utils/__tests__/groupThreadsByDate.test.ts @@ -3,7 +3,6 @@ import { groupThreadsByDate } from '../groupThreadsByDate'; describe('groupThreadsByDate', () => { const baseThread: Omit = { - agentId: 'agent-1', title: 'Test Thread', updatedAt: new Date().toISOString(), }; diff --git a/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts b/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts index 178fef4315..368bdf5321 100644 --- a/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts +++ b/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts @@ -1,13 +1,13 @@ -import { type UIMessageWithMetadata } from '@/ai/types/UIMessageWithMetadata'; import { mapDBPartToUIMessagePart } from '@/ai/utils/mapDBPartToUIMessagePart'; +import { type ExtendedUIMessage } from 'twenty-shared/ai'; import { type AgentChatMessage } from '~/generated/graphql'; export const mapDBMessagesToUIMessages = ( dbMessages: AgentChatMessage[], -): UIMessageWithMetadata[] => { +): ExtendedUIMessage[] => { return dbMessages.map((dbMessage) => ({ id: dbMessage.id, - role: dbMessage.role as UIMessageWithMetadata['role'], + role: dbMessage.role as ExtendedUIMessage['role'], parts: dbMessage.parts.map(mapDBPartToUIMessagePart), metadata: { createdAt: dbMessage.createdAt, diff --git a/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts b/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts index b22a1f3980..72e702c5eb 100644 --- a/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts +++ b/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts @@ -1,14 +1,10 @@ -import { - type ReasoningUIPart, - type ToolUIPart, - type UIMessagePart, - type UITool, -} from 'ai'; +import { type ReasoningUIPart, type ToolUIPart } from 'ai'; +import { type ExtendedUIMessagePart } from 'twenty-shared/ai'; import { type AgentChatMessagePart } from '~/generated/graphql'; export const mapDBPartToUIMessagePart = ( part: AgentChatMessagePart, -): UIMessagePart> => { +): ExtendedUIMessagePart => { switch (part.type) { case 'text': return { @@ -49,6 +45,14 @@ export const mapDBPartToUIMessagePart = ( return { type: 'step-start', }; + case 'data-routing-status': + return { + type: part.type, + data: { + text: part.textContent!, + state: part.state!, + }, + }; default: { if (part.type.includes('tool-') === true) { diff --git a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts index 4d0bd6dda5..7623e36bd0 100644 --- a/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts +++ b/packages/twenty-front/src/modules/apollo/services/__tests__/apollo.factory.test.ts @@ -58,6 +58,7 @@ const mockWorkspace = { }, isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, + routerModel: 'auto', }; const createMockOptions = (): Options => ({ diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index c6ed80bae2..986e25aa6a 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -243,14 +243,6 @@ const SettingsIntegrationDatabase = lazy(() => ), ); -const SettingsIntegrationMCP = lazy(() => - import('~/pages/settings/integrations/SettingsIntegrationMCPPage').then( - (module) => ({ - default: module.SettingsIntegrationMCPPage, - }), - ), -); - const SettingsIntegrationNewDatabaseConnection = lazy(() => import( '~/pages/settings/integrations/SettingsIntegrationNewDatabaseConnection' @@ -592,10 +584,6 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( path={SettingsPath.IntegrationDatabaseConnection} element={} /> - } - /> & { defaultRole?: Omit | null; - defaultAgent?: { id: string } | null; }; export const currentWorkspaceState = createState({ diff --git a/packages/twenty-front/src/modules/command-menu/pages/AIChatThreads/components/CommandMenuAIChatThreadsPage.tsx b/packages/twenty-front/src/modules/command-menu/pages/AIChatThreads/components/CommandMenuAIChatThreadsPage.tsx index c1598d9905..7ebdcefa00 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/AIChatThreads/components/CommandMenuAIChatThreadsPage.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/AIChatThreads/components/CommandMenuAIChatThreadsPage.tsx @@ -1,37 +1,15 @@ import { AIChatThreadsList } from '@/ai/components/AIChatThreadsList'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import styled from '@emotion/styled'; -import { useRecoilValue } from 'recoil'; const StyledContainer = styled.div` height: 100%; width: 100%; `; -const StyledEmptyState = styled.div` - align-items: center; - color: ${({ theme }) => theme.font.color.secondary}; - display: flex; - font-size: ${({ theme }) => theme.font.size.md}; - height: 100%; - justify-content: center; -`; - export const CommandMenuAIChatThreadsPage = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const agentId = currentWorkspace?.defaultAgent?.id; - - if (!agentId) { - return ( - - No AI Agent found. - - ); - } - return ( - + ); }; diff --git a/packages/twenty-front/src/modules/command-menu/pages/ask-ai/components/CommandMenuAskAIPage.tsx b/packages/twenty-front/src/modules/command-menu/pages/ask-ai/components/CommandMenuAskAIPage.tsx index 8a1a315c60..006bf4ab77 100644 --- a/packages/twenty-front/src/modules/command-menu/pages/ask-ai/components/CommandMenuAskAIPage.tsx +++ b/packages/twenty-front/src/modules/command-menu/pages/ask-ai/components/CommandMenuAskAIPage.tsx @@ -1,38 +1,15 @@ import { AIChatTab } from '@/ai/components/AIChatTab'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import styled from '@emotion/styled'; -import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; const StyledContainer = styled.div` height: 100%; width: 100%; `; -const StyledEmptyState = styled.div` - align-items: center; - color: ${({ theme }) => theme.font.color.secondary}; - display: flex; - font-size: ${({ theme }) => theme.font.size.md}; - height: 100%; - justify-content: center; -`; - export const CommandMenuAskAIPage = () => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); - const agentId = currentWorkspace?.defaultAgent?.id; - - if (!agentId) { - return ( - - {t`No AI Agent found.`} - - ); - } - return ( - + ); }; diff --git a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts index d57a9164b3..41bb41cd81 100644 --- a/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts +++ b/packages/twenty-front/src/modules/object-metadata/hooks/__tests__/useColumnDefinitionsFromFieldMetadata.test.ts @@ -51,6 +51,7 @@ const Wrapper = getJestMetadataAndApolloMocksAndActionMenuWrapper({ ], isTwoFactorAuthenticationEnforced: false, trashRetentionDays: 14, + routerModel: 'auto', }); }, }); diff --git a/packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts b/packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts deleted file mode 100644 index 44b8e52ea4..0000000000 --- a/packages/twenty-front/src/modules/settings/integrations/constants/SettingsIntegrationMcp.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { type SettingsIntegrationCategory } from '@/settings/integrations/types/SettingsIntegrationCategory'; -import { SettingsPath } from 'twenty-shared/types'; -import { getSettingsPath } from 'twenty-shared/utils'; - -export const SETTINGS_INTEGRATION_AI_CATEGORY: SettingsIntegrationCategory = { - key: 'ai', - title: 'With AI', - hyperlink: null, - integrations: [ - { - from: { - key: 'mcp', - image: '/images/integrations/mcp.svg', - }, - type: 'Add', - text: 'Connect MCP Client', - link: getSettingsPath(SettingsPath.IntegrationMCP), - }, - ], -}; diff --git a/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts b/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts index e9ef6f2362..9fab3ca021 100644 --- a/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts +++ b/packages/twenty-front/src/modules/settings/integrations/hooks/useSettingsIntegrationCategories.ts @@ -1,5 +1,4 @@ import { MOCK_REMOTE_DATABASES } from '@/settings/integrations/constants/MockRemoteDatabases'; -import { SETTINGS_INTEGRATION_AI_CATEGORY } from '@/settings/integrations/constants/SettingsIntegrationMcp'; import { SETTINGS_INTEGRATION_REQUEST_CATEGORY } from '@/settings/integrations/constants/SettingsIntegrationRequest'; import { SETTINGS_INTEGRATION_ZAPIER_CATEGORY } from '@/settings/integrations/constants/SettingsIntegrationZapier'; import { type SettingsIntegrationCategory } from '@/settings/integrations/types/SettingsIntegrationCategory'; @@ -30,10 +29,6 @@ export const useSettingsIntegrationCategories = ({ name }) => name === 'stripe', )?.isActive; - const isAiIntegrationEnabled = useIsFeatureEnabled( - FeatureFlagKey.IS_AI_ENABLED, - ); - const allIntegrations = getSettingsIntegrationAll({ isAirtableIntegrationEnabled, isAirtableIntegrationActive, @@ -46,7 +41,6 @@ export const useSettingsIntegrationCategories = return [ ...(allIntegrations.integrations.length > 0 ? [allIntegrations] : []), SETTINGS_INTEGRATION_ZAPIER_CATEGORY, - ...(isAiIntegrationEnabled ? [SETTINGS_INTEGRATION_AI_CATEGORY] : []), SETTINGS_INTEGRATION_REQUEST_CATEGORY, ]; }; diff --git a/packages/twenty-front/src/modules/users/components/UserAndViewsProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/UserAndViewsProviderEffect.tsx index 803486d42f..e338c0e4bc 100644 --- a/packages/twenty-front/src/modules/users/components/UserAndViewsProviderEffect.tsx +++ b/packages/twenty-front/src/modules/users/components/UserAndViewsProviderEffect.tsx @@ -118,8 +118,6 @@ export const UserAndViewsProviderEffect = () => { ...userQueryData.currentUser.currentWorkspace, defaultRole: userQueryData.currentUser.currentWorkspace.defaultRole ?? null, - defaultAgent: - userQueryData.currentUser.currentWorkspace.defaultAgent ?? null, }); } diff --git a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts index df6099ce62..208c9971b9 100644 --- a/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts +++ b/packages/twenty-front/src/modules/users/graphql/fragments/userQueryFragment.ts @@ -75,9 +75,7 @@ export const USER_QUERY_FRAGMENT = gql` defaultRole { ...RoleFragment } - defaultAgent { - id - } + routerModel isTwoFactorAuthenticationEnforced trashRetentionDays } diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx index 3ab3eff966..8dfd1c283d 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent.tsx @@ -1,5 +1,4 @@ import { useAiAgentOutputSchema } from '@/ai/hooks/useAiAgentOutputSchema'; -import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState'; import { SidePanelHeader } from '@/command-menu/components/SidePanelHeader'; import { FormTextFieldInput } from '@/object-record/record-field/ui/form-types/components/FormTextFieldInput'; import { Select } from '@/ui/input/components/Select'; @@ -12,7 +11,6 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components import { type AiAgentOutputSchema } from '@/workflow/workflow-variables/types/AiAgentOutputSchema'; import styled from '@emotion/styled'; import { t } from '@lingui/core/macro'; -import { useRecoilValue } from 'recoil'; import { useIcons } from 'twenty-ui/display'; import { type SelectOption } from 'twenty-ui/input'; import { useFindManyAgentsQuery } from '~/generated-metadata/graphql'; @@ -40,7 +38,6 @@ export const WorkflowEditActionAiAgent = ({ action, actionOptions, }: WorkflowEditActionAiAgentProps) => { - const currentWorkspace = useRecoilValue(currentWorkspaceState); const { getIcon } = useIcons(); const { headerTitle, headerIcon, headerIconColor, headerType } = useWorkflowActionHeader({ @@ -61,13 +58,11 @@ export const WorkflowEditActionAiAgent = ({ SelectOption[] >( (acc, agent) => { - if (agent.id !== currentWorkspace?.defaultAgent?.id) { - acc.push({ - label: agent.label, - value: agent.id, - Icon: agent.icon ? getIcon(agent.icon) : undefined, - }); - } + acc.push({ + label: agent.label, + value: agent.id, + Icon: agent.icon ? getIcon(agent.icon) : undefined, + }); return acc; }, [ diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx index 808ed01cb2..41ff414c37 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx @@ -1,31 +1,81 @@ import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { TabList } from '@/ui/layout/tab-list/components/TabList'; +import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState'; +import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath } from 'twenty-shared/utils'; -import { H2Title, IconPlus } from 'twenty-ui/display'; +import { + IconPlus, + IconRobot, + IconServer, + IconSettings, +} from 'twenty-ui/display'; import { Button } from 'twenty-ui/input'; -import { Section } from 'twenty-ui/layout'; import { UndecoratedLink } from 'twenty-ui/navigation'; -import { useFindManyAgentsQuery } from '~/generated-metadata/graphql'; import { t } from '@lingui/core/macro'; import { SettingsAIAgentsTable } from './components/SettingsAIAgentsTable'; +import { SettingsAIMCP } from './components/SettingsAIMCP'; +import { SettingsAIRouterSettings } from './components/SettingsAIRouterSettings'; + +const SETTINGS_AI_TABS_ID = 'settings-ai-tabs-id'; + +const SETTINGS_AI_TABS = { + AGENTS: 'agents', + SETTINGS: 'settings', + MCP: 'mcp', +}; export const SettingsAI = () => { - const { data } = useFindManyAgentsQuery(); + const activeTabId = useRecoilComponentValue( + activeTabIdComponentState, + SETTINGS_AI_TABS_ID, + ); + + const tabs = [ + { + id: SETTINGS_AI_TABS.AGENTS, + title: t`Agents`, + Icon: IconRobot, + }, + { + id: SETTINGS_AI_TABS.MCP, + title: t`MCP`, + Icon: IconServer, + }, + { + id: SETTINGS_AI_TABS.SETTINGS, + title: t`Settings`, + Icon: IconSettings, + }, + ]; + + const renderActiveTabContent = () => { + switch (activeTabId) { + case SETTINGS_AI_TABS.AGENTS: + return ; + case SETTINGS_AI_TABS.SETTINGS: + return ; + case SETTINGS_AI_TABS.MCP: + return ; + } + }; return ( -