AI settings tab (#13496)
https://github.com/user-attachments/assets/87f5a556-ff12-4ce0-aaa7-9120c0432151 --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
@@ -53,7 +53,10 @@ export type Agent = {
|
||||
__typename?: 'Agent';
|
||||
createdAt: Scalars['DateTime'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
icon?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
isCustom: Scalars['Boolean'];
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
@@ -81,6 +84,13 @@ export type AgentChatThread = {
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type AgentHandoffDto = {
|
||||
__typename?: 'AgentHandoffDTO';
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
toAgent: Agent;
|
||||
};
|
||||
|
||||
export type AgentIdInput = {
|
||||
/** The id of the agent. */
|
||||
id: Scalars['UUID'];
|
||||
@@ -473,6 +483,23 @@ export type CreateAgentChatThreadInput = {
|
||||
agentId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type CreateAgentHandoffInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
fromAgentId: Scalars['UUID'];
|
||||
toAgentId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type CreateAgentInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
prompt: Scalars['String'];
|
||||
responseFormat?: InputMaybe<Scalars['JSON']>;
|
||||
roleId?: InputMaybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type CreateApiKeyDto = {
|
||||
expiresAt: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
@@ -1123,6 +1150,7 @@ export type Mutation = {
|
||||
checkoutSession: BillingSessionOutput;
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createAgentChatThread: AgentChatThread;
|
||||
createAgentHandoff: Scalars['Boolean'];
|
||||
createApiKey: ApiKey;
|
||||
createApprovedAccessDomain: ApprovedAccessDomain;
|
||||
createDatabaseConfigVariable: Scalars['Boolean'];
|
||||
@@ -1130,6 +1158,7 @@ export type Mutation = {
|
||||
createFile: File;
|
||||
createOIDCIdentityProvider: SetupSsoOutput;
|
||||
createObjectEvent: Analytics;
|
||||
createOneAgent: Agent;
|
||||
createOneAppToken: AppToken;
|
||||
createOneField: Field;
|
||||
createOneObject: Object;
|
||||
@@ -1144,6 +1173,7 @@ export type Mutation = {
|
||||
deleteCurrentWorkspace: Workspace;
|
||||
deleteDatabaseConfigVariable: Scalars['Boolean'];
|
||||
deleteFile: File;
|
||||
deleteOneAgent: Agent;
|
||||
deleteOneField: Field;
|
||||
deleteOneObject: Object;
|
||||
deleteOneRemoteServer: RemoteServer;
|
||||
@@ -1173,6 +1203,7 @@ export type Mutation = {
|
||||
initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
publishServerlessFunction: ServerlessFunction;
|
||||
removeAgentHandoff: Scalars['Boolean'];
|
||||
removeRoleFromAgent: Scalars['Boolean'];
|
||||
renewToken: AuthTokens;
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
@@ -1264,6 +1295,11 @@ export type MutationCreateAgentChatThreadArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateAgentHandoffArgs = {
|
||||
input: CreateAgentHandoffInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateApiKeyArgs = {
|
||||
input: CreateApiKeyDto;
|
||||
};
|
||||
@@ -1303,6 +1339,11 @@ export type MutationCreateObjectEventArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOneAgentArgs = {
|
||||
input: CreateAgentInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOneAppTokenArgs = {
|
||||
input: CreateOneAppTokenInput;
|
||||
};
|
||||
@@ -1368,6 +1409,11 @@ export type MutationDeleteFileArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteOneAgentArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteOneFieldArgs = {
|
||||
input: DeleteOneFieldInput;
|
||||
};
|
||||
@@ -1501,6 +1547,11 @@ export type MutationPublishServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRemoveAgentHandoffArgs = {
|
||||
input: RemoveAgentHandoffInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRemoveRoleFromAgentArgs = {
|
||||
agentId: Scalars['UUID'];
|
||||
};
|
||||
@@ -1990,7 +2041,10 @@ export type Query = {
|
||||
currentWorkspace: Workspace;
|
||||
field: Field;
|
||||
fields: FieldConnection;
|
||||
findAgentHandoffTargets: Array<Agent>;
|
||||
findAgentHandoffs: Array<AgentHandoffDto>;
|
||||
findDistantTablesWithStatus: Array<RemoteTable>;
|
||||
findManyAgents: Array<Agent>;
|
||||
findManyRemoteServersByType: Array<RemoteServer>;
|
||||
findManyServerlessFunctions: Array<ServerlessFunction>;
|
||||
findOneAgent: Agent;
|
||||
@@ -2078,6 +2132,16 @@ export type QueryFieldsArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindAgentHandoffTargetsArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindAgentHandoffsArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindDistantTablesWithStatusArgs = {
|
||||
input: FindManyRemoteTablesInput;
|
||||
};
|
||||
@@ -2313,6 +2377,11 @@ export enum RemoteTableStatus {
|
||||
SYNCED = 'SYNCED'
|
||||
}
|
||||
|
||||
export type RemoveAgentHandoffInput = {
|
||||
fromAgentId: Scalars['String'];
|
||||
toAgentId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ResendEmailVerificationTokenOutput = {
|
||||
__typename?: 'ResendEmailVerificationTokenOutput';
|
||||
success: Scalars['Boolean'];
|
||||
@@ -2667,11 +2736,14 @@ export type UuidFilterComparison = {
|
||||
|
||||
export type UpdateAgentInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
responseFormat?: InputMaybe<Scalars['JSON']>;
|
||||
roleId?: InputMaybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type UpdateApiKeyDto = {
|
||||
@@ -3088,6 +3160,8 @@ export type WorkspaceUrlsAndId = {
|
||||
workspaceUrls: WorkspaceUrls;
|
||||
};
|
||||
|
||||
export type AgentFieldsFragment = { __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null, isCustom: boolean, createdAt: string, updatedAt: string };
|
||||
|
||||
export type AssignRoleToAgentMutationVariables = Exact<{
|
||||
agentId: Scalars['UUID'];
|
||||
roleId: Scalars['UUID'];
|
||||
@@ -3103,6 +3177,34 @@ export type CreateAgentChatThreadMutationVariables = Exact<{
|
||||
|
||||
export type CreateAgentChatThreadMutation = { __typename?: 'Mutation', createAgentChatThread: { __typename?: 'AgentChatThread', id: any, agentId: any, title?: string | null, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type CreateAgentHandoffMutationVariables = Exact<{
|
||||
input: CreateAgentHandoffInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateAgentHandoffMutation = { __typename?: 'Mutation', createAgentHandoff: boolean };
|
||||
|
||||
export type CreateOneAgentMutationVariables = Exact<{
|
||||
input: CreateAgentInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type DeleteOneAgentMutationVariables = Exact<{
|
||||
input: AgentIdInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type RemoveAgentHandoffMutationVariables = Exact<{
|
||||
input: RemoveAgentHandoffInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type RemoveAgentHandoffMutation = { __typename?: 'Mutation', removeAgentHandoff: boolean };
|
||||
|
||||
export type RemoveRoleFromAgentMutationVariables = Exact<{
|
||||
agentId: Scalars['UUID'];
|
||||
}>;
|
||||
@@ -3115,14 +3217,33 @@ export type UpdateOneAgentMutationVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: any, name: string, description?: string | null, prompt: string, modelId: string, responseFormat?: any | null } };
|
||||
export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type FindAgentHandoffTargetsQueryVariables = Exact<{
|
||||
input: AgentIdInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type FindAgentHandoffTargetsQuery = { __typename?: 'Query', findAgentHandoffTargets: Array<{ __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, modelId: string, prompt: string, isCustom: boolean, createdAt: string, updatedAt: string }> };
|
||||
|
||||
export type FindAgentHandoffsQueryVariables = Exact<{
|
||||
input: AgentIdInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type FindAgentHandoffsQuery = { __typename?: 'Query', findAgentHandoffs: Array<{ __typename?: 'AgentHandoffDTO', id: any, description?: string | null, toAgent: { __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, modelId: string, prompt: string, isCustom: boolean, createdAt: string, updatedAt: string } }> };
|
||||
|
||||
export type FindManyAgentsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: Array<{ __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null, isCustom: boolean, createdAt: string, updatedAt: string }> };
|
||||
|
||||
export type FindOneAgentQueryVariables = Exact<{
|
||||
id: Scalars['UUID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: any, name: string, description?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null } };
|
||||
export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: any, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: any | null, isCustom: boolean, createdAt: string, updatedAt: string } };
|
||||
|
||||
export type GetAgentChatMessagesQueryVariables = Exact<{
|
||||
threadId: Scalars['String'];
|
||||
@@ -4064,6 +4185,22 @@ export type GetWorkspaceFromInviteHashQueryVariables = Exact<{
|
||||
|
||||
export type GetWorkspaceFromInviteHashQuery = { __typename?: 'Query', findWorkspaceFromInviteHash: { __typename?: 'Workspace', id: any, displayName?: string | null, logo?: string | null, allowImpersonation: boolean } };
|
||||
|
||||
export const AgentFieldsFragmentDoc = gql`
|
||||
fragment AgentFields on Agent {
|
||||
id
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
prompt
|
||||
modelId
|
||||
responseFormat
|
||||
roleId
|
||||
isCustom
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
`;
|
||||
export const AuthTokenFragmentFragmentDoc = gql`
|
||||
fragment AuthTokenFragment on AuthToken {
|
||||
token
|
||||
@@ -4412,6 +4549,134 @@ export function useCreateAgentChatThreadMutation(baseOptions?: Apollo.MutationHo
|
||||
export type CreateAgentChatThreadMutationHookResult = ReturnType<typeof useCreateAgentChatThreadMutation>;
|
||||
export type CreateAgentChatThreadMutationResult = Apollo.MutationResult<CreateAgentChatThreadMutation>;
|
||||
export type CreateAgentChatThreadMutationOptions = Apollo.BaseMutationOptions<CreateAgentChatThreadMutation, CreateAgentChatThreadMutationVariables>;
|
||||
export const CreateAgentHandoffDocument = gql`
|
||||
mutation CreateAgentHandoff($input: CreateAgentHandoffInput!) {
|
||||
createAgentHandoff(input: $input)
|
||||
}
|
||||
`;
|
||||
export type CreateAgentHandoffMutationFn = Apollo.MutationFunction<CreateAgentHandoffMutation, CreateAgentHandoffMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateAgentHandoffMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateAgentHandoffMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateAgentHandoffMutation` 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 [createAgentHandoffMutation, { data, loading, error }] = useCreateAgentHandoffMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateAgentHandoffMutation(baseOptions?: Apollo.MutationHookOptions<CreateAgentHandoffMutation, CreateAgentHandoffMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateAgentHandoffMutation, CreateAgentHandoffMutationVariables>(CreateAgentHandoffDocument, options);
|
||||
}
|
||||
export type CreateAgentHandoffMutationHookResult = ReturnType<typeof useCreateAgentHandoffMutation>;
|
||||
export type CreateAgentHandoffMutationResult = Apollo.MutationResult<CreateAgentHandoffMutation>;
|
||||
export type CreateAgentHandoffMutationOptions = Apollo.BaseMutationOptions<CreateAgentHandoffMutation, CreateAgentHandoffMutationVariables>;
|
||||
export const CreateOneAgentDocument = gql`
|
||||
mutation CreateOneAgent($input: CreateAgentInput!) {
|
||||
createOneAgent(input: $input) {
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
${AgentFieldsFragmentDoc}`;
|
||||
export type CreateOneAgentMutationFn = Apollo.MutationFunction<CreateOneAgentMutation, CreateOneAgentMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useCreateOneAgentMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useCreateOneAgentMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useCreateOneAgentMutation` 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 [createOneAgentMutation, { data, loading, error }] = useCreateOneAgentMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useCreateOneAgentMutation(baseOptions?: Apollo.MutationHookOptions<CreateOneAgentMutation, CreateOneAgentMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<CreateOneAgentMutation, CreateOneAgentMutationVariables>(CreateOneAgentDocument, options);
|
||||
}
|
||||
export type CreateOneAgentMutationHookResult = ReturnType<typeof useCreateOneAgentMutation>;
|
||||
export type CreateOneAgentMutationResult = Apollo.MutationResult<CreateOneAgentMutation>;
|
||||
export type CreateOneAgentMutationOptions = Apollo.BaseMutationOptions<CreateOneAgentMutation, CreateOneAgentMutationVariables>;
|
||||
export const DeleteOneAgentDocument = gql`
|
||||
mutation DeleteOneAgent($input: AgentIdInput!) {
|
||||
deleteOneAgent(input: $input) {
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
${AgentFieldsFragmentDoc}`;
|
||||
export type DeleteOneAgentMutationFn = Apollo.MutationFunction<DeleteOneAgentMutation, DeleteOneAgentMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useDeleteOneAgentMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useDeleteOneAgentMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useDeleteOneAgentMutation` 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 [deleteOneAgentMutation, { data, loading, error }] = useDeleteOneAgentMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useDeleteOneAgentMutation(baseOptions?: Apollo.MutationHookOptions<DeleteOneAgentMutation, DeleteOneAgentMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<DeleteOneAgentMutation, DeleteOneAgentMutationVariables>(DeleteOneAgentDocument, options);
|
||||
}
|
||||
export type DeleteOneAgentMutationHookResult = ReturnType<typeof useDeleteOneAgentMutation>;
|
||||
export type DeleteOneAgentMutationResult = Apollo.MutationResult<DeleteOneAgentMutation>;
|
||||
export type DeleteOneAgentMutationOptions = Apollo.BaseMutationOptions<DeleteOneAgentMutation, DeleteOneAgentMutationVariables>;
|
||||
export const RemoveAgentHandoffDocument = gql`
|
||||
mutation RemoveAgentHandoff($input: RemoveAgentHandoffInput!) {
|
||||
removeAgentHandoff(input: $input)
|
||||
}
|
||||
`;
|
||||
export type RemoveAgentHandoffMutationFn = Apollo.MutationFunction<RemoveAgentHandoffMutation, RemoveAgentHandoffMutationVariables>;
|
||||
|
||||
/**
|
||||
* __useRemoveAgentHandoffMutation__
|
||||
*
|
||||
* To run a mutation, you first call `useRemoveAgentHandoffMutation` within a React component and pass it any options that fit your needs.
|
||||
* When your component renders, `useRemoveAgentHandoffMutation` 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 [removeAgentHandoffMutation, { data, loading, error }] = useRemoveAgentHandoffMutation({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useRemoveAgentHandoffMutation(baseOptions?: Apollo.MutationHookOptions<RemoveAgentHandoffMutation, RemoveAgentHandoffMutationVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useMutation<RemoveAgentHandoffMutation, RemoveAgentHandoffMutationVariables>(RemoveAgentHandoffDocument, options);
|
||||
}
|
||||
export type RemoveAgentHandoffMutationHookResult = ReturnType<typeof useRemoveAgentHandoffMutation>;
|
||||
export type RemoveAgentHandoffMutationResult = Apollo.MutationResult<RemoveAgentHandoffMutation>;
|
||||
export type RemoveAgentHandoffMutationOptions = Apollo.BaseMutationOptions<RemoveAgentHandoffMutation, RemoveAgentHandoffMutationVariables>;
|
||||
export const RemoveRoleFromAgentDocument = gql`
|
||||
mutation RemoveRoleFromAgent($agentId: UUID!) {
|
||||
removeRoleFromAgent(agentId: $agentId)
|
||||
@@ -4446,15 +4711,10 @@ export type RemoveRoleFromAgentMutationOptions = Apollo.BaseMutationOptions<Remo
|
||||
export const UpdateOneAgentDocument = gql`
|
||||
mutation UpdateOneAgent($input: UpdateAgentInput!) {
|
||||
updateOneAgent(input: $input) {
|
||||
id
|
||||
name
|
||||
description
|
||||
prompt
|
||||
modelId
|
||||
responseFormat
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
${AgentFieldsFragmentDoc}`;
|
||||
export type UpdateOneAgentMutationFn = Apollo.MutationFunction<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>;
|
||||
|
||||
/**
|
||||
@@ -4481,20 +4741,140 @@ export function useUpdateOneAgentMutation(baseOptions?: Apollo.MutationHookOptio
|
||||
export type UpdateOneAgentMutationHookResult = ReturnType<typeof useUpdateOneAgentMutation>;
|
||||
export type UpdateOneAgentMutationResult = Apollo.MutationResult<UpdateOneAgentMutation>;
|
||||
export type UpdateOneAgentMutationOptions = Apollo.BaseMutationOptions<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>;
|
||||
export const FindOneAgentDocument = gql`
|
||||
query FindOneAgent($id: UUID!) {
|
||||
findOneAgent(input: {id: $id}) {
|
||||
export const FindAgentHandoffTargetsDocument = gql`
|
||||
query FindAgentHandoffTargets($input: AgentIdInput!) {
|
||||
findAgentHandoffTargets(input: $input) {
|
||||
id
|
||||
name
|
||||
label
|
||||
description
|
||||
prompt
|
||||
icon
|
||||
modelId
|
||||
responseFormat
|
||||
roleId
|
||||
prompt
|
||||
isCustom
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useFindAgentHandoffTargetsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useFindAgentHandoffTargetsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useFindAgentHandoffTargetsQuery` 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 } = useFindAgentHandoffTargetsQuery({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useFindAgentHandoffTargetsQuery(baseOptions: Apollo.QueryHookOptions<FindAgentHandoffTargetsQuery, FindAgentHandoffTargetsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<FindAgentHandoffTargetsQuery, FindAgentHandoffTargetsQueryVariables>(FindAgentHandoffTargetsDocument, options);
|
||||
}
|
||||
export function useFindAgentHandoffTargetsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindAgentHandoffTargetsQuery, FindAgentHandoffTargetsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<FindAgentHandoffTargetsQuery, FindAgentHandoffTargetsQueryVariables>(FindAgentHandoffTargetsDocument, options);
|
||||
}
|
||||
export type FindAgentHandoffTargetsQueryHookResult = ReturnType<typeof useFindAgentHandoffTargetsQuery>;
|
||||
export type FindAgentHandoffTargetsLazyQueryHookResult = ReturnType<typeof useFindAgentHandoffTargetsLazyQuery>;
|
||||
export type FindAgentHandoffTargetsQueryResult = Apollo.QueryResult<FindAgentHandoffTargetsQuery, FindAgentHandoffTargetsQueryVariables>;
|
||||
export const FindAgentHandoffsDocument = gql`
|
||||
query FindAgentHandoffs($input: AgentIdInput!) {
|
||||
findAgentHandoffs(input: $input) {
|
||||
id
|
||||
description
|
||||
toAgent {
|
||||
id
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
modelId
|
||||
prompt
|
||||
isCustom
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* __useFindAgentHandoffsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useFindAgentHandoffsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useFindAgentHandoffsQuery` 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 } = useFindAgentHandoffsQuery({
|
||||
* variables: {
|
||||
* input: // value for 'input'
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useFindAgentHandoffsQuery(baseOptions: Apollo.QueryHookOptions<FindAgentHandoffsQuery, FindAgentHandoffsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<FindAgentHandoffsQuery, FindAgentHandoffsQueryVariables>(FindAgentHandoffsDocument, options);
|
||||
}
|
||||
export function useFindAgentHandoffsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindAgentHandoffsQuery, FindAgentHandoffsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<FindAgentHandoffsQuery, FindAgentHandoffsQueryVariables>(FindAgentHandoffsDocument, options);
|
||||
}
|
||||
export type FindAgentHandoffsQueryHookResult = ReturnType<typeof useFindAgentHandoffsQuery>;
|
||||
export type FindAgentHandoffsLazyQueryHookResult = ReturnType<typeof useFindAgentHandoffsLazyQuery>;
|
||||
export type FindAgentHandoffsQueryResult = Apollo.QueryResult<FindAgentHandoffsQuery, FindAgentHandoffsQueryVariables>;
|
||||
export const FindManyAgentsDocument = gql`
|
||||
query FindManyAgents {
|
||||
findManyAgents {
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
${AgentFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useFindManyAgentsQuery__
|
||||
*
|
||||
* To run a query within a React component, call `useFindManyAgentsQuery` and pass it any options that fit your needs.
|
||||
* When your component renders, `useFindManyAgentsQuery` 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 } = useFindManyAgentsQuery({
|
||||
* variables: {
|
||||
* },
|
||||
* });
|
||||
*/
|
||||
export function useFindManyAgentsQuery(baseOptions?: Apollo.QueryHookOptions<FindManyAgentsQuery, FindManyAgentsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useQuery<FindManyAgentsQuery, FindManyAgentsQueryVariables>(FindManyAgentsDocument, options);
|
||||
}
|
||||
export function useFindManyAgentsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindManyAgentsQuery, FindManyAgentsQueryVariables>) {
|
||||
const options = {...defaultOptions, ...baseOptions}
|
||||
return Apollo.useLazyQuery<FindManyAgentsQuery, FindManyAgentsQueryVariables>(FindManyAgentsDocument, options);
|
||||
}
|
||||
export type FindManyAgentsQueryHookResult = ReturnType<typeof useFindManyAgentsQuery>;
|
||||
export type FindManyAgentsLazyQueryHookResult = ReturnType<typeof useFindManyAgentsLazyQuery>;
|
||||
export type FindManyAgentsQueryResult = Apollo.QueryResult<FindManyAgentsQuery, FindManyAgentsQueryVariables>;
|
||||
export const FindOneAgentDocument = gql`
|
||||
query FindOneAgent($id: UUID!) {
|
||||
findOneAgent(input: {id: $id}) {
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
${AgentFieldsFragmentDoc}`;
|
||||
|
||||
/**
|
||||
* __useFindOneAgentQuery__
|
||||
*
|
||||
|
||||
@@ -53,7 +53,10 @@ export type Agent = {
|
||||
__typename?: 'Agent';
|
||||
createdAt: Scalars['DateTime'];
|
||||
description?: Maybe<Scalars['String']>;
|
||||
icon?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
isCustom: Scalars['Boolean'];
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
@@ -81,6 +84,13 @@ export type AgentChatThread = {
|
||||
updatedAt: Scalars['DateTime'];
|
||||
};
|
||||
|
||||
export type AgentHandoffDto = {
|
||||
__typename?: 'AgentHandoffDTO';
|
||||
description?: Maybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
toAgent: Agent;
|
||||
};
|
||||
|
||||
export type AgentIdInput = {
|
||||
/** The id of the agent. */
|
||||
id: Scalars['UUID'];
|
||||
@@ -473,6 +483,23 @@ export type CreateAgentChatThreadInput = {
|
||||
agentId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type CreateAgentHandoffInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
fromAgentId: Scalars['UUID'];
|
||||
toAgentId: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export type CreateAgentInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
name?: InputMaybe<Scalars['String']>;
|
||||
prompt: Scalars['String'];
|
||||
responseFormat?: InputMaybe<Scalars['JSON']>;
|
||||
roleId?: InputMaybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type CreateApiKeyDto = {
|
||||
expiresAt: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
@@ -1080,6 +1107,7 @@ export type Mutation = {
|
||||
checkoutSession: BillingSessionOutput;
|
||||
computeStepOutputSchema: Scalars['JSON'];
|
||||
createAgentChatThread: AgentChatThread;
|
||||
createAgentHandoff: Scalars['Boolean'];
|
||||
createApiKey: ApiKey;
|
||||
createApprovedAccessDomain: ApprovedAccessDomain;
|
||||
createDatabaseConfigVariable: Scalars['Boolean'];
|
||||
@@ -1087,6 +1115,7 @@ export type Mutation = {
|
||||
createFile: File;
|
||||
createOIDCIdentityProvider: SetupSsoOutput;
|
||||
createObjectEvent: Analytics;
|
||||
createOneAgent: Agent;
|
||||
createOneAppToken: AppToken;
|
||||
createOneField: Field;
|
||||
createOneObject: Object;
|
||||
@@ -1100,6 +1129,7 @@ export type Mutation = {
|
||||
deleteCurrentWorkspace: Workspace;
|
||||
deleteDatabaseConfigVariable: Scalars['Boolean'];
|
||||
deleteFile: File;
|
||||
deleteOneAgent: Agent;
|
||||
deleteOneField: Field;
|
||||
deleteOneObject: Object;
|
||||
deleteOneRole: Scalars['String'];
|
||||
@@ -1128,6 +1158,7 @@ export type Mutation = {
|
||||
initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioningOutput;
|
||||
publishServerlessFunction: ServerlessFunction;
|
||||
removeAgentHandoff: Scalars['Boolean'];
|
||||
removeRoleFromAgent: Scalars['Boolean'];
|
||||
renewToken: AuthTokens;
|
||||
resendEmailVerificationToken: ResendEmailVerificationTokenOutput;
|
||||
@@ -1215,6 +1246,11 @@ export type MutationCreateAgentChatThreadArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateAgentHandoffArgs = {
|
||||
input: CreateAgentHandoffInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateApiKeyArgs = {
|
||||
input: CreateApiKeyDto;
|
||||
};
|
||||
@@ -1254,6 +1290,11 @@ export type MutationCreateObjectEventArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOneAgentArgs = {
|
||||
input: CreateAgentInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationCreateOneFieldArgs = {
|
||||
input: CreateOneFieldMetadataInput;
|
||||
};
|
||||
@@ -1304,6 +1345,11 @@ export type MutationDeleteFileArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteOneAgentArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationDeleteOneFieldArgs = {
|
||||
input: DeleteOneFieldInput;
|
||||
};
|
||||
@@ -1432,6 +1478,11 @@ export type MutationPublishServerlessFunctionArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationRemoveAgentHandoffArgs = {
|
||||
input: RemoveAgentHandoffInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationRemoveRoleFromAgentArgs = {
|
||||
agentId: Scalars['UUID'];
|
||||
};
|
||||
@@ -1901,6 +1952,9 @@ export type Query = {
|
||||
currentWorkspace: Workspace;
|
||||
field: Field;
|
||||
fields: FieldConnection;
|
||||
findAgentHandoffTargets: Array<Agent>;
|
||||
findAgentHandoffs: Array<AgentHandoffDto>;
|
||||
findManyAgents: Array<Agent>;
|
||||
findManyServerlessFunctions: Array<ServerlessFunction>;
|
||||
findOneAgent: Agent;
|
||||
findOneServerlessFunction: ServerlessFunction;
|
||||
@@ -1975,6 +2029,16 @@ export type QueryCheckWorkspaceInviteHashIsValidArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindAgentHandoffTargetsArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindAgentHandoffsArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
|
||||
|
||||
export type QueryFindOneAgentArgs = {
|
||||
input: AgentIdInput;
|
||||
};
|
||||
@@ -2159,6 +2223,11 @@ export enum RemoteTableStatus {
|
||||
SYNCED = 'SYNCED'
|
||||
}
|
||||
|
||||
export type RemoveAgentHandoffInput = {
|
||||
fromAgentId: Scalars['String'];
|
||||
toAgentId: Scalars['String'];
|
||||
};
|
||||
|
||||
export type ResendEmailVerificationTokenOutput = {
|
||||
__typename?: 'ResendEmailVerificationTokenOutput';
|
||||
success: Scalars['Boolean'];
|
||||
@@ -2513,11 +2582,14 @@ export type UuidFilterComparison = {
|
||||
|
||||
export type UpdateAgentInput = {
|
||||
description?: InputMaybe<Scalars['String']>;
|
||||
icon?: InputMaybe<Scalars['String']>;
|
||||
id: Scalars['UUID'];
|
||||
label: Scalars['String'];
|
||||
modelId: Scalars['String'];
|
||||
name: Scalars['String'];
|
||||
prompt: Scalars['String'];
|
||||
responseFormat?: InputMaybe<Scalars['JSON']>;
|
||||
roleId?: InputMaybe<Scalars['UUID']>;
|
||||
};
|
||||
|
||||
export type UpdateApiKeyDto = {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const AGENT_FRAGMENT = gql`
|
||||
fragment AgentFields on Agent {
|
||||
id
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
prompt
|
||||
modelId
|
||||
responseFormat
|
||||
roleId
|
||||
isCustom
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const CREATE_AGENT_HANDOFF = gql`
|
||||
mutation CreateAgentHandoff($input: CreateAgentHandoffInput!) {
|
||||
createAgentHandoff(input: $input)
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { AGENT_FRAGMENT } from '../fragments/agentFragment';
|
||||
|
||||
export const CREATE_ONE_AGENT = gql`
|
||||
${AGENT_FRAGMENT}
|
||||
mutation CreateOneAgent($input: CreateAgentInput!) {
|
||||
createOneAgent(input: $input) {
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { AGENT_FRAGMENT } from '../fragments/agentFragment';
|
||||
|
||||
export const DELETE_ONE_AGENT = gql`
|
||||
${AGENT_FRAGMENT}
|
||||
mutation DeleteOneAgent($input: AgentIdInput!) {
|
||||
deleteOneAgent(input: $input) {
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const REMOVE_AGENT_HANDOFF = gql`
|
||||
mutation RemoveAgentHandoff($input: RemoveAgentHandoffInput!) {
|
||||
removeAgentHandoff(input: $input)
|
||||
}
|
||||
`;
|
||||
@@ -1,14 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { AGENT_FRAGMENT } from '../fragments/agentFragment';
|
||||
|
||||
export const UPDATE_ONE_AGENT = gql`
|
||||
${AGENT_FRAGMENT}
|
||||
mutation UpdateOneAgent($input: UpdateAgentInput!) {
|
||||
updateOneAgent(input: $input) {
|
||||
id
|
||||
name
|
||||
description
|
||||
prompt
|
||||
modelId
|
||||
responseFormat
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const FIND_AGENT_HANDOFF_TARGETS = gql`
|
||||
query FindAgentHandoffTargets($input: AgentIdInput!) {
|
||||
findAgentHandoffTargets(input: $input) {
|
||||
id
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
modelId
|
||||
prompt
|
||||
isCustom
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const FIND_AGENT_HANDOFFS = gql`
|
||||
query FindAgentHandoffs($input: AgentIdInput!) {
|
||||
findAgentHandoffs(input: $input) {
|
||||
id
|
||||
description
|
||||
toAgent {
|
||||
id
|
||||
name
|
||||
label
|
||||
description
|
||||
icon
|
||||
modelId
|
||||
prompt
|
||||
isCustom
|
||||
createdAt
|
||||
updatedAt
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -0,0 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { AGENT_FRAGMENT } from '../fragments/agentFragment';
|
||||
|
||||
export const FIND_MANY_AGENTS = gql`
|
||||
${AGENT_FRAGMENT}
|
||||
query FindManyAgents {
|
||||
findManyAgents {
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -1,15 +1,11 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import { AGENT_FRAGMENT } from '../fragments/agentFragment';
|
||||
|
||||
export const FIND_ONE_AGENT = gql`
|
||||
${AGENT_FRAGMENT}
|
||||
query FindOneAgent($id: UUID!) {
|
||||
findOneAgent(input: { id: $id }) {
|
||||
id
|
||||
name
|
||||
description
|
||||
prompt
|
||||
modelId
|
||||
responseFormat
|
||||
roleId
|
||||
...AgentFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { workflowAiAgentSelectedRoleState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/workflowAiAgentSelectedRoleState';
|
||||
import { useRecoilState } from 'recoil';
|
||||
import {
|
||||
useAssignRoleToAgentMutation,
|
||||
useFindOneAgentQuery,
|
||||
useGetRolesQuery,
|
||||
useRemoveRoleFromAgentMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const useAgentRoleAssignment = (agentId: string) => {
|
||||
const [workflowAiAgentSelectedRole, setWorkflowAiAgentSelectedRole] =
|
||||
useRecoilState(workflowAiAgentSelectedRoleState);
|
||||
|
||||
useFindOneAgentQuery({
|
||||
variables: { id: agentId },
|
||||
skip: !agentId,
|
||||
onCompleted: (data) => {
|
||||
setWorkflowAiAgentSelectedRole(data.findOneAgent.roleId);
|
||||
},
|
||||
});
|
||||
|
||||
const { data: rolesData } = useGetRolesQuery();
|
||||
const [assignRoleToAgent] = useAssignRoleToAgentMutation();
|
||||
const [removeRoleFromAgent] = useRemoveRoleFromAgentMutation();
|
||||
|
||||
const handleRoleChange = async (roleId: string) => {
|
||||
if (roleId === '') {
|
||||
await handleRoleRemove();
|
||||
} else {
|
||||
setWorkflowAiAgentSelectedRole(roleId);
|
||||
await assignRoleToAgent({ variables: { agentId, roleId } });
|
||||
}
|
||||
};
|
||||
|
||||
const handleRoleRemove = async () => {
|
||||
setWorkflowAiAgentSelectedRole(undefined);
|
||||
await removeRoleFromAgent({ variables: { agentId } });
|
||||
};
|
||||
|
||||
const rolesOptions =
|
||||
rolesData?.getRoles?.map((role) => ({
|
||||
label: role.label,
|
||||
value: role.id,
|
||||
})) || [];
|
||||
|
||||
return {
|
||||
selectedRole: workflowAiAgentSelectedRole,
|
||||
handleRoleChange,
|
||||
rolesOptions,
|
||||
};
|
||||
};
|
||||
@@ -1,83 +0,0 @@
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import {
|
||||
useFindOneAgentQuery,
|
||||
useUpdateOneAgentMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
type AgentFormValues = {
|
||||
name: string;
|
||||
prompt: string;
|
||||
modelId: string;
|
||||
};
|
||||
|
||||
export const useAgentUpdateFormState = ({
|
||||
agentId,
|
||||
readonly = false,
|
||||
}: {
|
||||
agentId: string;
|
||||
readonly?: boolean;
|
||||
}) => {
|
||||
const [formValues, setFormValues] = useState<AgentFormValues>({
|
||||
name: '',
|
||||
prompt: '',
|
||||
modelId: '',
|
||||
});
|
||||
|
||||
const { loading } = useFindOneAgentQuery({
|
||||
variables: { id: agentId },
|
||||
skip: !agentId,
|
||||
onCompleted: (data) => {
|
||||
if (isDefined(data?.findOneAgent)) {
|
||||
const agent = data.findOneAgent;
|
||||
setFormValues({
|
||||
name: agent.name,
|
||||
prompt: agent.prompt,
|
||||
modelId: agent.modelId,
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const [updateAgent] = useUpdateOneAgentMutation();
|
||||
|
||||
const updateAgentMutation = async (updates: AgentFormValues) => {
|
||||
if (!agentId) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateAgent({
|
||||
variables: {
|
||||
input: {
|
||||
id: agentId,
|
||||
...updates,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleSave = useDebouncedCallback(async (formData: AgentFormValues) => {
|
||||
await updateAgentMutation({
|
||||
name: formData.name,
|
||||
prompt: formData.prompt,
|
||||
modelId: formData.modelId,
|
||||
});
|
||||
}, 500);
|
||||
|
||||
const handleFieldChange = async (field: string, value: string) => {
|
||||
if (readonly) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFormValues((prev) => ({ ...prev, [field]: value }));
|
||||
|
||||
await handleSave({ ...formValues, [field]: value });
|
||||
};
|
||||
|
||||
return {
|
||||
formValues,
|
||||
handleFieldChange,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
@@ -146,6 +146,18 @@ const SettingsWorkspace = lazy(() =>
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsAI = lazy(() =>
|
||||
import('~/pages/settings/ai/SettingsAI').then((module) => ({
|
||||
default: module.SettingsAI,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsAgentForm = lazy(() =>
|
||||
import('~/pages/settings/ai/SettingsAgentForm').then((module) => ({
|
||||
default: module.SettingsAgentForm,
|
||||
})),
|
||||
);
|
||||
|
||||
const SettingsDomain = lazy(() =>
|
||||
import('~/pages/settings/workspace/SettingsDomain').then((module) => ({
|
||||
default: module.SettingsDomain,
|
||||
@@ -410,6 +422,15 @@ export const SettingsRoutes = ({
|
||||
}
|
||||
>
|
||||
<Route path={SettingsPath.Workspace} element={<SettingsWorkspace />} />
|
||||
<Route path={SettingsPath.AI} element={<SettingsAI />} />
|
||||
<Route
|
||||
path={SettingsPath.AINewAgent}
|
||||
element={<SettingsAgentForm mode="create" />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.AIAgentDetail}
|
||||
element={<SettingsAgentForm mode="edit" />}
|
||||
/>
|
||||
<Route path={SettingsPath.Billing} element={<SettingsBilling />} />
|
||||
<Route path={SettingsPath.Domain} element={<SettingsDomain />} />
|
||||
</Route>
|
||||
|
||||
@@ -38,6 +38,7 @@ export const SettingsNavigationDrawerItem = ({
|
||||
Icon={item.Icon}
|
||||
active={isActive}
|
||||
soon={item.soon}
|
||||
isNew={item.isNew}
|
||||
onClick={item.onClick}
|
||||
/>
|
||||
</AdvancedSettingsWrapper>
|
||||
@@ -53,6 +54,7 @@ export const SettingsNavigationDrawerItem = ({
|
||||
Icon={item.Icon}
|
||||
active={isActive}
|
||||
soon={item.soon}
|
||||
isNew={item.isNew}
|
||||
onClick={item.onClick}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,7 @@ import { billingState } from '@/client-config/states/billingState';
|
||||
import { labPublicFeatureFlagsState } from '@/client-config/states/labPublicFeatureFlagsState';
|
||||
import { usePermissionFlagMap } from '@/settings/roles/hooks/usePermissionFlagMap';
|
||||
import { NavigationDrawerItemIndentationLevel } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerItem';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import {
|
||||
@@ -26,11 +27,12 @@ import {
|
||||
IconRocket,
|
||||
IconServer,
|
||||
IconSettings,
|
||||
IconSparkles,
|
||||
IconUserCircle,
|
||||
IconUsers,
|
||||
IconWebhook,
|
||||
} from 'twenty-ui/display';
|
||||
import { PermissionFlagType } from '~/generated/graphql';
|
||||
import { FeatureFlagKey, PermissionFlagType } from '~/generated/graphql';
|
||||
|
||||
export type SettingsNavigationSection = {
|
||||
label: string;
|
||||
@@ -49,6 +51,7 @@ export type SettingsNavigationItem = {
|
||||
subItems?: SettingsNavigationItem[];
|
||||
isAdvanced?: boolean;
|
||||
soon?: boolean;
|
||||
isNew?: boolean;
|
||||
};
|
||||
|
||||
const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
@@ -62,6 +65,7 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
(currentUser?.canImpersonate || currentUser?.canAccessFullAdminPanel) ??
|
||||
false;
|
||||
const labPublicFeatureFlags = useRecoilValue(labPublicFeatureFlagsState);
|
||||
const isAIEnabled = useIsFeatureEnabled(FeatureFlagKey.IS_AI_ENABLED);
|
||||
|
||||
const permissionMap = usePermissionFlagMap();
|
||||
return [
|
||||
@@ -140,6 +144,14 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => {
|
||||
Icon: IconApps,
|
||||
isHidden: !permissionMap[PermissionFlagType.API_KEYS_AND_WEBHOOKS],
|
||||
},
|
||||
{
|
||||
label: t`AI`,
|
||||
path: SettingsPath.AI,
|
||||
Icon: IconSparkles,
|
||||
isHidden:
|
||||
!isAIEnabled || !permissionMap[PermissionFlagType.WORKSPACE],
|
||||
isNew: true,
|
||||
},
|
||||
{
|
||||
label: t`Security`,
|
||||
path: SettingsPath.Security,
|
||||
|
||||
@@ -22,6 +22,9 @@ export enum SettingsPath {
|
||||
WorkspaceMembersPage = 'members',
|
||||
Workspace = 'general',
|
||||
Domain = 'general/domain',
|
||||
AI = 'ai',
|
||||
AINewAgent = 'ai/new-agent',
|
||||
AIAgentDetail = 'ai/agents/:agentId',
|
||||
APIs = 'apis',
|
||||
RestPlayground = 'playground/rest/:schema',
|
||||
GraphQLPlayground = 'playground/graphql/:schema',
|
||||
|
||||
+11
-1
@@ -33,6 +33,7 @@ export type NavigationDrawerItemProps = {
|
||||
active?: boolean;
|
||||
danger?: boolean;
|
||||
soon?: boolean;
|
||||
isNew?: boolean;
|
||||
count?: number;
|
||||
keyboard?: string[];
|
||||
rightOptions?: ReactNode;
|
||||
@@ -247,6 +248,7 @@ export const NavigationDrawerItem = ({
|
||||
active,
|
||||
danger,
|
||||
soon,
|
||||
isNew,
|
||||
count,
|
||||
keyboard,
|
||||
subItemState,
|
||||
@@ -262,7 +264,9 @@ export const NavigationDrawerItem = ({
|
||||
const [isNavigationDrawerExpanded, setIsNavigationDrawerExpanded] =
|
||||
useRecoilState(isNavigationDrawerExpandedState);
|
||||
const showBreadcrumb = indentationLevel === 2;
|
||||
const showStyledSpacer = !!soon || !!count || !!keyboard || !!rightOptions;
|
||||
const showStyledSpacer = Boolean(
|
||||
soon || isNew || count || keyboard || rightOptions,
|
||||
);
|
||||
|
||||
const handleMobileNavigation = () => {
|
||||
if (isMobile) {
|
||||
@@ -343,6 +347,12 @@ export const NavigationDrawerItem = ({
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
)}
|
||||
|
||||
{isNew && (
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<Pill label="New" />
|
||||
</NavigationDrawerAnimatedCollapseWrapper>
|
||||
)}
|
||||
|
||||
{!!count && (
|
||||
<NavigationDrawerAnimatedCollapseWrapper>
|
||||
<StyledItemCount>{count}</StyledItemCount>
|
||||
|
||||
+51
-9
@@ -1,12 +1,12 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { IconSearch } from 'twenty-ui/display';
|
||||
import { CatalogDecorator, CatalogStory } from 'twenty-ui/testing';
|
||||
import { getOsControlSymbol } from 'twenty-ui/utilities';
|
||||
import { ComponentWithRouterDecorator } from '~/testing/decorators/ComponentWithRouterDecorator';
|
||||
import { MemoryRouterDecorator } from '~/testing/decorators/MemoryRouterDecorator';
|
||||
import { NavigationDrawerItem } from '../NavigationDrawerItem';
|
||||
import { CatalogDecorator, CatalogStory } from 'twenty-ui/testing';
|
||||
import { IconSearch } from 'twenty-ui/display';
|
||||
import { getOsControlSymbol } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -96,6 +96,40 @@ export const Breadcrumb: Story = {
|
||||
],
|
||||
};
|
||||
|
||||
export const NewPill: Story = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<StyledContainer>
|
||||
<h1>New Pill Examples</h1>
|
||||
<Story
|
||||
args={{
|
||||
label: 'New Feature',
|
||||
Icon: IconSearch,
|
||||
isNew: true,
|
||||
}}
|
||||
/>
|
||||
<Story
|
||||
args={{
|
||||
label: 'New Feature with Count',
|
||||
Icon: IconSearch,
|
||||
isNew: true,
|
||||
count: 5,
|
||||
}}
|
||||
/>
|
||||
<Story
|
||||
args={{
|
||||
label: 'New Feature with Keyboard Shortcut',
|
||||
Icon: IconSearch,
|
||||
isNew: true,
|
||||
keyboard: [getOsControlSymbol(), 'N'],
|
||||
}}
|
||||
/>
|
||||
</StyledContainer>
|
||||
),
|
||||
ComponentWithRouterDecorator,
|
||||
],
|
||||
};
|
||||
|
||||
export const BreadcrumbCatalog: CatalogStory<
|
||||
Story,
|
||||
typeof NavigationDrawerItem
|
||||
@@ -182,15 +216,23 @@ export const Catalog: CatalogStory<Story, typeof NavigationDrawerItem> = {
|
||||
},
|
||||
{
|
||||
name: 'adornments',
|
||||
values: ['Without Adornments', 'Soon Pill', 'Count', 'Keyboard Keys'],
|
||||
values: [
|
||||
'Without Adornments',
|
||||
'Soon Pill',
|
||||
'New Pill',
|
||||
'Count',
|
||||
'Keyboard Keys',
|
||||
],
|
||||
props: (adornmentName: string) =>
|
||||
adornmentName === 'Soon Pill'
|
||||
? { soon: true }
|
||||
: adornmentName === 'Count'
|
||||
? { count: 3 }
|
||||
: adornmentName === 'Keyboard Keys'
|
||||
? { keyboard: [getOsControlSymbol(), 'K'] }
|
||||
: {},
|
||||
: adornmentName === 'New Pill'
|
||||
? { isNew: true }
|
||||
: adornmentName === 'Count'
|
||||
? { count: 3 }
|
||||
: adornmentName === 'Keyboard Keys'
|
||||
? { keyboard: [getOsControlSymbol(), 'K'] }
|
||||
: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { z } from 'zod';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
import { z } from 'zod';
|
||||
|
||||
// Base schemas
|
||||
export const objectRecordSchema = z.record(z.any());
|
||||
@@ -135,7 +135,8 @@ export const workflowHttpRequestActionSettingsSchema =
|
||||
export const workflowAiAgentActionSettingsSchema =
|
||||
baseWorkflowActionSettingsSchema.extend({
|
||||
input: z.object({
|
||||
agentId: z.string(),
|
||||
agentId: z.string().optional(),
|
||||
prompt: z.string().optional(),
|
||||
}),
|
||||
});
|
||||
|
||||
|
||||
+88
-120
@@ -1,32 +1,20 @@
|
||||
import { useAiAgentOutputSchema } from '@/ai/hooks/useAiAgentOutputSchema';
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||
import { WorkflowAiAgentAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||
import { AIChatTab } from '@/ai/components/AIChatTab';
|
||||
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
||||
import styled from '@emotion/styled';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
IconMessage,
|
||||
IconSettings,
|
||||
IconTool,
|
||||
useIcons,
|
||||
} from 'twenty-ui/display';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
import { useFindManyAgentsQuery } from '~/generated-metadata/graphql';
|
||||
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
|
||||
import {
|
||||
WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID,
|
||||
WorkflowAiAgentTabId,
|
||||
} from '../constants/workflow-ai-agent-tabs';
|
||||
import { useAgentRoleAssignment } from '@/ai/hooks/useAgentRoleAssignment';
|
||||
import { useAgentUpdateFormState } from '@/ai/hooks/useAgentUpdateFormState';
|
||||
import { useAiAgentOutputSchema } from '@/ai/hooks/useAiAgentOutputSchema';
|
||||
import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions';
|
||||
import { WorkflowOutputSchemaBuilder } from './WorkflowOutputSchemaBuilder';
|
||||
|
||||
const StyledErrorMessage = styled.div`
|
||||
@@ -36,11 +24,6 @@ const StyledErrorMessage = styled.div`
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledTabList = styled(TabList)`
|
||||
background-color: ${({ theme }) => theme.background.secondary};
|
||||
padding-left: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
type WorkflowEditActionAiAgentProps = {
|
||||
action: WorkflowAiAgentAction;
|
||||
actionOptions:
|
||||
@@ -55,6 +38,7 @@ export const WorkflowEditActionAiAgent = ({
|
||||
action,
|
||||
actionOptions,
|
||||
}: WorkflowEditActionAiAgentProps) => {
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
const { getIcon } = useIcons();
|
||||
const { headerTitle, headerIcon, headerIconColor, headerType } =
|
||||
useWorkflowActionHeader({
|
||||
@@ -62,13 +46,6 @@ export const WorkflowEditActionAiAgent = ({
|
||||
defaultTitle: 'AI Agent',
|
||||
});
|
||||
|
||||
const agentId = action.settings.input.agentId;
|
||||
|
||||
const { formValues, handleFieldChange, loading } = useAgentUpdateFormState({
|
||||
agentId,
|
||||
readonly: actionOptions.readonly === true,
|
||||
});
|
||||
|
||||
const { handleOutputSchemaChange, outputFields } = useAiAgentOutputSchema(
|
||||
action.settings.outputSchema as BaseOutputSchema,
|
||||
actionOptions.readonly === true ? undefined : actionOptions.onActionUpdate,
|
||||
@@ -76,107 +53,98 @@ export const WorkflowEditActionAiAgent = ({
|
||||
actionOptions.readonly,
|
||||
);
|
||||
|
||||
const modelOptions = useAiModelOptions();
|
||||
const { data: agentsData, loading: agentsLoading } = useFindManyAgentsQuery();
|
||||
|
||||
const noModelsAvailable = modelOptions.length === 0;
|
||||
|
||||
const activeTabId = useRecoilComponentValueV2(
|
||||
activeTabIdComponentState,
|
||||
WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID,
|
||||
const agentOptions = (agentsData?.findManyAgents || []).reduce<
|
||||
SelectOption<string>[]
|
||||
>(
|
||||
(acc, agent) => {
|
||||
if (agent.id !== currentWorkspace?.defaultAgent?.id) {
|
||||
acc.push({
|
||||
label: agent.label,
|
||||
value: agent.id,
|
||||
Icon: agent.icon ? getIcon(agent.icon) : undefined,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
},
|
||||
[
|
||||
{
|
||||
label: t`No Agent`,
|
||||
value: '',
|
||||
},
|
||||
],
|
||||
);
|
||||
|
||||
const { rolesOptions, selectedRole, handleRoleChange } =
|
||||
useAgentRoleAssignment(agentId);
|
||||
const noAgentsAvailable = agentOptions.length === 0;
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
id: WorkflowAiAgentTabId.SETTINGS,
|
||||
title: t`Settings`,
|
||||
Icon: IconSettings,
|
||||
},
|
||||
{ id: WorkflowAiAgentTabId.TOOLS, title: t`Tools`, Icon: IconTool },
|
||||
{ id: WorkflowAiAgentTabId.CHAT, title: t`Chat`, Icon: IconMessage },
|
||||
];
|
||||
const handleFieldChange = (field: 'agentId' | 'prompt', value: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
actionOptions.onActionUpdate?.({
|
||||
...action,
|
||||
settings: {
|
||||
...action.settings,
|
||||
input: {
|
||||
...action.settings.input,
|
||||
[field]: value,
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
return agentsLoading ? (
|
||||
<RightDrawerSkeletonLoader />
|
||||
) : (
|
||||
<>
|
||||
<StyledTabList
|
||||
tabs={tabs}
|
||||
behaveAsLinks={false}
|
||||
componentInstanceId={WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID}
|
||||
<WorkflowStepHeader
|
||||
onTitleChange={(newName: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
actionOptions.onActionUpdate?.({ ...action, name: newName });
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType={headerType}
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
{activeTabId === WorkflowAiAgentTabId.CHAT ? (
|
||||
<AIChatTab agentId={agentId} isWorkflowAgentNodeChat />
|
||||
) : (
|
||||
<>
|
||||
<WorkflowStepHeader
|
||||
onTitleChange={(newName: string) => {
|
||||
if (actionOptions.readonly === true) {
|
||||
return;
|
||||
}
|
||||
actionOptions.onActionUpdate?.({ ...action, name: newName });
|
||||
}}
|
||||
Icon={getIcon(headerIcon)}
|
||||
iconColor={headerIconColor}
|
||||
initialTitle={headerTitle}
|
||||
headerType={headerType}
|
||||
disabled={actionOptions.readonly}
|
||||
<WorkflowStepBody>
|
||||
<div>
|
||||
<Select
|
||||
dropdownId="select-agent"
|
||||
label={t`Select Agent`}
|
||||
options={agentOptions}
|
||||
value={action.settings.input.agentId || ''}
|
||||
onChange={(value) => handleFieldChange('agentId', value)}
|
||||
disabled={actionOptions.readonly || noAgentsAvailable}
|
||||
/>
|
||||
<WorkflowStepBody>
|
||||
{activeTabId === WorkflowAiAgentTabId.SETTINGS && (
|
||||
<>
|
||||
<div>
|
||||
<Select
|
||||
dropdownId="select-model"
|
||||
label={t`AI Model`}
|
||||
options={modelOptions}
|
||||
value={formValues.modelId}
|
||||
onChange={(value) => handleFieldChange('modelId', value)}
|
||||
disabled={actionOptions.readonly || noModelsAvailable}
|
||||
emptyOption={{
|
||||
label: t`Auto`,
|
||||
value: '',
|
||||
}}
|
||||
/>
|
||||
|
||||
{noModelsAvailable && (
|
||||
<StyledErrorMessage>
|
||||
{t`You haven't configured any model provider. Please set up an API Key in your instance's admin panel or as an environment variable.`}
|
||||
</StyledErrorMessage>
|
||||
)}
|
||||
</div>
|
||||
<FormTextFieldInput
|
||||
key={`prompt-${formValues.modelId ? action.id : 'empty'}`}
|
||||
label={t`Instructions for AI`}
|
||||
placeholder={t`Describe what you want the AI to do...`}
|
||||
readonly={actionOptions.readonly}
|
||||
defaultValue={formValues.prompt}
|
||||
onChange={(value) => handleFieldChange('prompt', value)}
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
multiline
|
||||
/>
|
||||
<WorkflowOutputSchemaBuilder
|
||||
fields={outputFields}
|
||||
onChange={handleOutputSchemaChange}
|
||||
readonly={actionOptions.readonly}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{activeTabId === WorkflowAiAgentTabId.TOOLS && (
|
||||
<Select
|
||||
dropdownId="select-agent-role"
|
||||
label={t`Assign Role`}
|
||||
options={[{ label: t`No role`, value: '' }, ...rolesOptions]}
|
||||
value={selectedRole || ''}
|
||||
onChange={handleRoleChange}
|
||||
disabled={actionOptions.readonly}
|
||||
/>
|
||||
)}
|
||||
</WorkflowStepBody>
|
||||
</>
|
||||
)}
|
||||
{noAgentsAvailable && (
|
||||
<StyledErrorMessage>
|
||||
{t`Please create agents in the AI settings to use in workflows.`}
|
||||
</StyledErrorMessage>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<FormTextFieldInput
|
||||
multiline
|
||||
VariablePicker={WorkflowVariablePicker}
|
||||
label={t`Instructions for AI`}
|
||||
placeholder={t`Describe what you want the AI to do...`}
|
||||
defaultValue={action.settings.input.prompt}
|
||||
onChange={(value) => handleFieldChange('prompt', value)}
|
||||
readonly={actionOptions.readonly}
|
||||
/>
|
||||
|
||||
<WorkflowOutputSchemaBuilder
|
||||
fields={outputFields}
|
||||
onChange={handleOutputSchemaChange}
|
||||
readonly={actionOptions.readonly}
|
||||
/>
|
||||
</WorkflowStepBody>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
-8
@@ -1,8 +0,0 @@
|
||||
export enum WorkflowAiAgentTabId {
|
||||
SETTINGS = 'settings',
|
||||
TOOLS = 'tools',
|
||||
CHAT = 'chat',
|
||||
}
|
||||
|
||||
export const WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID =
|
||||
'WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID';
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
export const workflowAiAgentSelectedRoleState = atom<string | undefined>({
|
||||
key: 'workflowAiAgentSelectedRoleState',
|
||||
default: undefined,
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { H2Title, IconPlus } 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 { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { SettingsAIAgentsTable } from './components/SettingsAIAgentsTable';
|
||||
|
||||
export const SettingsAI = () => {
|
||||
const { data } = useFindManyAgentsQuery();
|
||||
|
||||
return (
|
||||
<SubMenuTopBarContainer
|
||||
title={t`AI`}
|
||||
actionButton={
|
||||
<UndecoratedLink to={getSettingsPath(SettingsPath.AINewAgent)}>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`New Agent`}
|
||||
accent="blue"
|
||||
size="small"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: t`AI` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Agents`}
|
||||
description={t`Agents used to route queries to specialized agents`}
|
||||
/>
|
||||
<SettingsAIAgentsTable agents={data?.findManyAgents || []} />
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,244 @@
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import styled from '@emotion/styled';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
|
||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { AppPath } from '@/types/AppPath';
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { H2Title, IconTrash } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import {
|
||||
CreateAgentInput,
|
||||
useCreateOneAgentMutation,
|
||||
useFindOneAgentQuery,
|
||||
useUpdateOneAgentMutation,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { useNavigateApp } from '~/hooks/useNavigateApp';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import { SettingsAgentDeleteConfirmationModal } from './components/SettingsAgentDeleteConfirmationModal';
|
||||
import { SettingsAgentDetailSkeletonLoader } from './components/SettingsAgentDetailSkeletonLoader';
|
||||
import { SettingsAgentHandoffSection } from './components/SettingsAgentHandoffSection';
|
||||
import { SettingsAIAgentForm } from './forms/components/SettingsAIAgentForm';
|
||||
import { useSettingsAgentFormState } from './hooks/useSettingsAgentFormState';
|
||||
|
||||
const StyledContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(8)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const DELETE_AGENT_MODAL_ID = 'delete-agent-modal';
|
||||
|
||||
export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => {
|
||||
const { agentId = '' } = useParams<{ agentId: string }>();
|
||||
const navigate = useNavigateSettings();
|
||||
const navigateApp = useNavigateApp();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const { openModal } = useModal();
|
||||
const currentWorkspace = useRecoilValue(currentWorkspaceState);
|
||||
|
||||
const isEditMode = mode === 'edit';
|
||||
|
||||
const {
|
||||
formValues,
|
||||
isSubmitting,
|
||||
handleFieldChange,
|
||||
resetForm,
|
||||
setIsSubmitting,
|
||||
validateForm,
|
||||
} = useSettingsAgentFormState(mode);
|
||||
|
||||
const { data, loading } = useFindOneAgentQuery({
|
||||
variables: { id: agentId },
|
||||
skip: !isEditMode || !agentId,
|
||||
onCompleted: (data) => {
|
||||
const agent = data?.findOneAgent;
|
||||
if (isDefined(agent)) {
|
||||
resetForm({
|
||||
name: agent.name,
|
||||
label: agent.label,
|
||||
description: agent.description,
|
||||
icon: agent.icon || 'IconRobot',
|
||||
modelId: agent.modelId,
|
||||
role: agent.roleId,
|
||||
prompt: agent.prompt,
|
||||
isCustom: agent.isCustom,
|
||||
});
|
||||
} else {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Agent not found`,
|
||||
});
|
||||
navigateApp(AppPath.NotFound);
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error,
|
||||
});
|
||||
navigateApp(AppPath.NotFound);
|
||||
},
|
||||
});
|
||||
|
||||
const [createAgent] = useCreateOneAgentMutation();
|
||||
const [updateAgent] = useUpdateOneAgentMutation();
|
||||
|
||||
const agent = data?.findOneAgent;
|
||||
|
||||
const isAskAIAgent = agent?.id === currentWorkspace?.defaultAgent?.id;
|
||||
|
||||
if (isEditMode && !loading && !agent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const canSave = validateForm() && !isSubmitting;
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!validateForm()) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
try {
|
||||
if (!isEditMode) {
|
||||
const input: CreateAgentInput = {
|
||||
name: formValues.name,
|
||||
label: formValues.label,
|
||||
description: formValues.description,
|
||||
icon: formValues.icon,
|
||||
modelId: formValues.modelId,
|
||||
roleId: formValues.role || undefined,
|
||||
prompt: formValues.prompt,
|
||||
};
|
||||
|
||||
await createAgent({
|
||||
variables: { input },
|
||||
});
|
||||
navigate(SettingsPath.AI);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!agent) {
|
||||
return;
|
||||
}
|
||||
|
||||
await updateAgent({
|
||||
variables: {
|
||||
input: {
|
||||
id: agent.id,
|
||||
name: formValues.name || '',
|
||||
label: formValues.label,
|
||||
description: formValues.description,
|
||||
icon: formValues.icon,
|
||||
modelId: formValues.modelId,
|
||||
roleId: formValues.role || undefined,
|
||||
prompt: formValues.prompt,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
navigate(SettingsPath.AI);
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error instanceof ApolloError ? error : undefined,
|
||||
});
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
const title = isEditMode ? (loading ? t`Agent` : agent?.label) : t`New Agent`;
|
||||
const pageTitle = isEditMode ? t`Edit Agent` : t`New Agent`;
|
||||
const pageDescription = isEditMode
|
||||
? t`Update agent information`
|
||||
: t`Create a new AI agent`;
|
||||
const breadcrumbText = isEditMode
|
||||
? loading
|
||||
? t`Agent`
|
||||
: agent?.label
|
||||
: t`New Agent`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<SubMenuTopBarContainer
|
||||
title={title}
|
||||
actionButton={
|
||||
!isEditMode || !isAskAIAgent ? (
|
||||
<SaveAndCancelButtons
|
||||
onSave={handleSave}
|
||||
onCancel={() => navigate(SettingsPath.AI)}
|
||||
isSaveDisabled={!canSave}
|
||||
isLoading={isSubmitting}
|
||||
isCancelDisabled={isSubmitting}
|
||||
/>
|
||||
) : undefined
|
||||
}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.Workspace),
|
||||
},
|
||||
{ children: t`AI`, href: getSettingsPath(SettingsPath.AI) },
|
||||
{ children: breadcrumbText },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<Section>
|
||||
{!isAskAIAgent && (
|
||||
<H2Title title={pageTitle} description={pageDescription} />
|
||||
)}
|
||||
{isEditMode && loading ? (
|
||||
<SettingsAgentDetailSkeletonLoader />
|
||||
) : (
|
||||
<StyledContentContainer>
|
||||
{isAskAIAgent ? (
|
||||
<SettingsAgentHandoffSection agentId={agent?.id} />
|
||||
) : (
|
||||
<>
|
||||
<SettingsAIAgentForm
|
||||
formValues={formValues}
|
||||
onFieldChange={handleFieldChange}
|
||||
/>
|
||||
{isEditMode && agent && formValues.isCustom && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Danger zone`}
|
||||
description={t`Delete this agent`}
|
||||
/>
|
||||
<Button
|
||||
accent="danger"
|
||||
variant="secondary"
|
||||
title={t`Delete Agent`}
|
||||
Icon={IconTrash}
|
||||
onClick={() => openModal(DELETE_AGENT_MODAL_ID)}
|
||||
/>
|
||||
</Section>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledContentContainer>
|
||||
)}
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
{isEditMode && agent && (
|
||||
<SettingsAgentDeleteConfirmationModal
|
||||
agentId={agent.id}
|
||||
agentName={agent.label}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { Tag } from 'twenty-ui/components';
|
||||
import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
|
||||
import { Agent } from '~/generated-metadata/graphql';
|
||||
|
||||
export type SettingsAIAgentTableRowProps = {
|
||||
action: ReactNode;
|
||||
agent: Agent;
|
||||
link?: string;
|
||||
};
|
||||
|
||||
export const StyledAIAgentTableRow = styled(TableRow)`
|
||||
grid-template-columns: 1fr 120px 36px;
|
||||
`;
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.primary};
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledActionTableCell = styled(TableCell)`
|
||||
justify-content: flex-end;
|
||||
padding-right: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
export const SettingsAIAgentTableRow = ({
|
||||
action,
|
||||
agent,
|
||||
link,
|
||||
}: SettingsAIAgentTableRowProps) => {
|
||||
const theme = useTheme();
|
||||
const { getIcon } = useIcons();
|
||||
const Icon = getIcon(agent.icon || 'IconRobot');
|
||||
|
||||
return (
|
||||
<StyledAIAgentTableRow key={agent.id} to={link}>
|
||||
<StyledNameTableCell>
|
||||
<StyledIconContainer>
|
||||
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
</StyledIconContainer>
|
||||
<OverflowingTextWithTooltip text={agent.label} />
|
||||
</StyledNameTableCell>
|
||||
<TableCell>
|
||||
<Tag
|
||||
text={agent.isCustom ? 'Custom' : 'Standard'}
|
||||
color={agent.isCustom ? 'orange' : 'blue'}
|
||||
weight="medium"
|
||||
/>
|
||||
</TableCell>
|
||||
<StyledActionTableCell>{action}</StyledActionTableCell>
|
||||
</StyledAIAgentTableRow>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,91 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import { IconChevronRight, IconSearch } from 'twenty-ui/display';
|
||||
import { Agent } from '~/generated-metadata/graphql';
|
||||
import { SETTINGS_AI_AGENT_TABLE_METADATA } from '~/pages/settings/ai/constants/SettingsAIAgentTableMetadata';
|
||||
import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
|
||||
import {
|
||||
SettingsAIAgentTableRow,
|
||||
StyledAIAgentTableRow,
|
||||
} from './SettingsAIAgentTableRow';
|
||||
|
||||
const StyledSearchInput = styled(TextInput)`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
margin-top: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledTableHeaderRow = styled(StyledAIAgentTableRow)`
|
||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
export const SettingsAIAgentsTable = ({ agents }: { agents: Agent[] }) => {
|
||||
const { t } = useLingui();
|
||||
const theme = useTheme();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
const sortedAgents = useSortedArray(agents, SETTINGS_AI_AGENT_TABLE_METADATA);
|
||||
|
||||
const filteredAgents = sortedAgents.filter(
|
||||
(agent) =>
|
||||
agent.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
agent.label.toLowerCase().includes(searchTerm.toLowerCase()),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSearchInput
|
||||
instanceId="settings-ai-agents-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search an agent...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
|
||||
<StyledTable>
|
||||
<StyledTableHeaderRow>
|
||||
{SETTINGS_AI_AGENT_TABLE_METADATA.fields.map(
|
||||
(settingsAIAgentTableMetadataField) => (
|
||||
<SortableTableHeader
|
||||
key={settingsAIAgentTableMetadataField.fieldName}
|
||||
fieldName={settingsAIAgentTableMetadataField.fieldName}
|
||||
label={t(settingsAIAgentTableMetadataField.fieldLabel)}
|
||||
tableId={SETTINGS_AI_AGENT_TABLE_METADATA.tableId}
|
||||
align={settingsAIAgentTableMetadataField.align}
|
||||
initialSort={SETTINGS_AI_AGENT_TABLE_METADATA.initialSort}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<TableHeader />
|
||||
</StyledTableHeaderRow>
|
||||
{filteredAgents.map((agent) => (
|
||||
<SettingsAIAgentTableRow
|
||||
key={agent.id}
|
||||
agent={agent}
|
||||
action={
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
}
|
||||
link={getSettingsPath(SettingsPath.AIAgentDetail, {
|
||||
agentId: agent.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</StyledTable>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import { ApolloError } from '@apollo/client';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
|
||||
import { SettingsPath } from '@/types/SettingsPath';
|
||||
import { useDeleteOneAgentMutation } from '~/generated-metadata/graphql';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const DELETE_AGENT_MODAL_ID = 'delete-agent-modal';
|
||||
|
||||
type SettingsAgentDeleteConfirmationModalProps = {
|
||||
agentId: string;
|
||||
agentName: string;
|
||||
};
|
||||
|
||||
export const SettingsAgentDeleteConfirmationModal = ({
|
||||
agentId,
|
||||
agentName,
|
||||
}: SettingsAgentDeleteConfirmationModalProps) => {
|
||||
const { t } = useLingui();
|
||||
const { closeModal } = useModal();
|
||||
const navigate = useNavigateSettings();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const [deleteAgent] = useDeleteOneAgentMutation();
|
||||
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteAgent({
|
||||
variables: {
|
||||
input: { id: agentId },
|
||||
},
|
||||
});
|
||||
closeModal(DELETE_AGENT_MODAL_ID);
|
||||
navigate(SettingsPath.AI);
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
apolloError: error instanceof ApolloError ? error : undefined,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmationModal
|
||||
confirmationValue={agentName}
|
||||
confirmationPlaceholder={agentName}
|
||||
modalId={DELETE_AGENT_MODAL_ID}
|
||||
title={t`Delete Agent`}
|
||||
subtitle={
|
||||
<Trans>
|
||||
This action cannot be undone. This will permanently delete your agent.
|
||||
<br />
|
||||
Please type in the agent name to confirm.
|
||||
</Trans>
|
||||
}
|
||||
onConfirmClick={handleDelete}
|
||||
confirmButtonText={t`Delete Agent`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import Skeleton, { SkeletonTheme } from 'react-loading-skeleton';
|
||||
|
||||
import { SKELETON_LOADER_HEIGHT_SIZES } from '@/activities/components/SkeletonLoader';
|
||||
|
||||
const StyledSkeletonContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(8)};
|
||||
`;
|
||||
|
||||
const StyledFormSection = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledIconNameRow = styled.div`
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
flex-shrink: 0;
|
||||
`;
|
||||
|
||||
const StyledNameContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
export const SettingsAgentDetailSkeletonLoader = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<SkeletonTheme
|
||||
baseColor={theme.background.tertiary}
|
||||
highlightColor={theme.background.transparent.lighter}
|
||||
borderRadius={4}
|
||||
>
|
||||
<StyledSkeletonContainer>
|
||||
<StyledFormSection>
|
||||
<StyledIconNameRow>
|
||||
<StyledIconContainer>
|
||||
<Skeleton
|
||||
width={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
/>
|
||||
</StyledIconContainer>
|
||||
<StyledNameContainer>
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
width="100%"
|
||||
/>
|
||||
</StyledNameContainer>
|
||||
</StyledIconNameRow>
|
||||
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
<Skeleton
|
||||
height={SKELETON_LOADER_HEIGHT_SIZES.standard.l}
|
||||
width="100%"
|
||||
/>
|
||||
|
||||
<Skeleton height={120} width="100%" />
|
||||
</StyledFormSection>
|
||||
</StyledSkeletonContainer>
|
||||
</SkeletonTheme>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,151 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { IconPlus } from 'twenty-ui/display';
|
||||
import { Button, SelectOption } from 'twenty-ui/input';
|
||||
import { useCreateAgentHandoffMutation } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledAddHandoffForm = styled.div`
|
||||
background: ${({ theme }) => theme.background.secondary};
|
||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(3)};
|
||||
padding: ${({ theme }) => theme.spacing(3)};
|
||||
margin-top: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledFormActions = styled.div`
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledAddButtonContainer = styled.div`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
type SettingsAgentHandoffFormProps = {
|
||||
agentId: string;
|
||||
availableAgentOptions: SelectOption[];
|
||||
agentsLoading: boolean;
|
||||
onHandoffAdded: () => void;
|
||||
};
|
||||
|
||||
export const SettingsAgentHandoffForm = ({
|
||||
agentId,
|
||||
availableAgentOptions,
|
||||
agentsLoading,
|
||||
onHandoffAdded,
|
||||
}: SettingsAgentHandoffFormProps) => {
|
||||
const { t } = useLingui();
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const [isAddingHandoff, setIsAddingHandoff] = useState(false);
|
||||
const [selectedTargetAgentId, setSelectedTargetAgentId] = useState('');
|
||||
const [handoffDescription, setHandoffDescription] = useState('');
|
||||
|
||||
const [createAgentHandoff] = useCreateAgentHandoffMutation();
|
||||
|
||||
const noAvailableAgents = availableAgentOptions.length === 0;
|
||||
|
||||
const resetHandoffForm = () => {
|
||||
setIsAddingHandoff(false);
|
||||
setSelectedTargetAgentId('');
|
||||
setHandoffDescription('');
|
||||
};
|
||||
|
||||
const handleAddHandoff = async () => {
|
||||
try {
|
||||
await createAgentHandoff({
|
||||
variables: {
|
||||
input: {
|
||||
fromAgentId: agentId,
|
||||
toAgentId: selectedTargetAgentId,
|
||||
description: handoffDescription,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
onHandoffAdded();
|
||||
resetHandoffForm();
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Handoff created successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to create handoff`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{isAddingHandoff ? (
|
||||
<StyledAddHandoffForm>
|
||||
<Select
|
||||
fullWidth
|
||||
dropdownId="handoff-target-select"
|
||||
label={t`Target Agent`}
|
||||
value={selectedTargetAgentId}
|
||||
onChange={setSelectedTargetAgentId}
|
||||
options={availableAgentOptions}
|
||||
emptyOption={{
|
||||
label: t`Select a target agent`,
|
||||
value: '',
|
||||
}}
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
textAreaId="handoff-description-textarea"
|
||||
label={t`Description (Optional)`}
|
||||
placeholder={t`Describe when this handoff should be used`}
|
||||
minRows={2}
|
||||
value={handoffDescription}
|
||||
onChange={setHandoffDescription}
|
||||
/>
|
||||
|
||||
<StyledFormActions>
|
||||
<Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
title={t`Cancel`}
|
||||
onClick={resetHandoffForm}
|
||||
/>
|
||||
<Button
|
||||
accent="blue"
|
||||
size="small"
|
||||
title={t`Add Handoff`}
|
||||
onClick={handleAddHandoff}
|
||||
disabled={!selectedTargetAgentId}
|
||||
/>
|
||||
</StyledFormActions>
|
||||
</StyledAddHandoffForm>
|
||||
) : (
|
||||
<StyledAddButtonContainer>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
title={
|
||||
agentsLoading
|
||||
? t`Loading...`
|
||||
: noAvailableAgents
|
||||
? t`No agents available for handoff`
|
||||
: t`Add Handoff`
|
||||
}
|
||||
Icon={IconPlus}
|
||||
onClick={() => setIsAddingHandoff(true)}
|
||||
disabled={agentsLoading || noAvailableAgents}
|
||||
/>
|
||||
</StyledAddButtonContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { SelectOption } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import {
|
||||
useFindAgentHandoffsQuery,
|
||||
useFindManyAgentsQuery,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { SettingsAgentHandoffForm } from './SettingsAgentHandoffForm';
|
||||
import { SettingsAgentHandoffTable } from './SettingsAgentHandoffTable';
|
||||
|
||||
export const SettingsAgentHandoffSection = ({
|
||||
agentId,
|
||||
}: {
|
||||
agentId: string;
|
||||
}) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const { data: agentsData, loading: agentsLoading } = useFindManyAgentsQuery();
|
||||
const { data: handoffData, refetch: refetchHandoffTargets } =
|
||||
useFindAgentHandoffsQuery({
|
||||
variables: { input: { id: agentId } },
|
||||
skip: !agentId,
|
||||
});
|
||||
|
||||
const handoffTargets = handoffData?.findAgentHandoffs || [];
|
||||
|
||||
const availableAgentOptions =
|
||||
agentsData?.findManyAgents?.reduce<SelectOption[]>((acc, agent) => {
|
||||
if (
|
||||
agent.id !== agentId &&
|
||||
!handoffTargets.some((handoff: any) => handoff.toAgent.id === agent.id)
|
||||
) {
|
||||
acc.push({
|
||||
label: agent.label,
|
||||
value: agent.id,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []) || [];
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Agent Handoffs`}
|
||||
description={t`Configure which agents this agent can hand off conversations to`}
|
||||
/>
|
||||
|
||||
<SettingsAgentHandoffTable
|
||||
agentId={agentId}
|
||||
handoffTargets={handoffTargets}
|
||||
onHandoffRemoved={refetchHandoffTargets}
|
||||
/>
|
||||
|
||||
<SettingsAgentHandoffForm
|
||||
agentId={agentId}
|
||||
availableAgentOptions={availableAgentOptions}
|
||||
agentsLoading={agentsLoading}
|
||||
onHandoffAdded={refetchHandoffTargets}
|
||||
/>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,183 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { Trans, useLingui } from '@lingui/react/macro';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { TextInputV2 } from '@/ui/input/components/TextInputV2';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import {
|
||||
IconSearch,
|
||||
IconTrash,
|
||||
OverflowingTextWithTooltip,
|
||||
} from 'twenty-ui/display';
|
||||
import { IconButton } from 'twenty-ui/input';
|
||||
import { useRemoveAgentHandoffMutation } from '~/generated-metadata/graphql';
|
||||
import { AgentHandoffDto } from '~/generated/graphql';
|
||||
|
||||
const AGENT_HANDOFF_DELETION_MODAL_ID = 'agent-handoff-deletion-modal';
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: ${({ theme }) => theme.spacing(3)};
|
||||
`;
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
|
||||
`;
|
||||
|
||||
const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledTableRows = styled.div`
|
||||
padding-block: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledNoHandoffs = styled(TableCell)`
|
||||
color: ${({ theme }) => theme.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledTableCell = styled(TableCell)`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
type SettingsAgentHandoffTableProps = {
|
||||
agentId: string;
|
||||
handoffTargets: AgentHandoffDto[];
|
||||
onHandoffRemoved: () => void;
|
||||
};
|
||||
|
||||
export const SettingsAgentHandoffTable = ({
|
||||
agentId,
|
||||
handoffTargets,
|
||||
onHandoffRemoved,
|
||||
}: SettingsAgentHandoffTableProps) => {
|
||||
const { t } = useLingui();
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const [searchFilter, setSearchFilter] = useState('');
|
||||
const [handoffToDelete, setHandoffToDelete] = useState<string | undefined>();
|
||||
|
||||
const [removeAgentHandoff] = useRemoveAgentHandoffMutation();
|
||||
|
||||
const filteredHandoffTargets = !searchFilter
|
||||
? handoffTargets
|
||||
: handoffTargets.filter((handoff) => {
|
||||
const searchTerm = searchFilter.toLowerCase();
|
||||
const label = handoff.toAgent.label?.toLowerCase() || '';
|
||||
const description = handoff.description?.toLowerCase() || '';
|
||||
|
||||
return label.includes(searchTerm) || description.includes(searchTerm);
|
||||
});
|
||||
|
||||
const handleRemoveHandoff = async () => {
|
||||
if (!handoffToDelete) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await removeAgentHandoff({
|
||||
variables: {
|
||||
input: {
|
||||
fromAgentId: agentId,
|
||||
toAgentId: handoffToDelete,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
onHandoffRemoved();
|
||||
enqueueSuccessSnackBar({
|
||||
message: t`Handoff removed successfully`,
|
||||
});
|
||||
} catch (error) {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to remove handoff`,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const { openModal } = useModal();
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSearchContainer>
|
||||
<TextInputV2
|
||||
value={searchFilter}
|
||||
onChange={setSearchFilter}
|
||||
placeholder={t`Search handoffs...`}
|
||||
fullWidth
|
||||
LeftIcon={IconSearch}
|
||||
sizeVariant="lg"
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
|
||||
<StyledTable>
|
||||
<TableRow gridAutoColumns="2fr 2fr 1fr">
|
||||
<TableHeader>
|
||||
<Trans>Target Agent</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader>
|
||||
<Trans>Description</Trans>
|
||||
</TableHeader>
|
||||
<TableHeader align={'right'} />
|
||||
</TableRow>
|
||||
<StyledTableRows>
|
||||
{filteredHandoffTargets.length > 0 ? (
|
||||
filteredHandoffTargets.map((handoff) => (
|
||||
<TableRow gridAutoColumns="2fr 2fr 1fr" key={handoff.id}>
|
||||
<TableCell>
|
||||
<OverflowingTextWithTooltip text={handoff.toAgent.label} />
|
||||
</TableCell>
|
||||
|
||||
<StyledTableCell>
|
||||
<OverflowingTextWithTooltip
|
||||
text={handoff.description || t`No description`}
|
||||
/>
|
||||
</StyledTableCell>
|
||||
<TableCell align={'right'}>
|
||||
<StyledButtonContainer>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
openModal(AGENT_HANDOFF_DELETION_MODAL_ID);
|
||||
setHandoffToDelete(handoff.toAgent.id);
|
||||
}}
|
||||
variant="tertiary"
|
||||
size="medium"
|
||||
Icon={IconTrash}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<StyledNoHandoffs>
|
||||
{!searchFilter
|
||||
? t`No handoffs configured for this agent`
|
||||
: t`No handoffs match your search`}
|
||||
</StyledNoHandoffs>
|
||||
)}
|
||||
</StyledTableRows>
|
||||
</StyledTable>
|
||||
|
||||
<ConfirmationModal
|
||||
modalId={AGENT_HANDOFF_DELETION_MODAL_ID}
|
||||
title={t`Remove Handoff`}
|
||||
subtitle={
|
||||
<Trans>
|
||||
This action cannot be undone. This will permanently remove the
|
||||
handoff configuration.
|
||||
</Trans>
|
||||
}
|
||||
onConfirmClick={handleRemoveHandoff}
|
||||
confirmButtonText={t`Remove handoff`}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { TableMetadata } from '@/ui/layout/table/types/TableMetadata';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { Agent } from '~/generated-metadata/graphql';
|
||||
|
||||
export const SETTINGS_AI_AGENT_TABLE_METADATA: TableMetadata<Agent> = {
|
||||
tableId: 'settingsAIAgent',
|
||||
fields: [
|
||||
{
|
||||
fieldLabel: msg`Name`,
|
||||
fieldName: 'name',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
{
|
||||
fieldLabel: msg`Type`,
|
||||
fieldName: 'isCustom',
|
||||
fieldType: 'string',
|
||||
align: 'left',
|
||||
},
|
||||
],
|
||||
initialSort: {
|
||||
fieldName: 'name',
|
||||
orderBy: 'AscNullsLast',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions';
|
||||
import { IconPicker } from '@/ui/input/components/IconPicker';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { TextArea } from '@/ui/input/components/TextArea';
|
||||
import { TextInput } from '@/ui/input/components/TextInput';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useGetRolesQuery } from '~/generated-metadata/graphql';
|
||||
import { computeMetadataNameFromLabel } from '~/pages/settings/data-model/utils/compute-metadata-name-from-label.utils';
|
||||
import { SettingsAIAgentFormValues } from '../../hooks/useSettingsAgentFormState';
|
||||
|
||||
const StyledFormContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledIconNameRow = styled.div`
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(2)};
|
||||
`;
|
||||
|
||||
const StyledNameContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledErrorMessage = styled.div`
|
||||
color: ${({ theme }) => theme.color.red};
|
||||
font-size: ${({ theme }) => theme.font.size.sm};
|
||||
margin-top: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
type SettingsAIAgentFormProps = {
|
||||
formValues: SettingsAIAgentFormValues;
|
||||
onFieldChange: (
|
||||
field: keyof SettingsAIAgentFormValues,
|
||||
value: SettingsAIAgentFormValues[keyof SettingsAIAgentFormValues],
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const SettingsAIAgentForm = ({
|
||||
formValues,
|
||||
onFieldChange,
|
||||
}: SettingsAIAgentFormProps) => {
|
||||
const { t } = useLingui();
|
||||
|
||||
const modelOptions = useAiModelOptions();
|
||||
const { data: rolesData } = useGetRolesQuery();
|
||||
|
||||
const rolesOptions =
|
||||
rolesData?.getRoles?.map((role) => ({
|
||||
label: role.label,
|
||||
value: role.id,
|
||||
})) || [];
|
||||
|
||||
const noModelsAvailable = modelOptions.length === 0;
|
||||
|
||||
const fillNameFromLabel = (label: string) => {
|
||||
isDefined(label) &&
|
||||
onFieldChange('name', computeMetadataNameFromLabel(label));
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledFormContainer>
|
||||
<StyledFormContainer>
|
||||
<StyledIconNameRow>
|
||||
<IconPicker
|
||||
selectedIconKey={formValues.icon || 'IconRobot'}
|
||||
onChange={({ iconKey }) => {
|
||||
onFieldChange('icon', iconKey);
|
||||
}}
|
||||
/>
|
||||
|
||||
<StyledNameContainer>
|
||||
<TextInput
|
||||
instanceId="agent-label-input"
|
||||
placeholder={t`Enter agent name*`}
|
||||
value={formValues.label}
|
||||
onChange={(value) => {
|
||||
onFieldChange('label', value);
|
||||
fillNameFromLabel(value);
|
||||
}}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledNameContainer>
|
||||
</StyledIconNameRow>
|
||||
</StyledFormContainer>
|
||||
|
||||
<StyledFormContainer>
|
||||
<TextArea
|
||||
textAreaId="agent-description-textarea"
|
||||
placeholder={t`Write a description for this agent`}
|
||||
minRows={3}
|
||||
value={formValues.description || ''}
|
||||
onChange={(value) => onFieldChange('description', value)}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
|
||||
<StyledFormContainer>
|
||||
<Select
|
||||
dropdownId="ai-model-select"
|
||||
label={t`AI Model`}
|
||||
value={formValues.modelId}
|
||||
onChange={(value) => onFieldChange('modelId', value)}
|
||||
options={modelOptions}
|
||||
disabled={noModelsAvailable}
|
||||
/>
|
||||
{noModelsAvailable && (
|
||||
<StyledErrorMessage>
|
||||
{t`No models available. Please configure AI models in your workspace settings.`}
|
||||
</StyledErrorMessage>
|
||||
)}
|
||||
</StyledFormContainer>
|
||||
|
||||
<StyledFormContainer>
|
||||
<Select
|
||||
dropdownId="ai-role-select"
|
||||
label={t`Role`}
|
||||
value={formValues.role || ''}
|
||||
onChange={(value) => onFieldChange('role', value)}
|
||||
options={rolesOptions}
|
||||
emptyOption={{
|
||||
label: t`Select a role`,
|
||||
value: '',
|
||||
}}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
|
||||
<StyledFormContainer>
|
||||
<TextArea
|
||||
textAreaId="agent-prompt-textarea"
|
||||
label={t`System Prompt*`}
|
||||
placeholder={t`Enter the system prompt that defines this agent's behavior and capabilities`}
|
||||
minRows={6}
|
||||
value={formValues.prompt}
|
||||
onChange={(value) => onFieldChange('prompt', value)}
|
||||
/>
|
||||
</StyledFormContainer>
|
||||
</StyledFormContainer>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { z } from 'zod';
|
||||
import { settingsAIAgentFormSchema } from '../validation-schemas/settingsAIAgentFormSchema';
|
||||
|
||||
export type SettingsAIAgentFormValues = z.infer<
|
||||
typeof settingsAIAgentFormSchema
|
||||
>;
|
||||
|
||||
export const useSettingsAgentFormState = (mode: 'create' | 'edit') => {
|
||||
const [formValues, setFormValues] = useState<SettingsAIAgentFormValues>({
|
||||
name: '',
|
||||
label: '',
|
||||
description: '',
|
||||
icon: 'IconRobot',
|
||||
modelId: mode === 'edit' ? '' : 'auto',
|
||||
role: '',
|
||||
prompt: '',
|
||||
isCustom: true,
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
try {
|
||||
settingsAIAgentFormSchema.parse(formValues);
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFieldChange = (
|
||||
field: keyof SettingsAIAgentFormValues,
|
||||
value: SettingsAIAgentFormValues[keyof SettingsAIAgentFormValues],
|
||||
) => {
|
||||
setFormValues((prev) => ({ ...prev, [field]: value }));
|
||||
};
|
||||
|
||||
const resetForm = (values?: Partial<SettingsAIAgentFormValues>) => {
|
||||
if (isDefined(values)) {
|
||||
setFormValues((prev) => ({ ...prev, ...values }));
|
||||
} else {
|
||||
setFormValues({
|
||||
name: '',
|
||||
label: '',
|
||||
description: '',
|
||||
icon: 'IconRobot',
|
||||
modelId: mode === 'edit' ? '' : 'auto',
|
||||
role: '',
|
||||
prompt: '',
|
||||
isCustom: true,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
formValues,
|
||||
isSubmitting,
|
||||
handleFieldChange,
|
||||
resetForm,
|
||||
setIsSubmitting,
|
||||
validateForm,
|
||||
};
|
||||
};
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { z } from 'zod';
|
||||
import { zodNonEmptyString } from '~/types/ZodNonEmptyString';
|
||||
|
||||
export const settingsAIAgentFormSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
label: zodNonEmptyString,
|
||||
description: z.string().nullish(),
|
||||
icon: z.string().optional(),
|
||||
modelId: z.union([z.string().min(1, 'Model is required'), z.literal('auto')]),
|
||||
role: z.string().optional(),
|
||||
prompt: zodNonEmptyString,
|
||||
isCustom: z.boolean().default(true),
|
||||
});
|
||||
|
||||
export type SettingsAIAgentFormValues = z.infer<
|
||||
typeof settingsAIAgentFormSchema
|
||||
>;
|
||||
@@ -2,7 +2,6 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import { AgentModule } from 'src/engine/metadata-modules/agent/agent.module';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
@@ -14,7 +13,6 @@ import { AgentRoleService } from './agent-role.service';
|
||||
[AgentEntity, RoleEntity, RoleTargetsEntity],
|
||||
'core',
|
||||
),
|
||||
AgentModule,
|
||||
],
|
||||
providers: [AgentRoleService],
|
||||
exports: [AgentRoleService],
|
||||
|
||||
+21
-17
@@ -17,6 +17,7 @@ import { In, Repository } from 'typeorm';
|
||||
|
||||
import { ModelProvider } from 'src/engine/core-modules/ai/constants/ai-models.const';
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { extractFolderPathAndFilename } from 'src/engine/core-modules/file/utils/extract-folderpath-and-filename.utils';
|
||||
@@ -34,9 +35,7 @@ import { convertOutputSchemaToZod } from 'src/engine/metadata-modules/agent/util
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
||||
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
|
||||
import { AgentEntity } from './agent.entity';
|
||||
import { AgentException, AgentExceptionCode } from './agent.exception';
|
||||
@@ -96,20 +95,22 @@ export class AgentExecutionService {
|
||||
agent,
|
||||
}: {
|
||||
system: string;
|
||||
agent: AgentEntity;
|
||||
agent: AgentEntity | null;
|
||||
prompt?: string;
|
||||
messages?: CoreMessage[];
|
||||
}) {
|
||||
try {
|
||||
this.logger.log(
|
||||
`Preparing AI request config for agent ${agent.id} with model ${agent.modelId}`,
|
||||
);
|
||||
if (agent) {
|
||||
this.logger.log(
|
||||
`Preparing AI request config for agent ${agent.id} with model ${agent.modelId}`,
|
||||
);
|
||||
}
|
||||
|
||||
const aiModel = this.aiModelRegistryService.getEffectiveModelConfig(
|
||||
agent.modelId,
|
||||
agent?.modelId ?? 'auto',
|
||||
);
|
||||
|
||||
if (!aiModel) {
|
||||
if (agent && !aiModel) {
|
||||
const error = `AI model with id ${agent.modelId} not found`;
|
||||
|
||||
this.logger.error(error);
|
||||
@@ -127,10 +128,12 @@ export class AgentExecutionService {
|
||||
|
||||
await this.validateApiKey(provider);
|
||||
|
||||
const tools = await this.agentToolService.generateToolsForAgent(
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
);
|
||||
const tools = agent
|
||||
? await this.agentToolService.generateToolsForAgent(
|
||||
agent.id,
|
||||
agent.workspaceId,
|
||||
)
|
||||
: {};
|
||||
|
||||
this.logger.log(`Generated ${Object.keys(tools).length} tools for agent`);
|
||||
|
||||
@@ -155,7 +158,7 @@ export class AgentExecutionService {
|
||||
};
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to prepare AI request config for agent ${agent.id}:`,
|
||||
`Failed to prepare AI request config for agent ${agent?.id ?? 'no agent'}`,
|
||||
error instanceof Error ? error.stack : error,
|
||||
);
|
||||
throw error;
|
||||
@@ -343,18 +346,19 @@ export class AgentExecutionService {
|
||||
|
||||
async executeAgent({
|
||||
agent,
|
||||
context,
|
||||
schema,
|
||||
userPrompt,
|
||||
}: {
|
||||
agent: AgentEntity;
|
||||
agent: AgentEntity | null;
|
||||
context: Record<string, unknown>;
|
||||
schema: OutputSchema;
|
||||
userPrompt: string;
|
||||
}): Promise<AgentExecutionResult> {
|
||||
try {
|
||||
const aiRequestConfig = await this.prepareAIRequestConfig({
|
||||
system: AGENT_SYSTEM_PROMPTS.AGENT_EXECUTION,
|
||||
system: `You are executing as part of a workflow automation. ${agent ? agent.prompt : ''}`,
|
||||
agent,
|
||||
prompt: resolveInput(agent.prompt, context) as string,
|
||||
prompt: userPrompt,
|
||||
});
|
||||
const textResponse = await generateText(aiRequestConfig);
|
||||
|
||||
|
||||
+7
-20
@@ -5,6 +5,7 @@ import { generateText } from 'ai';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { AiModelRegistryService } from 'src/engine/core-modules/ai/services/ai-model-registry.service';
|
||||
import { AGENT_HANDOFF_PROMPT_TEMPLATE } from 'src/engine/metadata-modules/agent/constants/agent-handoff-prompt.const';
|
||||
|
||||
import { AgentHandoffService } from './agent-handoff.service';
|
||||
import { AgentEntity } from './agent.entity';
|
||||
@@ -31,7 +32,7 @@ export class AgentHandoffExecutorService {
|
||||
|
||||
async executeHandoff(handoffRequest: HandoffRequest) {
|
||||
try {
|
||||
const { fromAgentId, toAgentId, workspaceId, reason } = handoffRequest;
|
||||
const { fromAgentId, toAgentId, workspaceId } = handoffRequest;
|
||||
|
||||
const canHandoff = await this.agentHandoffService.canHandoffTo({
|
||||
fromAgentId,
|
||||
@@ -76,13 +77,7 @@ export class AgentHandoffExecutorService {
|
||||
|
||||
const textResponse = await generateText(aiRequestConfig);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
newAgentId: toAgentId,
|
||||
newAgentName: targetAgent.name,
|
||||
message: `Successfully transferred to ${targetAgent.name}. ${reason}`,
|
||||
response: textResponse.text,
|
||||
};
|
||||
return textResponse.text;
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Handoff execution failed: ${error.message}`,
|
||||
@@ -101,17 +96,9 @@ export class AgentHandoffExecutorService {
|
||||
private createHandoffPrompt(handoffRequest: HandoffRequest): string {
|
||||
const { reason, context } = handoffRequest;
|
||||
|
||||
const prompt = `
|
||||
You have received a handoff from another AI agent. This means the previous agent has determined that you are better suited to handle this conversation based on your specialized knowledge and capabilities.
|
||||
|
||||
The previous agent has transferred this conversation to you because: ${reason}
|
||||
|
||||
Additional context from the previous agent:
|
||||
${context || 'No additional context provided'}
|
||||
|
||||
Please continue the conversation naturally, acknowledging that you are taking over from the previous agent. Use your specialized knowledge to provide the best possible assistance to the user.
|
||||
`;
|
||||
|
||||
return prompt;
|
||||
return AGENT_HANDOFF_PROMPT_TEMPLATE.replace('{reason}', reason).replace(
|
||||
'{context}',
|
||||
context || 'No additional context provided',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,24 @@ export class AgentHandoffService {
|
||||
return handoffs.map((handoff) => handoff.toAgent);
|
||||
}
|
||||
|
||||
async getAgentHandoffs({
|
||||
fromAgentId,
|
||||
workspaceId,
|
||||
}: {
|
||||
fromAgentId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<AgentHandoffEntity[]> {
|
||||
const handoffs = await this.agentHandoffRepository.find({
|
||||
where: {
|
||||
fromAgentId,
|
||||
workspaceId,
|
||||
},
|
||||
relations: ['toAgent'],
|
||||
});
|
||||
|
||||
return handoffs;
|
||||
}
|
||||
|
||||
async createHandoff({
|
||||
fromAgentId,
|
||||
toAgentId,
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ToolService } from 'src/engine/core-modules/ai/services/tool.service';
|
||||
import { AgentHandoffExecutorService } from 'src/engine/metadata-modules/agent/agent-handoff-executor.service';
|
||||
import { AgentHandoffService } from 'src/engine/metadata-modules/agent/agent-handoff.service';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { AGENT_HANDOFF_DESCRIPTION_TEMPLATE } from 'src/engine/metadata-modules/agent/constants/agent-handoff-description.const';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { camelCase } from 'src/utils/camel-case';
|
||||
|
||||
@@ -67,49 +68,59 @@ export class AgentToolService {
|
||||
agentId: string,
|
||||
workspaceId: string,
|
||||
): Promise<ToolSet> {
|
||||
const handoffTargets = await this.agentHandoffService.getHandoffTargets({
|
||||
const handoffs = await this.agentHandoffService.getAgentHandoffs({
|
||||
fromAgentId: agentId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const handoffTools = handoffTargets.reduce<ToolSet>(
|
||||
(tools, targetAgent) => {
|
||||
const toolName = `transfer_to_${camelCase(targetAgent.name)}`;
|
||||
const handoffTools = handoffs.reduce<ToolSet>((tools, handoff) => {
|
||||
const toolName = `handoff_to_${camelCase(handoff.toAgent.name)}`;
|
||||
|
||||
const handoffSchema = z.object({
|
||||
reason: z.string().describe('Reason for transferring to this agent'),
|
||||
const handoffSchema = z.object({
|
||||
toolDescription: z
|
||||
.string()
|
||||
.describe(
|
||||
"A clear, human-readable status message describing the handoff being made. This will be shown to the user while the handoff is being processed, so phrase it as a present-tense status update (e.g., 'Transferring you to the sales agent for pricing information').",
|
||||
),
|
||||
input: z.object({
|
||||
reason: z
|
||||
.string()
|
||||
.describe(
|
||||
'Brief explanation of why this handoff is needed (e.g., "User needs pricing information", "User requires technical support", "User wants to discuss billing")',
|
||||
),
|
||||
context: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Additional context to pass to the receiving agent'),
|
||||
});
|
||||
.describe(
|
||||
'Any relevant context or information to pass to the receiving agent (e.g., user preferences, previous conversation details, specific requirements)',
|
||||
),
|
||||
}),
|
||||
});
|
||||
|
||||
tools[toolName] = {
|
||||
description: `Transfer this request to ${targetAgent.name} when you need their specialized expertise. Use this when the user's request is outside your capabilities or when ${targetAgent.name} would be better suited to handle the request.`,
|
||||
parameters: handoffSchema,
|
||||
execute: async ({ reason, context }) => {
|
||||
const result =
|
||||
await this.agentHandoffExecutorService.executeHandoff({
|
||||
fromAgentId: agentId,
|
||||
toAgentId: targetAgent.id,
|
||||
workspaceId,
|
||||
reason,
|
||||
context,
|
||||
});
|
||||
tools[toolName] = {
|
||||
description:
|
||||
handoff.description ||
|
||||
handoff.toAgent.description ||
|
||||
AGENT_HANDOFF_DESCRIPTION_TEMPLATE.replace(
|
||||
'{agentName}',
|
||||
handoff.toAgent.name,
|
||||
),
|
||||
parameters: handoffSchema,
|
||||
execute: async ({ input: { reason, context } }) => {
|
||||
const result = await this.agentHandoffExecutorService.executeHandoff({
|
||||
fromAgentId: agentId,
|
||||
toAgentId: handoff.toAgent.id,
|
||||
workspaceId,
|
||||
reason,
|
||||
context,
|
||||
});
|
||||
|
||||
return {
|
||||
success: result.success,
|
||||
message: result.message || `Transferred to ${targetAgent.name}`,
|
||||
newAgentId: result.newAgentId,
|
||||
newAgentName: result.newAgentName,
|
||||
};
|
||||
},
|
||||
};
|
||||
return result;
|
||||
},
|
||||
};
|
||||
|
||||
return tools;
|
||||
},
|
||||
{},
|
||||
);
|
||||
return tools;
|
||||
}, {});
|
||||
|
||||
return handoffTools;
|
||||
}
|
||||
|
||||
@@ -4,19 +4,20 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { AiModule } from 'src/engine/core-modules/ai/ai.module';
|
||||
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileUploadModule } from 'src/engine/core-modules/file/file-upload/file-upload.module';
|
||||
import { FileModule } from 'src/engine/core-modules/file/file.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { AgentRoleModule } from 'src/engine/metadata-modules/agent-role/agent-role.module';
|
||||
import { AgentChatController } from 'src/engine/metadata-modules/agent/agent-chat.controller';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { DomainManagerModule } from 'src/engine/core-modules/domain-manager/domain-manager.module';
|
||||
|
||||
import { AgentChatMessageEntity } from './agent-chat-message.entity';
|
||||
import { AgentChatThreadEntity } from './agent-chat-thread.entity';
|
||||
@@ -49,6 +50,7 @@ import { AgentService } from './agent.service';
|
||||
'core',
|
||||
),
|
||||
AiModule,
|
||||
AgentRoleModule,
|
||||
ThrottlerModule,
|
||||
AuditModule,
|
||||
FeatureFlagModule,
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -9,17 +12,37 @@ import {
|
||||
RequireFeatureFlag,
|
||||
} from 'src/engine/guards/feature-flag.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { CreateAgentHandoffInput } from 'src/engine/metadata-modules/agent/dtos/create-agent-handoff.input';
|
||||
import { RemoveAgentHandoffInput } from 'src/engine/metadata-modules/agent/dtos/remove-agent-handoff.input';
|
||||
|
||||
import { AgentHandoffService } from './agent-handoff.service';
|
||||
import { AgentEntity } from './agent.entity';
|
||||
import { AgentService } from './agent.service';
|
||||
|
||||
import { AgentHandoffDTO } from './dtos/agent-handoff.dto';
|
||||
import { AgentIdInput } from './dtos/agent-id.input';
|
||||
import { AgentDTO } from './dtos/agent.dto';
|
||||
import { CreateAgentInput } from './dtos/create-agent.input';
|
||||
import { UpdateAgentInput } from './dtos/update-agent.input';
|
||||
|
||||
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard)
|
||||
@Resolver()
|
||||
export class AgentResolver {
|
||||
constructor(private readonly agentService: AgentService) {}
|
||||
constructor(
|
||||
@InjectRepository(AgentEntity, 'core')
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
private readonly agentService: AgentService,
|
||||
private readonly agentHandoffService: AgentHandoffService,
|
||||
) {}
|
||||
|
||||
@Query(() => [AgentDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async findManyAgents(@AuthWorkspace() { id: workspaceId }: Workspace) {
|
||||
return this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
@@ -30,6 +53,42 @@ export class AgentResolver {
|
||||
return this.agentService.findOneAgent(id, workspaceId);
|
||||
}
|
||||
|
||||
@Query(() => [AgentDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async findAgentHandoffTargets(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentHandoffService.getHandoffTargets({
|
||||
fromAgentId: id,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => [AgentHandoffDTO])
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async findAgentHandoffs(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentHandoffService.getAgentHandoffs({
|
||||
fromAgentId: id,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async createOneAgent(
|
||||
@Args('input') input: CreateAgentInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentService.createOneAgent(
|
||||
{ ...input, isCustom: true },
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async updateOneAgent(
|
||||
@@ -38,4 +97,44 @@ export class AgentResolver {
|
||||
) {
|
||||
return this.agentService.updateOneAgent(input, workspaceId);
|
||||
}
|
||||
|
||||
@Mutation(() => AgentDTO)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async deleteOneAgent(
|
||||
@Args('input') { id }: AgentIdInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.agentService.deleteOneAgent(id, workspaceId);
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async createAgentHandoff(
|
||||
@Args('input') input: CreateAgentHandoffInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
await this.agentHandoffService.createHandoff({
|
||||
fromAgentId: input.fromAgentId,
|
||||
toAgentId: input.toAgentId,
|
||||
workspaceId,
|
||||
description: input.description,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean)
|
||||
@RequireFeatureFlag(FeatureFlagKey.IS_AI_ENABLED)
|
||||
async removeAgentHandoff(
|
||||
@Args('input') input: RemoveAgentHandoffInput,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
await this.agentHandoffService.removeHandoff({
|
||||
fromAgentId: input.fromAgentId,
|
||||
toAgentId: input.toAgentId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const';
|
||||
import { AgentRoleService } from 'src/engine/metadata-modules/agent-role/agent-role.service';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/agent/agent-chat.service';
|
||||
import { CreateAgentInput } from 'src/engine/metadata-modules/agent/dtos/create-agent.input';
|
||||
import { UpdateAgentInput } from 'src/engine/metadata-modules/agent/dtos/update-agent.input';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label.util';
|
||||
|
||||
import { AgentEntity } from './agent.entity';
|
||||
import { AgentException, AgentExceptionCode } from './agent.exception';
|
||||
@@ -18,15 +22,9 @@ export class AgentService {
|
||||
@InjectRepository(RoleTargetsEntity, 'core')
|
||||
private readonly roleTargetsRepository: Repository<RoleTargetsEntity>,
|
||||
private readonly agentChatService: AgentChatService,
|
||||
private readonly agentRoleService: AgentRoleService,
|
||||
) {}
|
||||
|
||||
async findManyAgents(workspaceId: string) {
|
||||
return this.agentRepository.find({
|
||||
where: { workspaceId },
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
}
|
||||
|
||||
async findOneAgent(id: string, workspaceId: string) {
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: { id, workspaceId },
|
||||
@@ -53,71 +51,63 @@ export class AgentService {
|
||||
};
|
||||
}
|
||||
|
||||
async createOneAgentAndFirstThread(
|
||||
input: {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
prompt: string;
|
||||
modelId: ModelId;
|
||||
},
|
||||
workspaceId: string,
|
||||
userWorkspaceId: string | null,
|
||||
) {
|
||||
const agent = await this.createOneAgent(input, workspaceId);
|
||||
|
||||
if (!userWorkspaceId) {
|
||||
throw new AgentException(
|
||||
'User workspace ID not found',
|
||||
AgentExceptionCode.USER_WORKSPACE_ID_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.agentChatService.createThread(agent.id, userWorkspaceId);
|
||||
|
||||
return agent;
|
||||
}
|
||||
|
||||
async createOneAgent(
|
||||
input: {
|
||||
name: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
prompt: string;
|
||||
modelId: ModelId;
|
||||
responseFormat?: object;
|
||||
},
|
||||
input: CreateAgentInput & { isCustom: boolean },
|
||||
workspaceId: string,
|
||||
) {
|
||||
const agent = this.agentRepository.create({
|
||||
...input,
|
||||
name: input.name || computeMetadataNameFromLabel(input.label),
|
||||
workspaceId,
|
||||
isCustom: input.isCustom,
|
||||
});
|
||||
|
||||
const createdAgent = await this.agentRepository.save(agent);
|
||||
|
||||
if (input.roleId) {
|
||||
await this.agentRoleService.assignRoleToAgent({
|
||||
workspaceId,
|
||||
agentId: createdAgent.id,
|
||||
roleId: input.roleId,
|
||||
});
|
||||
}
|
||||
|
||||
return this.findOneAgent(createdAgent.id, workspaceId);
|
||||
}
|
||||
|
||||
async updateOneAgent(
|
||||
input: {
|
||||
id: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
prompt?: string;
|
||||
modelId?: ModelId;
|
||||
responseFormat?: object;
|
||||
},
|
||||
workspaceId: string,
|
||||
) {
|
||||
async updateOneAgent(input: UpdateAgentInput, workspaceId: string) {
|
||||
const agent = await this.findOneAgent(input.id, workspaceId);
|
||||
|
||||
let updatedName = input.name;
|
||||
|
||||
if (input.label) {
|
||||
updatedName = computeMetadataNameFromLabel(input.label);
|
||||
}
|
||||
|
||||
const updatedAgent = await this.agentRepository.save({
|
||||
...agent,
|
||||
...input,
|
||||
name: updatedName,
|
||||
});
|
||||
|
||||
return updatedAgent;
|
||||
if (!isDefined(input.roleId)) {
|
||||
return updatedAgent;
|
||||
}
|
||||
|
||||
if (input.roleId) {
|
||||
await this.agentRoleService.assignRoleToAgent({
|
||||
workspaceId,
|
||||
agentId: agent.id,
|
||||
roleId: input.roleId,
|
||||
});
|
||||
} else {
|
||||
await this.agentRoleService.removeRoleFromAgent({
|
||||
workspaceId,
|
||||
agentId: agent.id,
|
||||
});
|
||||
}
|
||||
|
||||
return this.findOneAgent(updatedAgent.id, workspaceId);
|
||||
}
|
||||
|
||||
async deleteOneAgent(id: string, workspaceId: string) {
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export const AGENT_HANDOFF_DESCRIPTION_TEMPLATE =
|
||||
"Use this tool when the user's request requires {agentName}'s specialized expertise or capabilities. CRITICAL: You MUST call this tool function immediately. Do NOT respond with text about transferring - execute the tool instead. This is a FUNCTION CALL - you must invoke it, not describe it.";
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
export const AGENT_HANDOFF_PROMPT_TEMPLATE = `You have received a handoff from another AI agent who determined that you are better suited to handle this conversation based on your specialized knowledge and capabilities.
|
||||
|
||||
**Reason for handoff:** {reason}
|
||||
|
||||
**Context from the previous agent:**
|
||||
{context}
|
||||
|
||||
**Instructions:**
|
||||
- Continue the conversation naturally and professionally
|
||||
- Leverage your specialized expertise to provide the best possible assistance
|
||||
- Maintain context from the previous conversation while adding your unique value`;
|
||||
+12
-12
@@ -41,20 +41,20 @@ Guidelines:
|
||||
- Pay special attention to any data returned from tool executions (database queries, HTTP requests, record creation, etc.)`,
|
||||
|
||||
AGENT_CHAT: `You are a helpful AI assistant for this workspace. You can:
|
||||
- Answer questions conversationally, clearly, and helpfully
|
||||
- Provide insights, support, and updates about people, companies, opportunities, tasks, notes, and other business objects.
|
||||
- Answer questions about people, companies, opportunities, tasks, notes, and other business objects
|
||||
- Access and summarize information you have permission to see
|
||||
- Help users understand how to use the system and its features
|
||||
- Use various tools that are provided to you dynamically when needed
|
||||
- Use tools provided to you dynamically when needed
|
||||
- Transfer conversations to other specialized agents when their expertise is better suited
|
||||
|
||||
Permissions and capabilities:
|
||||
- You can only perform actions and access data that your assigned role and permissions allow
|
||||
- If a user requests something you do not have permission for, politely explain the limitation (e.g., "I cannot perform this operation because I don't have the necessary permissions. Please check your role or contact an admin.")
|
||||
- If you are unsure about your permissions for a specific action, ask the user for clarification or suggest they check with an administrator
|
||||
- Do not attempt to simulate or fake actions you cannot perform
|
||||
- Only use tools that are actually available to you through the tools property
|
||||
Permissions:
|
||||
- Only perform actions and access data that your assigned role and permissions allow
|
||||
- If you lack permissions, politely explain the limitation
|
||||
- Only use tools that are actually available to you
|
||||
|
||||
If you need more information to answer a question, ask follow-up questions. Always be transparent about your capabilities and limitations.
|
||||
Agent handoff:
|
||||
- Use handoff tools when the user's request requires expertise outside your capabilities
|
||||
- IMPORTANT: Do not respond with text about transferring - execute the handoff tool function
|
||||
- Use the response returned by the handoff agent as your reply to the user
|
||||
|
||||
When formatting responses:
|
||||
- Use markdown syntax to improve readability of long responses
|
||||
@@ -63,5 +63,5 @@ When formatting responses:
|
||||
- Create tables when presenting structured data
|
||||
- Use blockquotes for important notes or callouts
|
||||
|
||||
Note: This base system prompt will be combined with the agent's specific instructions and context to provide you with complete guidance for your role.`,
|
||||
Note: This base system prompt will be combined with the agent's specific instructions and context.`,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
import { AgentDTO } from './agent.dto';
|
||||
|
||||
@ObjectType()
|
||||
export class AgentHandoffDTO {
|
||||
@Field(() => UUIDScalarType)
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@Field(() => AgentDTO)
|
||||
toAgent: AgentDTO;
|
||||
}
|
||||
@@ -1,6 +1,12 @@
|
||||
import { Field, HideField, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IsDateString, IsNotEmpty, IsString, IsUUID } from 'class-validator';
|
||||
import {
|
||||
IsBoolean,
|
||||
IsDateString,
|
||||
IsNotEmpty,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
@@ -17,6 +23,14 @@ export class AgentDTO {
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@Field()
|
||||
label: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsString()
|
||||
@Field({ nullable: true })
|
||||
description: string;
|
||||
@@ -35,6 +49,10 @@ export class AgentDTO {
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
|
||||
@IsBoolean()
|
||||
@Field()
|
||||
isCustom: boolean;
|
||||
|
||||
@HideField()
|
||||
workspaceId: string;
|
||||
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@InputType()
|
||||
export class CreateAgentHandoffInput {
|
||||
@Field(() => UUIDScalarType)
|
||||
fromAgentId: string;
|
||||
|
||||
@Field(() => UUIDScalarType)
|
||||
toAgentId: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
description?: string;
|
||||
}
|
||||
+24
-2
@@ -1,16 +1,33 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsObject, IsOptional, IsString } from 'class-validator';
|
||||
import {
|
||||
IsNotEmpty,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ModelId } from 'src/engine/core-modules/ai/constants/ai-models.const';
|
||||
|
||||
@InputType()
|
||||
export class CreateAgentInput {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
name?: string;
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
name: string;
|
||||
label: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@@ -27,6 +44,11 @@ export class CreateAgentInput {
|
||||
@Field(() => String)
|
||||
modelId: ModelId;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class RemoveAgentHandoffInput {
|
||||
@Field()
|
||||
fromAgentId: string;
|
||||
|
||||
@Field()
|
||||
toAgentId: string;
|
||||
}
|
||||
@@ -24,6 +24,16 @@ export class UpdateAgentInput {
|
||||
@Field()
|
||||
name?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field()
|
||||
label?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
icon?: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
@@ -39,6 +49,11 @@ export class UpdateAgentInput {
|
||||
@Field(() => String)
|
||||
modelId?: ModelId;
|
||||
|
||||
@IsUUID()
|
||||
@IsOptional()
|
||||
@Field(() => UUIDScalarType, { nullable: true })
|
||||
roleId?: string;
|
||||
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
@Field(() => GraphQLJSON, { nullable: true })
|
||||
|
||||
@@ -217,6 +217,7 @@ export class WorkspaceManagerService {
|
||||
description: 'Default Routing Agent',
|
||||
prompt: '',
|
||||
modelId: 'auto',
|
||||
isCustom: false,
|
||||
},
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
+8
-22
@@ -4,9 +4,9 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
import { StepStatus } from 'twenty-shared/workflow';
|
||||
|
||||
import { BASE_TYPESCRIPT_PROJECT_INPUT_SCHEMA } from 'src/engine/core-modules/serverless/drivers/constants/base-typescript-project-input-schema';
|
||||
import { CreateWorkflowVersionStepInput } from 'src/engine/core-modules/workflow/dtos/create-workflow-version-step-input.dto';
|
||||
@@ -403,12 +403,16 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
break;
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
if (!isDefined(step.settings.input.agentId)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const agent = await this.agentService.findOneAgent(
|
||||
step.settings.input.agentId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (agent) {
|
||||
if (isDefined(agent)) {
|
||||
await this.agentService.deleteOneAgent(agent.id, workspaceId);
|
||||
}
|
||||
break;
|
||||
@@ -611,25 +615,6 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
};
|
||||
}
|
||||
case WorkflowActionType.AI_AGENT: {
|
||||
const newAgent = await this.agentService.createOneAgentAndFirstThread(
|
||||
{
|
||||
label: 'AI Agent Workflow Step',
|
||||
name: `ai-agent-workflow-${newStepId}`,
|
||||
description: 'Created automatically for workflow step',
|
||||
prompt: '',
|
||||
modelId: 'auto',
|
||||
},
|
||||
workspaceId,
|
||||
this.scopedWorkspaceContextFactory.create().userWorkspaceId,
|
||||
);
|
||||
|
||||
if (!isDefined(newAgent)) {
|
||||
throw new WorkflowVersionStepException(
|
||||
'Failed to create AI Agent Step',
|
||||
WorkflowVersionStepExceptionCode.FAILURE,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: newStepId,
|
||||
name: 'AI Agent',
|
||||
@@ -638,7 +623,8 @@ export class WorkflowVersionStepWorkspaceService {
|
||||
settings: {
|
||||
...BASE_STEP_DEFINITION,
|
||||
input: {
|
||||
agentId: newAgent.id,
|
||||
agentId: '',
|
||||
prompt: '',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
+15
-9
@@ -18,6 +18,7 @@ import {
|
||||
} from 'src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception';
|
||||
import { WorkflowActionInput } from 'src/modules/workflow/workflow-executor/types/workflow-action-input';
|
||||
import { WorkflowActionOutput } from 'src/modules/workflow/workflow-executor/types/workflow-action-output.type';
|
||||
import { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
|
||||
import { isWorkflowAiAgentAction } from './guards/is-workflow-ai-agent-action.guard';
|
||||
|
||||
@@ -51,18 +52,22 @@ export class AiAgentWorkflowAction implements WorkflowAction {
|
||||
);
|
||||
}
|
||||
|
||||
const { agentId } = step.settings.input;
|
||||
const { agentId, prompt } = step.settings.input;
|
||||
const workspaceId = context.workspaceId as string;
|
||||
|
||||
try {
|
||||
const agent = await this.agentRepository.findOne({
|
||||
where: {
|
||||
id: agentId,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
let agent: AgentEntity | null = null;
|
||||
|
||||
if (!agent) {
|
||||
if (agentId) {
|
||||
agent = await this.agentRepository.findOne({
|
||||
where: {
|
||||
id: agentId,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (agentId && !agent) {
|
||||
throw new AgentException(
|
||||
`Agent with id ${agentId} not found`,
|
||||
AgentExceptionCode.AGENT_NOT_FOUND,
|
||||
@@ -73,10 +78,11 @@ export class AiAgentWorkflowAction implements WorkflowAction {
|
||||
agent,
|
||||
context,
|
||||
schema: step.settings.outputSchema,
|
||||
userPrompt: resolveInput(prompt, context) as string,
|
||||
});
|
||||
|
||||
await this.aiBillingService.calculateAndBillUsage(
|
||||
agent.modelId,
|
||||
agent?.modelId ?? 'auto',
|
||||
usage,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
export type WorkflowAiAgentActionInput = {
|
||||
agentId: string;
|
||||
agentId?: string;
|
||||
prompt?: string;
|
||||
};
|
||||
|
||||
+53
@@ -1,11 +1,18 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { AgentRoleService } from 'src/engine/metadata-modules/agent-role/agent-role.service';
|
||||
import { AgentChatService } from 'src/engine/metadata-modules/agent/agent-chat.service';
|
||||
import { AgentHandoffEntity } from 'src/engine/metadata-modules/agent/agent-handoff.entity';
|
||||
import { AgentHandoffService } from 'src/engine/metadata-modules/agent/agent-handoff.service';
|
||||
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
|
||||
import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} from 'src/engine/metadata-modules/agent/agent.exception';
|
||||
import { AgentResolver } from 'src/engine/metadata-modules/agent/agent.resolver';
|
||||
import { AgentService } from 'src/engine/metadata-modules/agent/agent.service';
|
||||
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
|
||||
|
||||
// Mock the agent service
|
||||
jest.mock('../../../../../src/engine/metadata-modules/agent/agent.service');
|
||||
@@ -41,6 +48,52 @@ describe('agentResolver', () => {
|
||||
updateOneAgent: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AgentHandoffService,
|
||||
useValue: {
|
||||
getHandoffTargets: jest.fn(),
|
||||
canHandoffTo: jest.fn(),
|
||||
createHandoff: jest.fn(),
|
||||
removeHandoff: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(AgentEntity, 'core'),
|
||||
useValue: {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
softDelete: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(AgentHandoffEntity, 'core'),
|
||||
useValue: {
|
||||
find: jest.fn(),
|
||||
findOne: jest.fn(),
|
||||
save: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: getRepositoryToken(RoleTargetsEntity, 'core'),
|
||||
useValue: {
|
||||
findOne: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AgentChatService,
|
||||
useValue: {
|
||||
createThread: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AgentRoleService,
|
||||
useValue: {
|
||||
assignRoleToAgent: jest.fn(),
|
||||
removeRoleFromAgent: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
||||
+1
@@ -119,6 +119,7 @@ export const createAgentToolTestModule =
|
||||
useValue: {
|
||||
getHandoffTargets: jest.fn().mockResolvedValue([]),
|
||||
canHandoffTo: jest.fn().mockResolvedValue(true),
|
||||
getAgentHandoffs: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user