diff --git a/.github/workflows/ci-server.yaml b/.github/workflows/ci-server.yaml index a0d940de605..6126b14ebed 100644 --- a/.github/workflows/ci-server.yaml +++ b/.github/workflows/ci-server.yaml @@ -171,7 +171,7 @@ jobs: strategy: fail-fast: false matrix: - shard: [1, 2, 3, 4, 5, 6] + shard: [1, 2, 3, 4, 5, 6, 7, 8] services: postgres: image: twentycrm/twenty-postgres-spilo @@ -209,7 +209,7 @@ jobs: ANALYTICS_ENABLED: true CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty" CLICKHOUSE_PASSWORD: clickhousePassword - SHARD_COUNTER: 6 + SHARD_COUNTER: 8 steps: - name: Fetch custom Github Actions and base branch history uses: actions/checkout@v4 diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 5f275c9aa9c..ebbdf49a417 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -56,21 +56,22 @@ "@react-pdf/renderer": "^4.1.6", "@scalar/api-reference-react": "^0.4.36", "@sentry/react": "^10.27.0", - "@tiptap/core": "^3.4.2", - "@tiptap/extension-bold": "^3.4.2", - "@tiptap/extension-document": "^3.4.2", - "@tiptap/extension-hard-break": "^3.4.2", - "@tiptap/extension-heading": "^3.4.2", - "@tiptap/extension-image": "^3.4.4", - "@tiptap/extension-italic": "^3.4.2", - "@tiptap/extension-link": "^3.4.2", + "@tiptap/core": "3.4.2", + "@tiptap/extension-bold": "3.4.2", + "@tiptap/extension-document": "3.4.2", + "@tiptap/extension-hard-break": "3.4.2", + "@tiptap/extension-heading": "3.4.2", + "@tiptap/extension-image": "3.4.4", + "@tiptap/extension-italic": "3.4.2", + "@tiptap/extension-link": "3.4.2", "@tiptap/extension-list": "3.4.2", - "@tiptap/extension-paragraph": "^3.4.2", - "@tiptap/extension-strike": "^3.4.2", - "@tiptap/extension-text": "^3.4.2", - "@tiptap/extension-underline": "^3.4.2", - "@tiptap/extensions": "^3.4.2", - "@tiptap/react": "^3.4.2", + "@tiptap/extension-paragraph": "3.4.2", + "@tiptap/extension-strike": "3.4.2", + "@tiptap/extension-text": "3.4.2", + "@tiptap/extension-underline": "3.4.2", + "@tiptap/extensions": "3.4.2", + "@tiptap/react": "3.4.2", + "@types/marked": "^6.0.0", "@xyflow/react": "^12.4.2", "ai": "5.0.52", "apollo-link-rest": "^0.9.0", @@ -90,6 +91,7 @@ "jwt-decode": "^4.0.0", "linkify-react": "^4.1.3", "linkifyjs": "^4.1.3", + "marked": "^17.0.1", "qs": "^6.11.2", "react-data-grid": "7.0.0-beta.13", "react-datepicker": "^6.7.1", @@ -120,7 +122,7 @@ "@lingui/swc-plugin": "^5.6.0", "@lingui/vite-plugin": "^5.1.2", "@playwright/test": "^1.56.1", - "@tiptap/suggestion": "^3.4.2", + "@tiptap/suggestion": "3.4.2", "@types/apollo-upload-client": "^17.0.2", "@types/file-saver": "^2.0.7", "@types/js-cookie": "^3.0.3", diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 0f081a0a511..ab4e353af8f 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -972,6 +972,14 @@ export type CreateServerlessFunctionInput = { timeoutSeconds?: InputMaybe; }; +export type CreateSkillInput = { + content: Scalars['String']; + description?: InputMaybe; + icon?: InputMaybe; + label: Scalars['String']; + name: Scalars['String']; +}; + export type CreateViewFieldInput = { aggregateOperation?: InputMaybe; fieldMetadataId: Scalars['UUID']; @@ -1807,6 +1815,7 @@ export enum ModelProvider { export type Mutation = { __typename?: 'Mutation'; + activateSkill: Skill; activateWorkflowVersion: Scalars['Boolean']; activateWorkspace: Workspace; assignRoleToAgent: Scalars['Boolean']; @@ -1854,9 +1863,11 @@ export type Mutation = { createRowLevelPermissionPredicate: RowLevelPermissionPredicate; createRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup; createSAMLIdentityProvider: SetupSsoOutput; + createSkill: Skill; createWebhook: Webhook; createWorkflowVersionEdge: WorkflowVersionStepChanges; createWorkflowVersionStep: WorkflowVersionStepChanges; + deactivateSkill: Skill; deactivateWorkflowVersion: Scalars['Boolean']; deleteApprovedAccessDomain: Scalars['Boolean']; deleteCoreView: Scalars['Boolean']; @@ -1886,6 +1897,7 @@ export type Mutation = { deleteRowLevelPermissionPredicate: RowLevelPermissionPredicate; deleteRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup; deleteSSOIdentityProvider: DeleteSsoOutput; + deleteSkill: Skill; deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput; deleteUser: User; deleteUserFromWorkspace: UserWorkspace; @@ -1981,6 +1993,7 @@ export type Mutation = { updatePasswordViaResetToken: InvalidatePasswordOutput; updateRowLevelPermissionPredicate: RowLevelPermissionPredicate; updateRowLevelPermissionPredicateGroup: RowLevelPermissionPredicateGroup; + updateSkill: Skill; updateUserEmail: Scalars['Boolean']; updateWebhook?: Maybe; updateWorkflowRunStep: WorkflowAction; @@ -2005,6 +2018,11 @@ export type Mutation = { }; +export type MutationActivateSkillArgs = { + id: Scalars['UUID']; +}; + + export type MutationActivateWorkflowVersionArgs = { workflowVersionId: Scalars['UUID']; }; @@ -2228,6 +2246,11 @@ export type MutationCreateSamlIdentityProviderArgs = { }; +export type MutationCreateSkillArgs = { + input: CreateSkillInput; +}; + + export type MutationCreateWebhookArgs = { input: CreateWebhookInput; }; @@ -2243,6 +2266,11 @@ export type MutationCreateWorkflowVersionStepArgs = { }; +export type MutationDeactivateSkillArgs = { + id: Scalars['UUID']; +}; + + export type MutationDeactivateWorkflowVersionArgs = { workflowVersionId: Scalars['UUID']; }; @@ -2384,6 +2412,11 @@ export type MutationDeleteSsoIdentityProviderArgs = { }; +export type MutationDeleteSkillArgs = { + id: Scalars['UUID']; +}; + + export type MutationDeleteTwoFactorAuthenticationMethodArgs = { twoFactorAuthenticationMethodId: Scalars['UUID']; }; @@ -2854,6 +2887,11 @@ export type MutationUpdateRowLevelPermissionPredicateGroupArgs = { }; +export type MutationUpdateSkillArgs = { + input: UpdateSkillInput; +}; + + export type MutationUpdateUserEmailArgs = { newEmail: Scalars['String']; verifyEmailRedirectPath?: InputMaybe; @@ -3390,6 +3428,8 @@ export type Query = { object: Object; objects: ObjectConnection; search: SearchResultConnection; + skill?: Maybe; + skills: Array; validatePasswordResetToken: ValidatePasswordResetTokenOutput; versionInfo: VersionInfo; webhook?: Maybe; @@ -3732,6 +3772,11 @@ export type QuerySearchArgs = { }; +export type QuerySkillArgs = { + id: Scalars['UUID']; +}; + + export type QueryValidatePasswordResetTokenArgs = { passwordResetToken: Scalars['String']; }; @@ -4159,6 +4204,22 @@ export type SignedFile = { token: Scalars['String']; }; +export type Skill = { + __typename?: 'Skill'; + applicationId?: Maybe; + content: Scalars['String']; + createdAt: Scalars['DateTime']; + description?: Maybe; + icon?: Maybe; + id: Scalars['UUID']; + isActive: Scalars['Boolean']; + isCustom: Scalars['Boolean']; + label: Scalars['String']; + name: Scalars['String']; + standardId?: Maybe; + updatedAt: Scalars['DateTime']; +}; + export type StandaloneRichTextConfiguration = { __typename?: 'StandaloneRichTextConfiguration'; body: RichTextV2Body; @@ -4603,6 +4664,16 @@ export type UpdateServerlessFunctionInputUpdates = { timeoutSeconds?: InputMaybe; }; +export type UpdateSkillInput = { + content?: InputMaybe; + description?: InputMaybe; + icon?: InputMaybe; + id: Scalars['UUID']; + isActive?: InputMaybe; + label?: InputMaybe; + name?: InputMaybe; +}; + export type UpdateViewFieldInput = { /** The id of the view field to update */ id: Scalars['UUID']; @@ -5220,6 +5291,15 @@ export type WorkspaceUrlsAndId = { 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, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }; +export type SkillFieldsFragment = { __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string }; + +export type ActivateSkillMutationVariables = Exact<{ + id: Scalars['UUID']; +}>; + + +export type ActivateSkillMutation = { __typename?: 'Mutation', activateSkill: { __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string } }; + export type AssignRoleToAgentMutationVariables = Exact<{ agentId: Scalars['UUID']; roleId: Scalars['UUID']; @@ -5240,6 +5320,20 @@ 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, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type CreateSkillMutationVariables = Exact<{ + input: CreateSkillInput; +}>; + + +export type CreateSkillMutation = { __typename?: 'Mutation', createSkill: { __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string } }; + +export type DeactivateSkillMutationVariables = Exact<{ + id: Scalars['UUID']; +}>; + + +export type DeactivateSkillMutation = { __typename?: 'Mutation', deactivateSkill: { __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string } }; + export type DeleteOneAgentMutationVariables = Exact<{ input: AgentIdInput; }>; @@ -5247,6 +5341,13 @@ export type DeleteOneAgentMutationVariables = Exact<{ 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, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type DeleteSkillMutationVariables = Exact<{ + id: Scalars['UUID']; +}>; + + +export type DeleteSkillMutation = { __typename?: 'Mutation', deleteSkill: { __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string } }; + export type EvaluateAgentTurnMutationVariables = Exact<{ turnId: Scalars['UUID']; }>; @@ -5276,11 +5377,23 @@ 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, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type UpdateSkillMutationVariables = Exact<{ + input: UpdateSkillInput; +}>; + + +export type UpdateSkillMutation = { __typename?: 'Mutation', updateSkill: { __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string } }; + 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, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }> }; +export type FindManySkillsQueryVariables = Exact<{ [key: string]: never; }>; + + +export type FindManySkillsQuery = { __typename?: 'Query', skills: Array<{ __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string }> }; + export type FindOneAgentQueryVariables = Exact<{ id: Scalars['UUID']; }>; @@ -5288,6 +5401,13 @@ 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, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type FindOneSkillQueryVariables = Exact<{ + id: Scalars['UUID']; +}>; + + +export type FindOneSkillQuery = { __typename?: 'Query', skill?: { __typename?: 'Skill', id: string, name: string, label: string, description?: string | null, icon?: string | null, content: string, isCustom: boolean, isActive: boolean, createdAt: string, updatedAt: string } | null }; + export type GetAgentTurnsQueryVariables = Exact<{ agentId: Scalars['UUID']; }>; @@ -6758,6 +6878,20 @@ export type GetWorkspaceFromInviteHashQueryVariables = Exact<{ export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: string, displayName?: string | null, logo?: string | null, allowImpersonation: boolean } }; +export const SkillFieldsFragmentDoc = gql` + fragment SkillFields on Skill { + id + name + label + description + icon + content + isCustom + isActive + createdAt + updatedAt +} + `; export const AgentFieldsFragmentDoc = gql` fragment AgentFields on Agent { id @@ -7417,6 +7551,39 @@ export const WorkflowDiffFragmentFragmentDoc = gql` stepsDiff } `; +export const ActivateSkillDocument = gql` + mutation ActivateSkill($id: UUID!) { + activateSkill(id: $id) { + ...SkillFields + } +} + ${SkillFieldsFragmentDoc}`; +export type ActivateSkillMutationFn = Apollo.MutationFunction; + +/** + * __useActivateSkillMutation__ + * + * To run a mutation, you first call `useActivateSkillMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useActivateSkillMutation` 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 [activateSkillMutation, { data, loading, error }] = useActivateSkillMutation({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useActivateSkillMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(ActivateSkillDocument, options); + } +export type ActivateSkillMutationHookResult = ReturnType; +export type ActivateSkillMutationResult = Apollo.MutationResult; +export type ActivateSkillMutationOptions = Apollo.BaseMutationOptions; export const AssignRoleToAgentDocument = gql` mutation AssignRoleToAgent($agentId: UUID!, $roleId: UUID!) { assignRoleToAgent(agentId: $agentId, roleId: $roleId) @@ -7517,6 +7684,72 @@ export function useCreateOneAgentMutation(baseOptions?: Apollo.MutationHookOptio export type CreateOneAgentMutationHookResult = ReturnType; export type CreateOneAgentMutationResult = Apollo.MutationResult; export type CreateOneAgentMutationOptions = Apollo.BaseMutationOptions; +export const CreateSkillDocument = gql` + mutation CreateSkill($input: CreateSkillInput!) { + createSkill(input: $input) { + ...SkillFields + } +} + ${SkillFieldsFragmentDoc}`; +export type CreateSkillMutationFn = Apollo.MutationFunction; + +/** + * __useCreateSkillMutation__ + * + * To run a mutation, you first call `useCreateSkillMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useCreateSkillMutation` 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 [createSkillMutation, { data, loading, error }] = useCreateSkillMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useCreateSkillMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateSkillDocument, options); + } +export type CreateSkillMutationHookResult = ReturnType; +export type CreateSkillMutationResult = Apollo.MutationResult; +export type CreateSkillMutationOptions = Apollo.BaseMutationOptions; +export const DeactivateSkillDocument = gql` + mutation DeactivateSkill($id: UUID!) { + deactivateSkill(id: $id) { + ...SkillFields + } +} + ${SkillFieldsFragmentDoc}`; +export type DeactivateSkillMutationFn = Apollo.MutationFunction; + +/** + * __useDeactivateSkillMutation__ + * + * To run a mutation, you first call `useDeactivateSkillMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeactivateSkillMutation` 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 [deactivateSkillMutation, { data, loading, error }] = useDeactivateSkillMutation({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useDeactivateSkillMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeactivateSkillDocument, options); + } +export type DeactivateSkillMutationHookResult = ReturnType; +export type DeactivateSkillMutationResult = Apollo.MutationResult; +export type DeactivateSkillMutationOptions = Apollo.BaseMutationOptions; export const DeleteOneAgentDocument = gql` mutation DeleteOneAgent($input: AgentIdInput!) { deleteOneAgent(input: $input) { @@ -7550,6 +7783,39 @@ export function useDeleteOneAgentMutation(baseOptions?: Apollo.MutationHookOptio export type DeleteOneAgentMutationHookResult = ReturnType; export type DeleteOneAgentMutationResult = Apollo.MutationResult; export type DeleteOneAgentMutationOptions = Apollo.BaseMutationOptions; +export const DeleteSkillDocument = gql` + mutation DeleteSkill($id: UUID!) { + deleteSkill(id: $id) { + ...SkillFields + } +} + ${SkillFieldsFragmentDoc}`; +export type DeleteSkillMutationFn = Apollo.MutationFunction; + +/** + * __useDeleteSkillMutation__ + * + * To run a mutation, you first call `useDeleteSkillMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useDeleteSkillMutation` 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 [deleteSkillMutation, { data, loading, error }] = useDeleteSkillMutation({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useDeleteSkillMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeleteSkillDocument, options); + } +export type DeleteSkillMutationHookResult = ReturnType; +export type DeleteSkillMutationResult = Apollo.MutationResult; +export type DeleteSkillMutationOptions = Apollo.BaseMutationOptions; export const EvaluateAgentTurnDocument = gql` mutation EvaluateAgentTurn($turnId: UUID!) { evaluateAgentTurn(turnId: $turnId) { @@ -7694,6 +7960,39 @@ export function useUpdateOneAgentMutation(baseOptions?: Apollo.MutationHookOptio export type UpdateOneAgentMutationHookResult = ReturnType; export type UpdateOneAgentMutationResult = Apollo.MutationResult; export type UpdateOneAgentMutationOptions = Apollo.BaseMutationOptions; +export const UpdateSkillDocument = gql` + mutation UpdateSkill($input: UpdateSkillInput!) { + updateSkill(input: $input) { + ...SkillFields + } +} + ${SkillFieldsFragmentDoc}`; +export type UpdateSkillMutationFn = Apollo.MutationFunction; + +/** + * __useUpdateSkillMutation__ + * + * To run a mutation, you first call `useUpdateSkillMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useUpdateSkillMutation` 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 [updateSkillMutation, { data, loading, error }] = useUpdateSkillMutation({ + * variables: { + * input: // value for 'input' + * }, + * }); + */ +export function useUpdateSkillMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(UpdateSkillDocument, options); + } +export type UpdateSkillMutationHookResult = ReturnType; +export type UpdateSkillMutationResult = Apollo.MutationResult; +export type UpdateSkillMutationOptions = Apollo.BaseMutationOptions; export const FindManyAgentsDocument = gql` query FindManyAgents { findManyAgents { @@ -7728,6 +8027,40 @@ export function useFindManyAgentsLazyQuery(baseOptions?: Apollo.LazyQueryHookOpt export type FindManyAgentsQueryHookResult = ReturnType; export type FindManyAgentsLazyQueryHookResult = ReturnType; export type FindManyAgentsQueryResult = Apollo.QueryResult; +export const FindManySkillsDocument = gql` + query FindManySkills { + skills { + ...SkillFields + } +} + ${SkillFieldsFragmentDoc}`; + +/** + * __useFindManySkillsQuery__ + * + * To run a query within a React component, call `useFindManySkillsQuery` and pass it any options that fit your needs. + * When your component renders, `useFindManySkillsQuery` 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 } = useFindManySkillsQuery({ + * variables: { + * }, + * }); + */ +export function useFindManySkillsQuery(baseOptions?: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindManySkillsDocument, options); + } +export function useFindManySkillsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindManySkillsDocument, options); + } +export type FindManySkillsQueryHookResult = ReturnType; +export type FindManySkillsLazyQueryHookResult = ReturnType; +export type FindManySkillsQueryResult = Apollo.QueryResult; export const FindOneAgentDocument = gql` query FindOneAgent($id: UUID!) { findOneAgent(input: {id: $id}) { @@ -7763,6 +8096,41 @@ export function useFindOneAgentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio export type FindOneAgentQueryHookResult = ReturnType; export type FindOneAgentLazyQueryHookResult = ReturnType; export type FindOneAgentQueryResult = Apollo.QueryResult; +export const FindOneSkillDocument = gql` + query FindOneSkill($id: UUID!) { + skill(id: $id) { + ...SkillFields + } +} + ${SkillFieldsFragmentDoc}`; + +/** + * __useFindOneSkillQuery__ + * + * To run a query within a React component, call `useFindOneSkillQuery` and pass it any options that fit your needs. + * When your component renders, `useFindOneSkillQuery` 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 } = useFindOneSkillQuery({ + * variables: { + * id: // value for 'id' + * }, + * }); + */ +export function useFindOneSkillQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindOneSkillDocument, options); + } +export function useFindOneSkillLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindOneSkillDocument, options); + } +export type FindOneSkillQueryHookResult = ReturnType; +export type FindOneSkillLazyQueryHookResult = ReturnType; +export type FindOneSkillQueryResult = Apollo.QueryResult; export const GetAgentTurnsDocument = gql` query GetAgentTurns($agentId: UUID!) { agentTurns(agentId: $agentId) { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 25295430535..a7005a36c2e 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -3996,6 +3996,22 @@ export type SignedFile = { token: Scalars['String']; }; +export type Skill = { + __typename?: 'Skill'; + applicationId?: Maybe; + content: Scalars['String']; + createdAt: Scalars['DateTime']; + description?: Maybe; + icon?: Maybe; + id: Scalars['UUID']; + isActive: Scalars['Boolean']; + isCustom: Scalars['Boolean']; + label: Scalars['String']; + name: Scalars['String']; + standardId?: Maybe; + updatedAt: Scalars['DateTime']; +}; + export type StandaloneRichTextConfiguration = { __typename?: 'StandaloneRichTextConfiguration'; body: RichTextV2Body; diff --git a/packages/twenty-front/src/modules/advanced-text-editor/hooks/useAdvancedTextEditor.ts b/packages/twenty-front/src/modules/advanced-text-editor/hooks/useAdvancedTextEditor.ts index f4593038ad7..53f26b57835 100644 --- a/packages/twenty-front/src/modules/advanced-text-editor/hooks/useAdvancedTextEditor.ts +++ b/packages/twenty-front/src/modules/advanced-text-editor/hooks/useAdvancedTextEditor.ts @@ -17,9 +17,12 @@ import { Text } from '@tiptap/extension-text'; import { Underline } from '@tiptap/extension-underline'; import { Dropcursor, Placeholder, UndoRedo } from '@tiptap/extensions'; import { type Editor, useEditor } from '@tiptap/react'; +import { marked } from 'marked'; import { type DependencyList, useMemo } from 'react'; import { isDefined } from 'twenty-shared/utils'; +export type AdvancedTextEditorContentType = 'json' | 'markdown'; + type UseAdvancedTextEditorProps = { placeholder: string | undefined; readonly: boolean | undefined; @@ -30,6 +33,7 @@ type UseAdvancedTextEditorProps = { onImageUpload?: (file: File) => Promise; onImageUploadError?: (error: Error, file: File) => void; enableSlashCommand?: boolean; + contentType?: AdvancedTextEditorContentType; }; export const useAdvancedTextEditor = ( @@ -43,9 +47,12 @@ export const useAdvancedTextEditor = ( onImageUpload, onImageUploadError, enableSlashCommand, + contentType = 'json', }: UseAdvancedTextEditorProps, dependencies?: DependencyList, ) => { + const isMarkdownMode = contentType === 'markdown'; + const extensions = useMemo( () => [ Document, @@ -87,12 +94,23 @@ export const useAdvancedTextEditor = ( ], ); + const getEditorContent = () => { + if (!isDefined(defaultValue)) { + return undefined; + } + + if (isMarkdownMode) { + // Convert markdown to HTML, then TipTap will parse the HTML + return marked.parse(defaultValue, { async: false }) as string; + } + + return getInitialAdvancedTextEditorContent(defaultValue); + }; + const editor = useEditor( { extensions, - content: isDefined(defaultValue) - ? getInitialAdvancedTextEditorContent(defaultValue) - : undefined, + content: getEditorContent(), editable: !readonly, onUpdate: ({ editor }) => { onUpdate(editor); diff --git a/packages/twenty-front/src/modules/ai/graphql/fragments/skillFragment.ts b/packages/twenty-front/src/modules/ai/graphql/fragments/skillFragment.ts new file mode 100644 index 00000000000..32976c77fc3 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/fragments/skillFragment.ts @@ -0,0 +1,16 @@ +import { gql } from '@apollo/client'; + +export const SKILL_FRAGMENT = gql` + fragment SkillFields on Skill { + id + name + label + description + icon + content + isCustom + isActive + createdAt + updatedAt + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/activateSkill.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/activateSkill.ts new file mode 100644 index 00000000000..29877dc27cc --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/mutations/activateSkill.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { SKILL_FRAGMENT } from '@/ai/graphql/fragments/skillFragment'; + +export const ACTIVATE_SKILL = gql` + ${SKILL_FRAGMENT} + mutation ActivateSkill($id: UUID!) { + activateSkill(id: $id) { + ...SkillFields + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/createSkill.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/createSkill.ts new file mode 100644 index 00000000000..90f70dfa707 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/mutations/createSkill.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { SKILL_FRAGMENT } from '@/ai/graphql/fragments/skillFragment'; + +export const CREATE_SKILL = gql` + ${SKILL_FRAGMENT} + mutation CreateSkill($input: CreateSkillInput!) { + createSkill(input: $input) { + ...SkillFields + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/deactivateSkill.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/deactivateSkill.ts new file mode 100644 index 00000000000..f5611caa7c1 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/mutations/deactivateSkill.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { SKILL_FRAGMENT } from '@/ai/graphql/fragments/skillFragment'; + +export const DEACTIVATE_SKILL = gql` + ${SKILL_FRAGMENT} + mutation DeactivateSkill($id: UUID!) { + deactivateSkill(id: $id) { + ...SkillFields + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/deleteSkill.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/deleteSkill.ts new file mode 100644 index 00000000000..6936a72dd99 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/mutations/deleteSkill.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { SKILL_FRAGMENT } from '@/ai/graphql/fragments/skillFragment'; + +export const DELETE_SKILL = gql` + ${SKILL_FRAGMENT} + mutation DeleteSkill($id: UUID!) { + deleteSkill(id: $id) { + ...SkillFields + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/mutations/updateSkill.ts b/packages/twenty-front/src/modules/ai/graphql/mutations/updateSkill.ts new file mode 100644 index 00000000000..580c88d7b16 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/mutations/updateSkill.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { SKILL_FRAGMENT } from '@/ai/graphql/fragments/skillFragment'; + +export const UPDATE_SKILL = gql` + ${SKILL_FRAGMENT} + mutation UpdateSkill($input: UpdateSkillInput!) { + updateSkill(input: $input) { + ...SkillFields + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/queries/findManySkills.ts b/packages/twenty-front/src/modules/ai/graphql/queries/findManySkills.ts new file mode 100644 index 00000000000..9ae2855de72 --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/queries/findManySkills.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { SKILL_FRAGMENT } from '@/ai/graphql/fragments/skillFragment'; + +export const FIND_MANY_SKILLS = gql` + ${SKILL_FRAGMENT} + query FindManySkills { + skills { + ...SkillFields + } + } +`; diff --git a/packages/twenty-front/src/modules/ai/graphql/queries/findOneSkill.ts b/packages/twenty-front/src/modules/ai/graphql/queries/findOneSkill.ts new file mode 100644 index 00000000000..5e52085013f --- /dev/null +++ b/packages/twenty-front/src/modules/ai/graphql/queries/findOneSkill.ts @@ -0,0 +1,12 @@ +import { gql } from '@apollo/client'; + +import { SKILL_FRAGMENT } from '@/ai/graphql/fragments/skillFragment'; + +export const FIND_ONE_SKILL = gql` + ${SKILL_FRAGMENT} + query FindOneSkill($id: UUID!) { + skill(id: $id) { + ...SkillFields + } + } +`; diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index 28f2557a70d..cadf50811d4 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -171,6 +171,12 @@ const SettingsAgentTurnDetail = lazy(() => })), ); +const SettingsSkillForm = lazy(() => + import('~/pages/settings/ai/SettingsSkillForm').then((module) => ({ + default: module.SettingsSkillForm, + })), +); + const SettingsWorkspaceMembers = lazy(() => import('~/pages/settings/members/SettingsWorkspaceMembers').then( (module) => ({ @@ -440,6 +446,14 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( path={SettingsPath.AIAgentTurnDetail} element={} /> + } + /> + } + /> } /> } /> { role: t`role`, roleTarget: t`role target`, agent: t`agent`, + skill: t`skill`, pageLayout: t`page layout`, pageLayoutTab: t`page layout tab`, pageLayoutWidget: t`page layout widget`, diff --git a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormAdvancedTextFieldInput.tsx b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormAdvancedTextFieldInput.tsx index 7722e3ba0b5..cdf40c240ec 100644 --- a/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormAdvancedTextFieldInput.tsx +++ b/packages/twenty-front/src/modules/object-record/record-field/ui/form-types/components/FormAdvancedTextFieldInput.tsx @@ -1,5 +1,8 @@ import { AdvancedTextEditor } from '@/advanced-text-editor/components/AdvancedTextEditor'; -import { useAdvancedTextEditor } from '@/advanced-text-editor/hooks/useAdvancedTextEditor'; +import { + type AdvancedTextEditorContentType, + useAdvancedTextEditor, +} from '@/advanced-text-editor/hooks/useAdvancedTextEditor'; import { FormFieldInputContainer } from '@/object-record/record-field/ui/form-types/components/FormFieldInputContainer'; import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent'; import { InputErrorHelper } from '@/ui/input/components/InputErrorHelper'; @@ -86,6 +89,7 @@ type FormAdvancedTextFieldInputProps = { fullScreenBreadcrumbs?: BreadcrumbProps['links']; minHeight: number; maxWidth: number; + contentType?: AdvancedTextEditorContentType; }; export const FormAdvancedTextFieldInput = ({ @@ -103,6 +107,7 @@ export const FormAdvancedTextFieldInput = ({ fullScreenBreadcrumbs, minHeight, maxWidth, + contentType = 'json', }: FormAdvancedTextFieldInputProps) => { const instanceId = useId(); const isMobile = useIsMobile(); @@ -119,9 +124,15 @@ export const FormAdvancedTextFieldInput = ({ placeholder: placeholder, readonly, defaultValue, + contentType, onUpdate: (editor) => { - const jsonContent = editor.getJSON(); - onChange(JSON.stringify(jsonContent)); + if (contentType === 'markdown') { + // For markdown mode, output the HTML which preserves formatting + onChange(editor.getHTML()); + } else { + const jsonContent = editor.getJSON(); + onChange(JSON.stringify(jsonContent)); + } }, onFocus: () => { pushFocusItemToFocusStack({ diff --git a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx index 41ff414c37e..35a15133a47 100644 --- a/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx +++ b/packages/twenty-front/src/pages/settings/ai/SettingsAI.tsx @@ -1,82 +1,17 @@ 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 { - IconPlus, - IconRobot, - IconServer, - IconSettings, -} from 'twenty-ui/display'; -import { Button } from 'twenty-ui/input'; -import { UndecoratedLink } from 'twenty-ui/navigation'; 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', -}; +import { SettingsSkillsTable } from './components/SettingsSkillsTable'; export const SettingsAI = () => { - 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 ( -