diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index a0fec64e4d3..1e90440a875 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -53,6 +53,7 @@ export type Agent = { applicationId?: Maybe; createdAt: Scalars['DateTime']; description?: Maybe; + evaluationInputs: Array; icon?: Maybe; id: Scalars['UUID']; isCustom: Scalars['Boolean']; @@ -67,17 +68,32 @@ export type Agent = { updatedAt: Scalars['DateTime']; }; -export type AgentChatMessage = { - __typename?: 'AgentChatMessage'; +export type AgentChatThread = { + __typename?: 'AgentChatThread'; createdAt: Scalars['DateTime']; id: Scalars['UUID']; - parts: Array; - role: Scalars['String']; - threadId: Scalars['UUID']; + title?: Maybe; + updatedAt: Scalars['DateTime']; }; -export type AgentChatMessagePart = { - __typename?: 'AgentChatMessagePart'; +export type AgentIdInput = { + /** The id of the agent. */ + id: Scalars['UUID']; +}; + +export type AgentMessage = { + __typename?: 'AgentMessage'; + agentId?: Maybe; + createdAt: Scalars['DateTime']; + id: Scalars['UUID']; + parts: Array; + role: Scalars['String']; + threadId: Scalars['UUID']; + turnId: Scalars['UUID']; +}; + +export type AgentMessagePart = { + __typename?: 'AgentMessagePart'; createdAt: Scalars['DateTime']; errorDetails?: Maybe; errorMessage?: Maybe; @@ -105,17 +121,23 @@ export type AgentChatMessagePart = { type: Scalars['String']; }; -export type AgentChatThread = { - __typename?: 'AgentChatThread'; +export type AgentTurn = { + __typename?: 'AgentTurn'; + agentId?: Maybe; createdAt: Scalars['DateTime']; + evaluations: Array; id: Scalars['UUID']; - title?: Maybe; - updatedAt: Scalars['DateTime']; + messages: Array; + threadId: Scalars['UUID']; }; -export type AgentIdInput = { - /** The id of the agent. */ +export type AgentTurnEvaluation = { + __typename?: 'AgentTurnEvaluation'; + comment?: Maybe; + createdAt: Scalars['DateTime']; id: Scalars['UUID']; + score: Scalars['Int']; + turnId: Scalars['UUID']; }; export type AggregateChartConfiguration = { @@ -751,6 +773,7 @@ export type CoreViewSort = { export type CreateAgentInput = { description?: InputMaybe; + evaluationInputs?: InputMaybe>; icon?: InputMaybe; label: Scalars['String']; modelConfiguration?: InputMaybe; @@ -1830,6 +1853,7 @@ export type Mutation = { emailPasswordResetLink: EmailPasswordResetLinkOutput; enablePostgresProxy: PostgresCredentials; endSubscriptionTrialPeriod: BillingEndTrialPeriodOutput; + evaluateAgentTurn: AgentTurnEvaluation; executeOneServerlessFunction: ServerlessFunctionExecutionResult; generateApiKeyToken: ApiKeyToken; generateTransientToken: TransientTokenOutput; @@ -1850,6 +1874,7 @@ export type Mutation = { restorePageLayoutWidget: PageLayoutWidget; retryJobs: RetryJobsResponse; revokeApiKey?: Maybe; + runEvaluationInput: AgentTurn; runWorkflowVersion: RunWorkflowVersionOutput; saveImapSmtpCaldavAccount: ImapSmtpCaldavConnectionSuccess; sendInvitations: SendInvitationsOutput; @@ -2380,6 +2405,11 @@ export type MutationEmailPasswordResetLinkArgs = { }; +export type MutationEvaluateAgentTurnArgs = { + turnId: Scalars['UUID']; +}; + + export type MutationExecuteOneServerlessFunctionArgs = { input: ExecuteServerlessFunctionInput; }; @@ -2484,6 +2514,12 @@ export type MutationRevokeApiKeyArgs = { }; +export type MutationRunEvaluationInputArgs = { + agentId: Scalars['UUID']; + input: Scalars['String']; +}; + + export type MutationRunWorkflowVersionArgs = { input: RunWorkflowVersionInput; }; @@ -3084,6 +3120,7 @@ export enum PermissionFlagType { DATA_MODEL = 'DATA_MODEL', DOWNLOAD_FILE = 'DOWNLOAD_FILE', EXPORT_CSV = 'EXPORT_CSV', + HTTP_REQUEST_TOOL = 'HTTP_REQUEST_TOOL', IMPERSONATE = 'IMPERSONATE', IMPORT_CSV = 'IMPORT_CSV', LAYOUTS = 'LAYOUTS', @@ -3172,10 +3209,11 @@ export type PublishServerlessFunctionInput = { export type Query = { __typename?: 'Query'; + agentTurns: Array; apiKey?: Maybe; apiKeys: Array; billingPortalSession: BillingSessionOutput; - chatMessages: Array; + chatMessages: Array; chatThread: AgentChatThread; chatThreads: Array; checkUserExists: CheckUserExistOutput; @@ -3257,6 +3295,11 @@ export type Query = { }; +export type QueryAgentTurnsArgs = { + agentId: Scalars['UUID']; +}; + + export type QueryApiKeyArgs = { input: GetApiKeyInput; }; @@ -4124,6 +4167,7 @@ export type UuidFilterComparison = { export type UpdateAgentInput = { description?: InputMaybe; + evaluationInputs?: InputMaybe>; icon?: InputMaybe; id: Scalars['UUID']; label?: InputMaybe; @@ -4904,7 +4948,7 @@ export type WorkspaceUrlsAndId = { workspaceUrls: WorkspaceUrls; }; -export type AgentFieldsFragment = { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }; +export type AgentFieldsFragment = { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }; export type AssignRoleToAgentMutationVariables = Exact<{ agentId: Scalars['UUID']; @@ -4924,14 +4968,21 @@ export type CreateOneAgentMutationVariables = Exact<{ }>; -export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type CreateOneAgentMutation = { __typename?: 'Mutation', createOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; export type DeleteOneAgentMutationVariables = Exact<{ input: AgentIdInput; }>; -export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type DeleteOneAgentMutation = { __typename?: 'Mutation', deleteOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; + +export type EvaluateAgentTurnMutationVariables = Exact<{ + turnId: Scalars['UUID']; +}>; + + +export type EvaluateAgentTurnMutation = { __typename?: 'Mutation', evaluateAgentTurn: { __typename?: 'AgentTurnEvaluation', id: string, turnId: string, score: number, comment?: string | null, createdAt: string } }; export type RemoveRoleFromAgentMutationVariables = Exact<{ agentId: Scalars['UUID']; @@ -4940,31 +4991,46 @@ export type RemoveRoleFromAgentMutationVariables = Exact<{ export type RemoveRoleFromAgentMutation = { __typename?: 'Mutation', removeRoleFromAgent: boolean }; +export type RunEvaluationInputMutationVariables = Exact<{ + agentId: Scalars['UUID']; + input: Scalars['String']; +}>; + + +export type RunEvaluationInputMutation = { __typename?: 'Mutation', runEvaluationInput: { __typename?: 'AgentTurn', id: string, threadId: string, agentId?: string | null, createdAt: string, evaluations: Array<{ __typename?: 'AgentTurnEvaluation', id: string, score: number, comment?: string | null, createdAt: string }> } }; + export type UpdateOneAgentMutationVariables = Exact<{ input: UpdateAgentInput; }>; -export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; export type FindManyAgentsQueryVariables = Exact<{ [key: string]: never; }>; -export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }> }; +export type FindManyAgentsQuery = { __typename?: 'Query', findManyAgents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }> }; export type FindOneAgentQueryVariables = Exact<{ id: Scalars['UUID']; }>; -export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string } }; +export type FindOneAgentQuery = { __typename?: 'Query', findOneAgent: { __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string } }; + +export type GetAgentTurnsQueryVariables = Exact<{ + agentId: Scalars['UUID']; +}>; + + +export type GetAgentTurnsQuery = { __typename?: 'Query', agentTurns: Array<{ __typename?: 'AgentTurn', id: string, threadId: string, agentId?: string | null, createdAt: string, evaluations: Array<{ __typename?: 'AgentTurnEvaluation', id: string, score: number, comment?: string | null, createdAt: string }>, messages: Array<{ __typename?: 'AgentMessage', id: string, role: string, createdAt: string, parts: Array<{ __typename?: 'AgentMessagePart', id: string, type: string, textContent?: string | null, reasoningContent?: string | null, toolName?: string | null, toolCallId?: string | null, toolInput?: any | null, toolOutput?: any | null, errorMessage?: string | null, state?: string | null, errorDetails?: any | null, sourceUrlSourceId?: string | null, sourceUrlUrl?: string | null, sourceUrlTitle?: string | null, sourceDocumentSourceId?: string | null, sourceDocumentMediaType?: string | null, sourceDocumentTitle?: string | null, sourceDocumentFilename?: string | null, fileMediaType?: string | null, fileFilename?: string | null, fileUrl?: string | null, providerMetadata?: any | null }> }> }> }; export type GetChatMessagesQueryVariables = Exact<{ threadId: Scalars['UUID']; }>; -export type GetChatMessagesQuery = { __typename?: 'Query', chatMessages: Array<{ __typename?: 'AgentChatMessage', id: string, threadId: string, role: string, createdAt: string, parts: Array<{ __typename?: 'AgentChatMessagePart', id: string, messageId: string, orderIndex: number, type: string, textContent?: string | null, reasoningContent?: string | null, toolName?: string | null, toolCallId?: string | null, toolInput?: any | null, toolOutput?: any | null, state?: string | null, errorMessage?: string | null, errorDetails?: any | null, sourceUrlSourceId?: string | null, sourceUrlUrl?: string | null, sourceUrlTitle?: string | null, sourceDocumentSourceId?: string | null, sourceDocumentMediaType?: string | null, sourceDocumentTitle?: string | null, sourceDocumentFilename?: string | null, fileMediaType?: string | null, fileFilename?: string | null, fileUrl?: string | null, providerMetadata?: any | null, createdAt: string }> }> }; +export type GetChatMessagesQuery = { __typename?: 'Query', chatMessages: Array<{ __typename?: 'AgentMessage', id: string, threadId: string, turnId: string, role: string, createdAt: string, parts: Array<{ __typename?: 'AgentMessagePart', id: string, messageId: string, orderIndex: number, type: string, textContent?: string | null, reasoningContent?: string | null, toolName?: string | null, toolCallId?: string | null, toolInput?: any | null, toolOutput?: any | null, state?: string | null, errorMessage?: string | null, errorDetails?: any | null, sourceUrlSourceId?: string | null, sourceUrlUrl?: string | null, sourceUrlTitle?: string | null, sourceDocumentSourceId?: string | null, sourceDocumentMediaType?: string | null, sourceDocumentTitle?: string | null, sourceDocumentFilename?: string | null, fileMediaType?: string | null, fileFilename?: string | null, fileUrl?: string | null, providerMetadata?: any | null, createdAt: string }> }> }; export type GetChatThreadsQueryVariables = Exact<{ [key: string]: never; }>; @@ -4990,7 +5056,7 @@ export type UpdateOneApplicationVariableMutationVariables = Exact<{ export type UpdateOneApplicationVariableMutation = { __typename?: 'Mutation', updateOneApplicationVariable: boolean }; -export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, serverlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, handlerPath: string, handlerName: string, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }> }; +export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, serverlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, handlerPath: string, handlerName: string, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }> }; export type FindManyApplicationsQueryVariables = Exact<{ [key: string]: never; }>; @@ -5002,7 +5068,7 @@ export type FindOneApplicationQueryVariables = Exact<{ }>; -export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, serverlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, handlerPath: string, handlerName: string, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }> } }; +export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId?: string | null, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, serverlessFunctions: Array<{ __typename?: 'ServerlessFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, latestVersion?: string | null, publishedVersions: Array, handlerPath: string, handlerName: string, createdAt: string, updatedAt: string, cronTriggers?: Array<{ __typename?: 'CronTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, databaseEventTriggers?: Array<{ __typename?: 'DatabaseEventTrigger', id: string, settings: any, createdAt: string, updatedAt: string }> | null, routeTriggers?: Array<{ __typename?: 'RouteTrigger', id: string, path: string, isAuthRequired: boolean, httpMethod: HttpMethod, createdAt: string, updatedAt: string }> | null }> } }; export type UploadFileMutationVariables = Exact<{ file: Scalars['Upload']; @@ -5782,7 +5848,7 @@ export type UpsertPermissionFlagsMutation = { __typename?: 'Mutation', upsertPer export type GetRolesQueryVariables = Exact<{ [key: string]: never; }>; -export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, applicationId?: string | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null }> }; +export type GetRolesQuery = { __typename?: 'Query', getRoles: Array<{ __typename?: 'Role', id: string, label: string, description?: string | null, icon?: string | null, canUpdateAllSettings: boolean, canAccessAllTools: boolean, isEditable: boolean, canReadAllObjectRecords: boolean, canUpdateAllObjectRecords: boolean, canSoftDeleteAllObjectRecords: boolean, canDestroyAllObjectRecords: boolean, canBeAssignedToUsers: boolean, canBeAssignedToAgents: boolean, canBeAssignedToApiKeys: boolean, workspaceMembers: Array<{ __typename?: 'WorkspaceMember', id: string, avatarUrl?: string | null, userEmail: string, name: { __typename?: 'FullName', firstName: string, lastName: string } }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array, applicationId?: string | null, createdAt: string, updatedAt: string }>, apiKeys: Array<{ __typename?: 'ApiKeyForRole', id: string, name: string, expiresAt: string, revokedAt?: string | null }>, permissionFlags?: Array<{ __typename?: 'PermissionFlag', id: string, flag: PermissionFlagType, roleId: string }> | null, objectPermissions?: Array<{ __typename?: 'ObjectPermission', objectMetadataId: string, canReadObjectRecords?: boolean | null, canUpdateObjectRecords?: boolean | null, canSoftDeleteObjectRecords?: boolean | null, canDestroyObjectRecords?: boolean | null, restrictedFields?: any | null }> | null, fieldPermissions?: Array<{ __typename?: 'FieldPermission', objectMetadataId: string, fieldMetadataId: string, canReadFieldValue?: boolean | null, canUpdateFieldValue?: boolean | null, id: string, roleId: string }> | null }> }; export type CreateApprovedAccessDomainMutationVariables = Exact<{ input: CreateApprovedAccessDomainInput; @@ -6435,6 +6501,7 @@ export const AgentFieldsFragmentDoc = gql` roleId isCustom modelConfiguration + evaluationInputs applicationId createdAt updatedAt @@ -7211,6 +7278,43 @@ export function useDeleteOneAgentMutation(baseOptions?: Apollo.MutationHookOptio export type DeleteOneAgentMutationHookResult = ReturnType; export type DeleteOneAgentMutationResult = Apollo.MutationResult; export type DeleteOneAgentMutationOptions = Apollo.BaseMutationOptions; +export const EvaluateAgentTurnDocument = gql` + mutation EvaluateAgentTurn($turnId: UUID!) { + evaluateAgentTurn(turnId: $turnId) { + id + turnId + score + comment + createdAt + } +} + `; +export type EvaluateAgentTurnMutationFn = Apollo.MutationFunction; + +/** + * __useEvaluateAgentTurnMutation__ + * + * To run a mutation, you first call `useEvaluateAgentTurnMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useEvaluateAgentTurnMutation` 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 [evaluateAgentTurnMutation, { data, loading, error }] = useEvaluateAgentTurnMutation({ + * variables: { + * turnId: // value for 'turnId' + * }, + * }); + */ +export function useEvaluateAgentTurnMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(EvaluateAgentTurnDocument, options); + } +export type EvaluateAgentTurnMutationHookResult = ReturnType; +export type EvaluateAgentTurnMutationResult = Apollo.MutationResult; +export type EvaluateAgentTurnMutationOptions = Apollo.BaseMutationOptions; export const RemoveRoleFromAgentDocument = gql` mutation RemoveRoleFromAgent($agentId: UUID!) { removeRoleFromAgent(agentId: $agentId) @@ -7242,6 +7346,49 @@ export function useRemoveRoleFromAgentMutation(baseOptions?: Apollo.MutationHook export type RemoveRoleFromAgentMutationHookResult = ReturnType; export type RemoveRoleFromAgentMutationResult = Apollo.MutationResult; export type RemoveRoleFromAgentMutationOptions = Apollo.BaseMutationOptions; +export const RunEvaluationInputDocument = gql` + mutation RunEvaluationInput($agentId: UUID!, $input: String!) { + runEvaluationInput(agentId: $agentId, input: $input) { + id + threadId + agentId + createdAt + evaluations { + id + score + comment + createdAt + } + } +} + `; +export type RunEvaluationInputMutationFn = Apollo.MutationFunction; + +/** + * __useRunEvaluationInputMutation__ + * + * To run a mutation, you first call `useRunEvaluationInputMutation` within a React component and pass it any options that fit your needs. + * When your component renders, `useRunEvaluationInputMutation` 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 [runEvaluationInputMutation, { data, loading, error }] = useRunEvaluationInputMutation({ + * variables: { + * agentId: // value for 'agentId' + * input: // value for 'input' + * }, + * }); + */ +export function useRunEvaluationInputMutation(baseOptions?: Apollo.MutationHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(RunEvaluationInputDocument, options); + } +export type RunEvaluationInputMutationHookResult = ReturnType; +export type RunEvaluationInputMutationResult = Apollo.MutationResult; +export type RunEvaluationInputMutationOptions = Apollo.BaseMutationOptions; export const UpdateOneAgentDocument = gql` mutation UpdateOneAgent($input: UpdateAgentInput!) { updateOneAgent(input: $input) { @@ -7344,11 +7491,85 @@ export function useFindOneAgentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptio export type FindOneAgentQueryHookResult = ReturnType; export type FindOneAgentLazyQueryHookResult = ReturnType; export type FindOneAgentQueryResult = Apollo.QueryResult; +export const GetAgentTurnsDocument = gql` + query GetAgentTurns($agentId: UUID!) { + agentTurns(agentId: $agentId) { + id + threadId + agentId + createdAt + evaluations { + id + score + comment + createdAt + } + messages { + id + role + createdAt + parts { + id + type + textContent + reasoningContent + toolName + toolCallId + toolInput + toolOutput + errorMessage + state + errorDetails + sourceUrlSourceId + sourceUrlUrl + sourceUrlTitle + sourceDocumentSourceId + sourceDocumentMediaType + sourceDocumentTitle + sourceDocumentFilename + fileMediaType + fileFilename + fileUrl + providerMetadata + } + } + } +} + `; + +/** + * __useGetAgentTurnsQuery__ + * + * To run a query within a React component, call `useGetAgentTurnsQuery` and pass it any options that fit your needs. + * When your component renders, `useGetAgentTurnsQuery` 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 } = useGetAgentTurnsQuery({ + * variables: { + * agentId: // value for 'agentId' + * }, + * }); + */ +export function useGetAgentTurnsQuery(baseOptions: Apollo.QueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(GetAgentTurnsDocument, options); + } +export function useGetAgentTurnsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(GetAgentTurnsDocument, options); + } +export type GetAgentTurnsQueryHookResult = ReturnType; +export type GetAgentTurnsLazyQueryHookResult = ReturnType; +export type GetAgentTurnsQueryResult = Apollo.QueryResult; export const GetChatMessagesDocument = gql` query GetChatMessages($threadId: UUID!) { chatMessages(threadId: $threadId) { id threadId + turnId role createdAt parts { diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index f89b6051ef9..042bcee9f5a 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -53,6 +53,7 @@ export type Agent = { applicationId?: Maybe; createdAt: Scalars['DateTime']; description?: Maybe; + evaluationInputs: Array; icon?: Maybe; id: Scalars['UUID']; isCustom: Scalars['Boolean']; @@ -67,17 +68,32 @@ export type Agent = { updatedAt: Scalars['DateTime']; }; -export type AgentChatMessage = { - __typename?: 'AgentChatMessage'; +export type AgentChatThread = { + __typename?: 'AgentChatThread'; createdAt: Scalars['DateTime']; id: Scalars['UUID']; - parts: Array; - role: Scalars['String']; - threadId: Scalars['UUID']; + title?: Maybe; + updatedAt: Scalars['DateTime']; }; -export type AgentChatMessagePart = { - __typename?: 'AgentChatMessagePart'; +export type AgentIdInput = { + /** The id of the agent. */ + id: Scalars['UUID']; +}; + +export type AgentMessage = { + __typename?: 'AgentMessage'; + agentId?: Maybe; + createdAt: Scalars['DateTime']; + id: Scalars['UUID']; + parts: Array; + role: Scalars['String']; + threadId: Scalars['UUID']; + turnId: Scalars['UUID']; +}; + +export type AgentMessagePart = { + __typename?: 'AgentMessagePart'; createdAt: Scalars['DateTime']; errorDetails?: Maybe; errorMessage?: Maybe; @@ -105,17 +121,23 @@ export type AgentChatMessagePart = { type: Scalars['String']; }; -export type AgentChatThread = { - __typename?: 'AgentChatThread'; +export type AgentTurn = { + __typename?: 'AgentTurn'; + agentId?: Maybe; createdAt: Scalars['DateTime']; + evaluations: Array; id: Scalars['UUID']; - title?: Maybe; - updatedAt: Scalars['DateTime']; + messages: Array; + threadId: Scalars['UUID']; }; -export type AgentIdInput = { - /** The id of the agent. */ +export type AgentTurnEvaluation = { + __typename?: 'AgentTurnEvaluation'; + comment?: Maybe; + createdAt: Scalars['DateTime']; id: Scalars['UUID']; + score: Scalars['Int']; + turnId: Scalars['UUID']; }; export type AggregateChartConfiguration = { @@ -751,6 +773,7 @@ export type CoreViewSort = { export type CreateAgentInput = { description?: InputMaybe; + evaluationInputs?: InputMaybe>; icon?: InputMaybe; label: Scalars['String']; modelConfiguration?: InputMaybe; @@ -3018,6 +3041,7 @@ export enum PermissionFlagType { DATA_MODEL = 'DATA_MODEL', DOWNLOAD_FILE = 'DOWNLOAD_FILE', EXPORT_CSV = 'EXPORT_CSV', + HTTP_REQUEST_TOOL = 'HTTP_REQUEST_TOOL', IMPERSONATE = 'IMPERSONATE', IMPORT_CSV = 'IMPORT_CSV', LAYOUTS = 'LAYOUTS', @@ -3980,6 +4004,7 @@ export type UuidFilterComparison = { export type UpdateAgentInput = { description?: InputMaybe; + evaluationInputs?: InputMaybe>; icon?: InputMaybe; id: Scalars['UUID']; label?: InputMaybe; diff --git a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx index 477874736d2..f813afde045 100644 --- a/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx +++ b/packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx @@ -4,7 +4,7 @@ import { useRecoilValue } from 'recoil'; import { Avatar, IconSparkles } from 'twenty-ui/display'; import { AgentChatFilePreview } from '@/ai/components/internal/AgentChatFilePreview'; -import { AgentChatMessageRole } from '@/ai/constants/AgentChatMessageRole'; +import { AgentMessageRole } from '@/ai/constants/AgentMessageRole'; import { AIChatAssistantMessageRenderer } from '@/ai/components/AIChatAssistantMessageRenderer'; import { AIChatErrorMessage } from '@/ai/components/AIChatErrorMessage'; @@ -161,17 +161,17 @@ export const AIChatMessage = ({ ); const showError = - isDefined(error) && message.role === AgentChatMessageRole.ASSISTANT; + isDefined(error) && message.role === AgentMessageRole.ASSISTANT; const fileParts = message.parts.filter((part) => part.type === 'file'); return ( - {message.role === AgentChatMessageRole.ASSISTANT && ( + {message.role === AgentMessageRole.ASSISTANT && ( )} - {message.role === AgentChatMessageRole.USER && ( + {message.role === AgentMessageRole.USER && ( )} - + ({ key: 'agentChatMessagesComponentState', defaultValue: [], diff --git a/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts b/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts index 368bdf53213..aaf65b4eb0a 100644 --- a/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts +++ b/packages/twenty-front/src/modules/ai/utils/mapDBMessagesToUIMessages.ts @@ -1,9 +1,9 @@ import { mapDBPartToUIMessagePart } from '@/ai/utils/mapDBPartToUIMessagePart'; import { type ExtendedUIMessage } from 'twenty-shared/ai'; -import { type AgentChatMessage } from '~/generated/graphql'; +import { type AgentMessage } from '~/generated/graphql'; export const mapDBMessagesToUIMessages = ( - dbMessages: AgentChatMessage[], + dbMessages: AgentMessage[], ): ExtendedUIMessage[] => { return dbMessages.map((dbMessage) => ({ id: dbMessage.id, diff --git a/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts b/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts index f3414bebd02..5bb6d60a0a0 100644 --- a/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts +++ b/packages/twenty-front/src/modules/ai/utils/mapDBPartToUIMessagePart.ts @@ -1,9 +1,9 @@ import { type ReasoningUIPart, type ToolUIPart } from 'ai'; import { type ExtendedUIMessagePart } from 'twenty-shared/ai'; -import { type AgentChatMessagePart } from '~/generated/graphql'; +import { type AgentMessagePart } from '~/generated/graphql'; export const mapDBPartToUIMessagePart = ( - part: AgentChatMessagePart, + part: AgentMessagePart, ): ExtendedUIMessagePart => { switch (part.type) { case 'text': diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index d4bfe5b3624..f6cdef944c7 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -165,6 +165,12 @@ const SettingsAgentForm = lazy(() => })), ); +const SettingsAgentTurnDetail = lazy(() => + import('~/pages/settings/ai/SettingsAgentTurnDetail').then((module) => ({ + default: module.SettingsAgentTurnDetail, + })), +); + const SettingsWorkspaceMembers = lazy(() => import('~/pages/settings/SettingsWorkspaceMembers').then((module) => ({ default: module.SettingsWorkspaceMembers, @@ -422,6 +428,10 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( path={SettingsPath.AIAgentDetail} element={} /> + } + /> } /> } /> theme.spacing(1)}; + min-width: 0; + overflow: hidden; +`; + +const StyledLabel = styled.span` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; `; const StyledDescription = styled.span` @@ -81,7 +89,7 @@ export const SettingsListItemCardContent = ({ /> )} - {label} + {label} {!!description && {description}} diff --git a/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx index 966451480f6..94a53549e66 100644 --- a/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx +++ b/packages/twenty-front/src/modules/settings/roles/role-permissions/permission-flags/components/SettingsRolePermissionsSettingsSection.tsx @@ -68,78 +68,117 @@ export const SettingsRolePermissionsSettingsSection = ({ name: t`API Keys & Webhooks`, description: t`Manage API keys and webhooks`, Icon: IconCode, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.WORKSPACE, name: t`Workspace`, description: t`Set global workspace preferences`, Icon: IconSettings, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.WORKSPACE_MEMBERS, name: t`Users`, description: t`Add or remove users`, Icon: IconUsers, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.ROLES, name: t`Roles`, description: t`Define user roles and access levels`, Icon: IconLockOpen, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.DATA_MODEL, name: t`Data Model`, description: t`Edit data structure and fields`, Icon: IconHierarchy, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.SECURITY, name: t`Security`, description: t`Manage security policies`, Icon: IconKey, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.WORKFLOWS, name: t`Workflows`, description: t`Manage workflows`, Icon: IconSettingsAutomation, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.SSO_BYPASS, name: t`SSO Bypass`, description: t`Enable bypass options`, Icon: IconShield, + isRelevantForAgents: false, + isRelevantForApiKeys: false, + isRelevantForUsers: true, }, { key: PermissionFlagType.IMPERSONATE, name: t`Impersonate`, description: t`Impersonate workspace users`, Icon: IconSpy, + isRelevantForAgents: false, + isRelevantForApiKeys: false, + isRelevantForUsers: true, }, { key: PermissionFlagType.APPLICATIONS, name: t`Applications`, description: t`Install and manage applications`, Icon: IconApps, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.LAYOUTS, name: t`Layouts`, description: t`Customize page layouts and UI structure`, Icon: IconLayoutSidebarRightCollapse, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, { key: PermissionFlagType.BILLING, name: t`Billing`, description: t`Manage billing and subscriptions`, Icon: IconCreditCard, + isRelevantForAgents: false, + isRelevantForApiKeys: false, + isRelevantForUsers: true, }, { key: PermissionFlagType.AI_SETTINGS, name: t`AI`, description: t`Create and configure AI agents`, Icon: IconSparkles, + isRelevantForAgents: true, + isRelevantForApiKeys: true, + isRelevantForUsers: true, }, ]; @@ -153,30 +192,67 @@ export const SettingsRolePermissionsSettingsSection = ({ ) { return false; } + + // Filter based on role assignment capabilities + const canBeAssignedOnlyToAgents = + settingsDraftRole.canBeAssignedToAgents && + !settingsDraftRole.canBeAssignedToUsers && + !settingsDraftRole.canBeAssignedToApiKeys; + + const canBeAssignedOnlyToApiKeys = + settingsDraftRole.canBeAssignedToApiKeys && + !settingsDraftRole.canBeAssignedToUsers && + !settingsDraftRole.canBeAssignedToAgents; + + const canBeAssignedOnlyToUsers = + settingsDraftRole.canBeAssignedToUsers && + !settingsDraftRole.canBeAssignedToAgents && + !settingsDraftRole.canBeAssignedToApiKeys; + + if (canBeAssignedOnlyToAgents && !permission.isRelevantForAgents) { + return false; + } + + if (canBeAssignedOnlyToApiKeys && !permission.isRelevantForApiKeys) { + return false; + } + + if (canBeAssignedOnlyToUsers && !permission.isRelevantForUsers) { + return false; + } + return true; }); - }, [isAIEnabled, isApplicationEnabled]); + }, [isAIEnabled, isApplicationEnabled, settingsDraftRole]); + + const shouldShowAllAccessToggle = + !settingsDraftRole.canBeAssignedToAgents || + settingsDraftRole.canBeAssignedToUsers; return (
- - { - setSettingsDraftRole({ - ...settingsDraftRole, - canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings, - }); - }} - /> - + {shouldShowAllAccessToggle && ( + + { + setSettingsDraftRole({ + ...settingsDraftRole, + canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings, + }); + }} + /> + + )} - - { - setSettingsDraftRole({ - ...settingsDraftRole, - canAccessAllTools: !settingsDraftRole.canAccessAllTools, - }); - }} - /> - + {shouldShowAllAccessToggle && ( + + { + setSettingsDraftRole({ + ...settingsDraftRole, + canAccessAllTools: !settingsDraftRole.canAccessAllTools, + }); + }} + /> + + )} { const navigateApp = useNavigateApp(); const { enqueueErrorSnackBar } = useSnackBar(); const [isReadonlyMode, setIsReadonlyMode] = useState(false); + const [originalFormValues, setOriginalFormValues] = useState< + ReturnType['formValues'] | null + >(null); const isEditMode = mode === 'edit'; const isCreateMode = mode === 'create'; @@ -82,7 +93,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { if (isDefined(agent.applicationId)) { setIsReadonlyMode(true); } - resetForm({ + const initialValues = { name: agent.name, label: agent.label, description: agent.description, @@ -93,7 +104,10 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { isCustom: agent.isCustom, modelConfiguration: agent.modelConfiguration || {}, responseFormat: agent.responseFormat || { type: 'text', schema: {} }, - }); + evaluationInputs: agent.evaluationInputs || [], + }; + resetForm(initialValues); + setOriginalFormValues(initialValues); } else { enqueueErrorSnackBar({ message: t`Agent not found`, @@ -130,6 +144,94 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { isDefined(formValues.role) && !isDeeplyEqual(settingsDraftRole, settingsPersistedRole); + const autoSave = useDebouncedCallback(async () => { + if ( + isCreateMode || + isReadonlyMode || + !validateForm() || + isSubmitting || + !agent + ) { + return; + } + + const hasChanges = + originalFormValues && !isDeeplyEqual(formValues, originalFormValues); + + if (!hasChanges && !isRoleDirty) { + return; + } + + setIsSubmitting(true); + + try { + if (isRoleDirty && isDefined(formValues.role)) { + try { + await saveDraftRoleToDB(); + } catch (error) { + if (error instanceof ApolloError) { + enqueueErrorSnackBar({ + apolloError: error, + }); + } else { + const errorMessage = + error instanceof Error ? error.message : String(error); + enqueueErrorSnackBar({ + message: t`Failed to save role permissions: ${errorMessage}`, + }); + } + setIsSubmitting(false); + 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, + prompt: formValues.prompt, + modelConfiguration: formValues.modelConfiguration, + responseFormat: formValues.responseFormat, + evaluationInputs: formValues.evaluationInputs, + }, + }, + }); + + setOriginalFormValues({ ...formValues }); + } catch (error) { + enqueueErrorSnackBar({ + apolloError: error instanceof ApolloError ? error : undefined, + }); + } finally { + setIsSubmitting(false); + } + }, 1_000); + + useEffect(() => { + if (isEditMode && !loading && isDefined(originalFormValues)) { + autoSave(); + } + }, [ + formValues, + isRoleDirty, + isEditMode, + loading, + originalFormValues, + autoSave, + ]); + + useEffect(() => { + return () => { + autoSave.flush(); + }; + }, [autoSave]); + if (!isCreateMode && !loading && !agent) { return null; } @@ -147,6 +249,16 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { title: t`Role`, Icon: IconLock, }, + { + id: SETTINGS_AGENT_DETAIL_TABS.TABS_IDS.EVALS, + title: t`Evals`, + Icon: IconListCheck, + }, + { + id: SETTINGS_AGENT_DETAIL_TABS.TABS_IDS.LOGS, + title: t`Logs`, + Icon: IconList, + }, ]; const handleSave = async () => { @@ -192,6 +304,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { prompt: formValues.prompt, modelConfiguration: formValues.modelConfiguration, responseFormat: formValues.responseFormat, + evaluationInputs: formValues.evaluationInputs, }; await createAgent({ @@ -218,6 +331,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { prompt: formValues.prompt, modelConfiguration: formValues.modelConfiguration, responseFormat: formValues.responseFormat, + evaluationInputs: formValues.evaluationInputs, }, }, }); @@ -275,6 +389,26 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { agent={agent} /> ); + + case SETTINGS_AGENT_DETAIL_TABS.TABS_IDS.EVALS: + return ( + + handleFieldChange('evaluationInputs', inputs) + } + disabled={ + process.env.NODE_ENV === 'development' + ? isReadonlyMode + : isReadonlyMode || (isEditMode ? !agent?.isCustom : false) + } + /> + ); + + case SETTINGS_AGENT_DETAIL_TABS.TABS_IDS.LOGS: + return ; + default: return <>; } @@ -286,7 +420,7 @@ export const SettingsAgentForm = ({ mode }: { mode: 'create' | 'edit' }) => { theme.spacing(3)}; +`; + +const StyledTableHeaderRow = styled(TableRow)` + grid-template-columns: 140px 80px 1fr; + margin-bottom: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledTableRow = styled(TableRow)` + grid-template-columns: 140px 80px 1fr; +`; + +const StyledDateCell = styled(TableCell)` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + +const StyledScoreCell = styled(TableCell)` + align-items: center; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledCommentCell = styled(TableCell)` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const StyledMessagesContainer = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledMessageBubble = styled.div` + display: flex; + flex-direction: column; + gap: ${({ theme }) => theme.spacing(1)}; +`; + +const StyledMessageRole = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; + font-size: ${({ theme }) => theme.font.size.sm}; + font-weight: ${({ theme }) => theme.font.weight.medium}; + text-transform: uppercase; +`; + +const StyledMessageContent = styled.div` + color: ${({ theme }) => theme.font.color.primary}; + max-width: 100%; +`; + +export const SettingsAgentTurnDetail = () => { + const { agentId, turnId } = useParams<{ + agentId: string; + turnId: string; + }>(); + + const { data, loading } = useQuery(GET_AGENT_TURNS, { + variables: { agentId: agentId || '' }, + skip: !agentId, + }); + + const turn = data?.agentTurns?.find((t: any) => t.id === turnId); + + const getScoreColor = (score: number) => { + if (score >= 80) return 'green'; + if (score >= 60) return 'orange'; + return 'red'; + }; + + if (loading) { + return ( + + + + + + ); + } + + if (!turn) { + return ( + + +
{t`Turn not found`}
+
+
+ ); + } + + return ( + + +
+ + {turn.messages && turn.messages.length > 0 ? ( + + {mapDBMessagesToUIMessages( + [...turn.messages] + .filter((msg: any) => msg.parts && msg.parts.length > 0) + .sort((a: any, b: any) => { + if (a.role === 'user' && b.role === 'assistant') return -1; + if (a.role === 'assistant' && b.role === 'user') return 1; + return ( + new Date(a.createdAt).getTime() - + new Date(b.createdAt).getTime() + ); + }), + ).map((message) => { + const roleLabel = + message.role === 'user' + ? t`User` + : message.role === 'system' + ? t`System` + : t`Assistant`; + return ( + + {roleLabel} + + + + + ); + })} + + ) : ( +
{t`No messages found for this turn`}
+ )} +
+ +
+ + {turn.evaluations && turn.evaluations.length > 0 ? ( + + + {t`Date`} + {t`Score`} + {t`Comment`} + + {[...turn.evaluations] + .sort( + (a: any, b: any) => + new Date(b.createdAt).getTime() - + new Date(a.createdAt).getTime(), + ) + .map((evaluation: any) => ( + + + {new Date(evaluation.createdAt).toLocaleDateString( + 'en-US', + { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }, + )} + + + + + + {evaluation.comment || t`No comment`} + + + ))} + + ) : ( +
{t`No evaluations yet for this turn`}
+ )} +
+
+
+ ); +}; diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentEvalsTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentEvalsTab.tsx new file mode 100644 index 00000000000..07c4f97c2dc --- /dev/null +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentEvalsTab.tsx @@ -0,0 +1,193 @@ +import { RUN_EVALUATION_INPUT } from '@/ai/graphql/mutations/runEvaluationInput'; +import { SettingsListCard } from '@/settings/components/SettingsListCard'; +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +import { TextInput } from '@/ui/input/components/TextInput'; +import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown'; +import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent'; +import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer'; +import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown'; +import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal'; +import { useModal } from '@/ui/layout/modal/hooks/useModal'; +import { useMutation } from '@apollo/client'; +import styled from '@emotion/styled'; +import { t } from '@lingui/core/macro'; +import { useState } from 'react'; +import { + IconDotsVertical, + IconMessage, + IconPlayerPlay, + IconPlus, + IconTrash, +} from 'twenty-ui/display'; +import { Button, LightIconButton } from 'twenty-ui/input'; +import { Section } from 'twenty-ui/layout'; +import { MenuItem } from 'twenty-ui/navigation'; +import { v4 as uuidv4 } from 'uuid'; + +const DELETE_EVAL_INPUT_MODAL_ID = 'delete-eval-input-modal'; + +const StyledInputContainer = styled.div` + align-items: center; + display: flex; + gap: ${({ theme }) => theme.spacing(2)}; + margin-bottom: ${({ theme }) => theme.spacing(6)}; + margin-top: ${({ theme }) => theme.spacing(4)}; +`; + +const StyledEmptyMessage = styled.div` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + +type SettingsAgentEvalsTabProps = { + agentId: string; + evaluationInputs: string[]; + onEvaluationInputsChange: (inputs: string[]) => void; + disabled?: boolean; +}; + +type EvalInput = { + id: string; + text: string; +}; + +export const SettingsAgentEvalsTab = ({ + agentId, + evaluationInputs, + onEvaluationInputsChange, + disabled = false, +}: SettingsAgentEvalsTabProps) => { + const [newInput, setNewInput] = useState(''); + const [inputToDelete, setInputToDelete] = useState(null); + const { openModal } = useModal(); + const { closeDropdown } = useCloseDropdown(); + const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); + + const [runEvaluationInput] = useMutation(RUN_EVALUATION_INPUT, { + onCompleted: () => { + enqueueSuccessSnackBar({ + message: t`Evaluation input executed successfully`, + }); + }, + onError: () => { + enqueueErrorSnackBar({ + message: t`Failed to execute evaluation input`, + }); + }, + }); + + const evalInputs: EvalInput[] = evaluationInputs.map((text) => ({ + id: uuidv4(), + text, + })); + + const handleAddInput = () => { + if (newInput.trim() !== '') { + onEvaluationInputsChange([...evaluationInputs, newInput.trim()]); + setNewInput(''); + } + }; + + const handleDeleteInput = () => { + if (inputToDelete !== null) { + const index = evalInputs.findIndex((input) => input.id === inputToDelete); + if (index !== -1) { + const newInputs = [...evaluationInputs]; + newInputs.splice(index, 1); + onEvaluationInputsChange(newInputs); + } + setInputToDelete(null); + } + }; + + const openDeleteModal = (id: string) => { + setInputToDelete(id); + openModal(DELETE_EVAL_INPUT_MODAL_ID); + }; + + const handleRunInput = (text: string, itemId: string) => { + runEvaluationInput({ + variables: { agentId, input: text }, + }); + closeDropdown(`eval-input-dropdown-${itemId}`); + }; + + return ( + <> +
+ + { + if (e.key === 'Enter') { + e.preventDefault(); + handleAddInput(); + } + }} + disabled={disabled} + fullWidth + /> +
+ + + + ); +}; diff --git a/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentLogsTab.tsx b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentLogsTab.tsx new file mode 100644 index 00000000000..6563f78591d --- /dev/null +++ b/packages/twenty-front/src/pages/settings/ai/components/SettingsAgentLogsTab.tsx @@ -0,0 +1,209 @@ +import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; +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 { useMutation, useQuery } from '@apollo/client'; +import styled from '@emotion/styled'; +import { t } from '@lingui/core/macro'; +import Skeleton from 'react-loading-skeleton'; +import { SettingsPath } from 'twenty-shared/types'; +import { getSettingsPath } from 'twenty-shared/utils'; +import { IconChevronRight, Status } from 'twenty-ui/display'; +import { Button, LightIconButton } from 'twenty-ui/input'; +import { + AnimatedPlaceholder, + AnimatedPlaceholderEmptyContainer, + AnimatedPlaceholderEmptySubTitle, + AnimatedPlaceholderEmptyTextContainer, + AnimatedPlaceholderEmptyTitle, +} from 'twenty-ui/layout'; +import { UndecoratedLink } from 'twenty-ui/navigation'; +import { EVALUATE_AGENT_TURN } from '@/ai/graphql/mutations/evaluateAgentTurn'; +import { GET_AGENT_TURNS } from '@/ai/graphql/queries/getAgentTurns'; + +const StyledTable = styled(Table)` + margin-top: ${({ theme }) => theme.spacing(3)}; +`; + +const StyledTableHeaderRow = styled(TableRow)` + grid-template-columns: 140px 80px 1fr 40px; + margin-bottom: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledTableRow = styled(TableRow)` + grid-template-columns: 140px 80px 1fr 40px; +`; + +const StyledScoreCell = styled(TableCell)` + align-items: center; + gap: ${({ theme }) => theme.spacing(2)}; +`; + +const StyledDateCell = styled(TableCell)` + color: ${({ theme }) => theme.font.color.tertiary}; +`; + +const StyledInputCell = styled(TableCell)` + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const StyledActionCell = styled(TableCell)` + justify-content: flex-end; + padding-right: ${({ theme }) => theme.spacing(2)}; +`; + +type SettingsAgentLogsTabProps = { + agentId: string; +}; + +export const SettingsAgentLogsTab = ({ + agentId, +}: SettingsAgentLogsTabProps) => { + const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar(); + + const { data, loading, refetch } = useQuery(GET_AGENT_TURNS, { + variables: { agentId }, + skip: !agentId, + }); + + const [evaluateTurn, { loading: evaluating }] = useMutation( + EVALUATE_AGENT_TURN, + { + onCompleted: () => { + enqueueSuccessSnackBar({ + message: t`Turn evaluated successfully`, + }); + refetch(); + }, + onError: () => { + enqueueErrorSnackBar({ + message: t`Failed to evaluate turn`, + }); + }, + }, + ); + + const turns = data?.agentTurns || []; + + const getLatestEvaluation = (evaluations: any[]) => { + if (!evaluations || evaluations.length === 0) return null; + return [...evaluations].sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + )[0]; + }; + + const getScoreColor = (score: number) => { + if (score >= 80) return 'green'; + if (score >= 60) return 'orange'; + return 'red'; + }; + + const getUserMessageInput = (messages: any[]) => { + const userMessage = messages?.find((message) => message.role === 'user'); + if (!userMessage) return null; + + const textParts = userMessage.parts + ?.filter((part: any) => part.type === 'text' && part.textContent) + .map((part: any) => part.textContent); + + return textParts?.join(' ') || null; + }; + + if (loading) { + return ( + + + {t`Date`} + {t`Score`} + {t`Input`} + + + {Array.from({ length: 3 }).map((_, index) => ( + + ))} + + ); + } + + if (turns.length === 0) { + return ( + + + + + {t`No logs yet`} + + + {t`Agent interactions will appear here once the agent is used in conversations`} + + + + ); + } + + return ( + + + {t`Date`} + {t`Score`} + {t`Input`} + + + {turns.map((turn: any) => { + const latestEvaluation = getLatestEvaluation(turn.evaluations); + const userInput = getUserMessageInput(turn.messages); + + return ( + + + {new Date(turn.createdAt).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + })} + + + {latestEvaluation ? ( + + ) : ( +