diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index e8d9eace4a..3ef6d6154c 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -57,6 +57,7 @@ export type Agent = { id: Scalars['UUID']; isCustom: Scalars['Boolean']; label: Scalars['String']; + modelConfiguration?: Maybe; modelId: Scalars['String']; name: Scalars['String']; prompt: Scalars['String']; @@ -485,6 +486,7 @@ export type ClientAiModelConfig = { inputCostPer1kTokensInCredits: Scalars['Float']; label: Scalars['String']; modelId: Scalars['String']; + nativeCapabilities?: Maybe; outputCostPer1kTokensInCredits: Scalars['Float']; provider: ModelProvider; }; @@ -697,6 +699,7 @@ export type CreateAgentInput = { description?: InputMaybe; icon?: InputMaybe; label: Scalars['String']; + modelConfiguration?: InputMaybe; modelId: Scalars['String']; name?: InputMaybe; prompt: Scalars['String']; @@ -2629,6 +2632,12 @@ export type MutationVerifyTwoFactorAuthenticationMethodForAuthenticatedUserArgs otp: Scalars['String']; }; +export type NativeModelCapabilities = { + __typename?: 'NativeModelCapabilities'; + twitterSearch?: Maybe; + webSearch?: Maybe; +}; + export type NumberChartConfiguration = { __typename?: 'NumberChartConfiguration'; aggregateFieldMetadataId: Scalars['UUID']; @@ -3802,6 +3811,7 @@ export type UpdateAgentInput = { icon?: InputMaybe; id: Scalars['UUID']; label: Scalars['String']; + modelConfiguration?: InputMaybe; modelId: Scalars['String']; name: Scalars['String']; prompt: Scalars['String']; @@ -4459,7 +4469,7 @@ export type WorkspaceUrlsAndId = { workspaceUrls: WorkspaceUrls; }; -export type AgentFieldsFragment = { __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, createdAt: string, updatedAt: string }; +export type AgentFieldsFragment = { __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, createdAt: string, updatedAt: string }; export type AssignRoleToAgentMutationVariables = Exact<{ agentId: Scalars['UUID']; @@ -4488,14 +4498,14 @@ export type CreateOneAgentMutationVariables = Exact<{ }>; -export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __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, createdAt: string, updatedAt: string } }; +export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __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, createdAt: string, updatedAt: string } }; export type DeleteOneAgentMutationVariables = Exact<{ input: AgentIdInput; }>; -export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __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, createdAt: string, updatedAt: string } }; +export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __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, createdAt: string, updatedAt: string } }; export type RemoveAgentHandoffMutationVariables = Exact<{ input: RemoveAgentHandoffInput; @@ -4516,7 +4526,7 @@ export type UpdateOneAgentMutationVariables = Exact<{ }>; -export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __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, createdAt: string, updatedAt: string } }; +export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __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, createdAt: string, updatedAt: string } }; export type FindAgentHandoffTargetsQueryVariables = Exact<{ input: AgentIdInput; @@ -4535,14 +4545,14 @@ export type FindAgentHandoffsQuery = { __typename?: 'Query', findAgentHandoffs: export type FindManyAgentsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: Array<{ __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, createdAt: string, updatedAt: string }> }; +export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: Array<{ __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, createdAt: string, updatedAt: string }> }; export type FindOneAgentQueryVariables = Exact<{ id: Scalars['UUID']; }>; -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, createdAt: string, updatedAt: string } }; +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, createdAt: string, updatedAt: string } }; export type GetAgentChatMessagesQueryVariables = Exact<{ threadId: Scalars['UUID']; @@ -5296,7 +5306,7 @@ export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPer export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __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, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __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, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null }> }; +export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __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, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __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, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null }> }; export type CreateApprovedAccessDomainMutationVariables = Exact<{ input: CreateApprovedAccessDomainInput; @@ -5903,6 +5913,7 @@ export const AgentFieldsFragmentDoc = gql` responseFormat roleId isCustom + modelConfiguration createdAt updatedAt } diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index fd430ec221..8cd8558cf9 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -57,6 +57,7 @@ export type Agent = { id: Scalars['UUID']; isCustom: Scalars['Boolean']; label: Scalars['String']; + modelConfiguration?: Maybe; modelId: Scalars['String']; name: Scalars['String']; prompt: Scalars['String']; @@ -485,6 +486,7 @@ export type ClientAiModelConfig = { inputCostPer1kTokensInCredits: Scalars['Float']; label: Scalars['String']; modelId: Scalars['String']; + nativeCapabilities?: Maybe; outputCostPer1kTokensInCredits: Scalars['Float']; provider: ModelProvider; }; @@ -697,6 +699,7 @@ export type CreateAgentInput = { description?: InputMaybe; icon?: InputMaybe; label: Scalars['String']; + modelConfiguration?: InputMaybe; modelId: Scalars['String']; name?: InputMaybe; prompt: Scalars['String']; @@ -2486,6 +2489,12 @@ export type MutationVerifyTwoFactorAuthenticationMethodForAuthenticatedUserArgs otp: Scalars['String']; }; +export type NativeModelCapabilities = { + __typename?: 'NativeModelCapabilities'; + twitterSearch?: Maybe; + webSearch?: Maybe; +}; + export type NumberChartConfiguration = { __typename?: 'NumberChartConfiguration'; aggregateFieldMetadataId: Scalars['UUID']; @@ -3580,6 +3589,7 @@ export type UpdateAgentInput = { icon?: InputMaybe; id: Scalars['UUID']; label: Scalars['String']; + modelConfiguration?: InputMaybe; modelId: Scalars['String']; name: Scalars['String']; prompt: Scalars['String']; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx b/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx index 711a8d6bd8..577030eaea 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatAssistantMessageRenderer.tsx @@ -3,11 +3,14 @@ import { IconDotsVertical } from 'twenty-ui/display'; import { LazyMarkdownRenderer } from '@/ai/components/LazyMarkdownRenderer'; import { ToolStepRenderer } from '@/ai/components/ToolStepRenderer'; -import { type ToolInput } from '@/ai/types/ToolInput'; -import { type ToolOutput } from '@/ai/types/ToolOutput'; import { keyframes, useTheme } from '@emotion/react'; import styled from '@emotion/styled'; -import type { ToolUIPart, UIDataTypes, UIMessagePart, UITools } from 'ai'; +import { + isToolUIPart, + type UIDataTypes, + type UIMessagePart, + type UITools, +} from 'ai'; const StyledStepsContainer = styled.div` display: flex; @@ -64,29 +67,29 @@ export const AIChatAssistantMessageRenderer = ({ isLastMessageStreaming: boolean; }) => { const renderStep = ( - step: UIMessagePart, + part: UIMessagePart, index: number, ) => { - switch (step.type) { + switch (part.type) { case 'reasoning': return ( ); case 'text': - return ; + return ; default: { - if (step.type.includes('tool-')) { - const { output, input, type } = step as ToolUIPart; + if (isToolUIPart(part)) { + const { output, input, type } = part; return ( ); diff --git a/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx b/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx index d91ca8cede..12a593e778 100644 --- a/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx +++ b/packages/twenty-front/src/modules/ai/components/ToolStepRenderer.tsx @@ -7,8 +7,9 @@ import { AnimatedExpandableContainer } from 'twenty-ui/layout'; import { ShimmeringText } from '@/ai/components/ShimmeringText'; import { type ToolInput } from '@/ai/types/ToolInput'; -import { type ToolOutput } from '@/ai/types/ToolOutput'; import { getToolIcon } from '@/ai/utils/getToolIcon'; +import { getToolDisplayMessage } from '@/ai/utils/getWebSearchToolDisplayMessage'; +import { type ToolUIPart } from 'ai'; import { isDefined } from 'twenty-shared/utils'; const StyledContainer = styled.div` @@ -73,7 +74,7 @@ export const ToolStepRenderer = ({ toolName, }: { input: ToolInput; - output: ToolOutput; + output: ToolUIPart['output']; toolName: string; }) => { const theme = useTheme(); @@ -86,7 +87,9 @@ export const ToolStepRenderer = ({ - {input?.loadingMessage} + + {getToolDisplayMessage(input, toolName, false)} + @@ -94,9 +97,12 @@ export const ToolStepRenderer = ({ } const displayMessage = - output && typeof output === 'object' && 'message' in output - ? (output as { message: string }).message - : undefined; + output && + typeof output === 'object' && + 'message' in output && + typeof output.message === 'string' + ? output.message + : getToolDisplayMessage(input, toolName, true); const result = output && typeof output === 'object' && 'result' in output diff --git a/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts b/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts index 2f0afe4778..e1935b9108 100644 --- a/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts +++ b/packages/twenty-front/src/modules/ai/graphql/fragments/agentFragment.ts @@ -12,6 +12,7 @@ export const AGENT_FRAGMENT = gql` responseFormat roleId isCustom + modelConfiguration createdAt updatedAt } diff --git a/packages/twenty-front/src/modules/ai/types/ToolInput.ts b/packages/twenty-front/src/modules/ai/types/ToolInput.ts index b74f54c84a..4f6bdd23b9 100644 --- a/packages/twenty-front/src/modules/ai/types/ToolInput.ts +++ b/packages/twenty-front/src/modules/ai/types/ToolInput.ts @@ -1,4 +1,3 @@ -export type ToolInput = { - loadingMessage: string; - input: Record; -}; +import { type ToolUIPart } from 'ai'; + +export type ToolInput = ToolUIPart['input']; diff --git a/packages/twenty-front/src/modules/ai/types/ToolOutput.ts b/packages/twenty-front/src/modules/ai/types/ToolOutput.ts deleted file mode 100644 index 3aad7377d6..0000000000 --- a/packages/twenty-front/src/modules/ai/types/ToolOutput.ts +++ /dev/null @@ -1 +0,0 @@ -export type ToolOutput = Record; diff --git a/packages/twenty-front/src/modules/ai/utils/getWebSearchToolDisplayMessage.ts b/packages/twenty-front/src/modules/ai/utils/getWebSearchToolDisplayMessage.ts new file mode 100644 index 0000000000..be53a90dc8 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/utils/getWebSearchToolDisplayMessage.ts @@ -0,0 +1,56 @@ +import { type ToolInput } from '@/ai/types/ToolInput'; +import { isDefined } from 'twenty-shared/utils'; + +const extractSearchQuery = (input: ToolInput): string => { + if (!input) { + return ''; + } + + if ( + typeof input === 'object' && + 'query' in input && + typeof input.query === 'string' + ) { + return input.query; + } + + if ( + typeof input === 'object' && + 'action' in input && + isDefined(input.action) && + typeof input.action === 'object' && + 'query' in input.action && + typeof input.action.query === 'string' + ) { + return input.action.query; + } + + return ''; +}; + +const extractLoadingMessage = (input: ToolInput): string => { + if ( + isDefined(input) && + typeof input === 'object' && + 'loadingMessage' in input && + typeof input.loadingMessage === 'string' + ) { + return input.loadingMessage; + } + + return 'Processing...'; +}; + +export const getToolDisplayMessage = ( + input: ToolInput, + toolName: string, + isFinished?: boolean, +): string => { + if (toolName === 'web_search') { + const query = extractSearchQuery(input); + const action = isFinished ? 'Searched' : 'Searching'; + return query ? `${action} the web for '${query}'` : `${action} the web`; + } + + return extractLoadingMessage(input); +}; diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx index 5097a92485..c5965ec866 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAgentForm.tsx @@ -71,9 +71,10 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { description: agent.description, icon: agent.icon || 'IconRobot', modelId: agent.modelId, - role: agent.roleId ?? undefined, + role: agent.roleId, prompt: agent.prompt, isCustom: agent.isCustom, + modelConfiguration: agent.modelConfiguration || {}, }); } else { enqueueErrorSnackBar({ @@ -118,8 +119,9 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { description: formValues.description, icon: formValues.icon, modelId: formValues.modelId, - roleId: formValues.role || undefined, + roleId: formValues.role, prompt: formValues.prompt, + modelConfiguration: formValues.modelConfiguration, }; await createAgent({ @@ -142,8 +144,9 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { description: formValues.description, icon: formValues.icon, modelId: formValues.modelId, - roleId: formValues.role || undefined, + roleId: formValues.role, prompt: formValues.prompt, + modelConfiguration: formValues.modelConfiguration, }, }, }); diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentModelCapabilities.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentModelCapabilities.tsx new file mode 100644 index 0000000000..f8faa4ce66 --- /dev/null +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentModelCapabilities.tsx @@ -0,0 +1,142 @@ +import { useTheme } from '@emotion/react'; +import styled from '@emotion/styled'; +import { useRecoilValue } from 'recoil'; + +import { aiModelsState } from '@/client-config/states/aiModelsState'; +import { InputLabel } from '@/ui/input/components/InputLabel'; +import { t } from '@lingui/core/macro'; +import { isDefined } from 'twenty-shared/utils'; +import { IconBrandX, IconWorld } from 'twenty-ui/display'; +import { Checkbox } from 'twenty-ui/input'; +import { Section } from 'twenty-ui/layout'; + +const StyledCheckboxContainer = styled.div<{ disabled: boolean }>` + display: flex; + align-items: center; + justify-content: space-between; + padding: ${({ theme }) => theme.spacing(1)}; + border-radius: ${({ theme }) => theme.border.radius.sm}; + transition: background-color + ${({ theme }) => theme.animation.duration.normal}s ease; + cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')}; + + &:hover { + background-color: ${({ theme, disabled }) => + disabled ? 'transparent' : theme.background.transparent.light}; + } +`; + +const StyledCheckboxLabel = styled.div` + display: flex; + align-items: center; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +type ModelConfiguration = { + webSearch?: { + enabled: boolean; + configuration?: Record; + }; + twitterSearch?: { + enabled: boolean; + configuration?: Record; + }; +}; + +type SettingsAgentModelCapabilitiesProps = { + selectedModelId: string; + modelConfiguration: ModelConfiguration; + onConfigurationChange: (configuration: ModelConfiguration) => void; + disabled?: boolean; +}; + +export const SettingsAgentModelCapabilities = ({ + selectedModelId, + modelConfiguration, + onConfigurationChange, + disabled = false, +}: SettingsAgentModelCapabilitiesProps) => { + const theme = useTheme(); + const aiModels = useRecoilValue(aiModelsState); + + const selectedModel = aiModels.find((m) => m.modelId === selectedModelId); + const nativeCapabilities = selectedModel?.nativeCapabilities; + + if (!isDefined(nativeCapabilities)) { + return null; + } + + if (!nativeCapabilities.webSearch && !nativeCapabilities.twitterSearch) { + return null; + } + + const handleCapabilityToggle = ( + capability: 'webSearch' | 'twitterSearch', + enabled: boolean, + ) => { + if (disabled) { + return; + } + + onConfigurationChange({ + ...modelConfiguration, + [capability]: { + enabled, + configuration: modelConfiguration[capability]?.configuration || {}, + }, + }); + }; + + const capabilities = [ + ...(nativeCapabilities.webSearch + ? [ + { + key: 'webSearch' as const, + label: t`Web Search`, + Icon: IconWorld, + enabled: modelConfiguration.webSearch?.enabled || false, + }, + ] + : []), + ...(nativeCapabilities.twitterSearch + ? [ + { + key: 'twitterSearch' as const, + label: t`Twitter/X Search`, + Icon: IconBrandX, + enabled: modelConfiguration.twitterSearch?.enabled || false, + }, + ] + : []), + ]; + + return ( +
+ {t`Enable model-specific features`} +
+ {capabilities.map((capability) => ( + + handleCapabilityToggle(capability.key, !capability.enabled) + } + > + + + {capability.label} + + { + event.stopPropagation(); + handleCapabilityToggle(capability.key, event.target.checked); + }} + disabled={disabled} + /> + + ))} +
+
+ ); +}; diff --git a/packages/twenty-front/src/pages/settings/ai/forms/components/SettingsAIAgentForm.tsx b/packages/twenty-front/src/pages/settings/ai/forms/components/SettingsAIAgentForm.tsx index 3a1e2bd1ad..f326db7382 100644 --- a/packages/twenty-front/src/pages/settings/ai/forms/components/SettingsAIAgentForm.tsx +++ b/packages/twenty-front/src/pages/settings/ai/forms/components/SettingsAIAgentForm.tsx @@ -9,6 +9,7 @@ import { TextArea } from '@/ui/input/components/TextArea'; import { isDefined } from 'twenty-shared/utils'; import { useGetRolesQuery } from '~/generated-metadata/graphql'; import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/computeMetadataNameFromLabel'; +import { SettingsAgentModelCapabilities } from '../../components/SettingsAgentModelCapabilities'; import { type SettingsAIAgentFormValues } from '../../hooks/useSettingsAgentFormState'; const StyledFormContainer = styled.div` @@ -52,13 +53,18 @@ export const SettingsAIAgentForm = ({ const modelOptions = useAiModelOptions(); const { data: rolesData } = useGetRolesQuery(); - const rolesOptions = - rolesData?.getRoles + const rolesOptions = [ + { + label: t`None`, + value: null, + }, + ...(rolesData?.getRoles ?.filter((role) => role.canBeAssignedToAgents) .map((role) => ({ label: role.label, value: role.id, - })) || []; + })) || []), + ]; const noModelsAvailable = modelOptions.length === 0; @@ -122,6 +128,19 @@ export const SettingsAIAgentForm = ({ )} + {formValues.modelId && ( + + + onFieldChange('modelConfiguration', configuration) + } + disabled={disabled} + /> + + )} +