feat(ai): add current context to ai chat (#13315)
## TODO - [ ] add dropdown to use records from outside the context - [x] add loader for files chip - [x] add roleId where it's necessary - [x] Split AvatarChip in two components. One with the icon that will call the second with leftComponent. - [ ] Fix tests - [x] Fix UI regression on Search
This commit is contained in:
@ -2971,6 +2971,14 @@ export type WorkspaceUrlsAndId = {
|
|||||||
workspaceUrls: WorkspaceUrls;
|
workspaceUrls: WorkspaceUrls;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type AssignRoleToAgentMutationVariables = Exact<{
|
||||||
|
agentId: Scalars['UUID'];
|
||||||
|
roleId: Scalars['UUID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type AssignRoleToAgentMutation = { __typename?: 'Mutation', assignRoleToAgent: boolean };
|
||||||
|
|
||||||
export type CreateAgentChatThreadMutationVariables = Exact<{
|
export type CreateAgentChatThreadMutationVariables = Exact<{
|
||||||
input: CreateAgentChatThreadInput;
|
input: CreateAgentChatThreadInput;
|
||||||
}>;
|
}>;
|
||||||
@ -2978,6 +2986,27 @@ 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 CreateAgentChatThreadMutation = { __typename?: 'Mutation', createAgentChatThread: { __typename?: 'AgentChatThread', id: any, agentId: any, title?: string | null, createdAt: string, updatedAt: string } };
|
||||||
|
|
||||||
|
export type RemoveRoleFromAgentMutationVariables = Exact<{
|
||||||
|
agentId: Scalars['UUID'];
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type RemoveRoleFromAgentMutation = { __typename?: 'Mutation', removeRoleFromAgent: boolean };
|
||||||
|
|
||||||
|
export type UpdateOneAgentMutationVariables = Exact<{
|
||||||
|
input: UpdateAgentInput;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
|
||||||
|
export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: any, name: string, description?: string | null, prompt: string, modelId: string, responseFormat?: any | null } };
|
||||||
|
|
||||||
|
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 GetAgentChatMessagesQueryVariables = Exact<{
|
export type GetAgentChatMessagesQueryVariables = Exact<{
|
||||||
threadId: Scalars['String'];
|
threadId: Scalars['String'];
|
||||||
}>;
|
}>;
|
||||||
@ -3795,35 +3824,6 @@ export type UpdateWorkflowVersionStepMutationVariables = Exact<{
|
|||||||
|
|
||||||
export type UpdateWorkflowVersionStepMutation = { __typename?: 'Mutation', updateWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean, nextStepIds?: Array<any> | null } };
|
export type UpdateWorkflowVersionStepMutation = { __typename?: 'Mutation', updateWorkflowVersionStep: { __typename?: 'WorkflowAction', id: any, name: string, type: string, settings: any, valid: boolean, nextStepIds?: Array<any> | null } };
|
||||||
|
|
||||||
export type AssignRoleToAgentMutationVariables = Exact<{
|
|
||||||
agentId: Scalars['UUID'];
|
|
||||||
roleId: Scalars['UUID'];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type AssignRoleToAgentMutation = { __typename?: 'Mutation', assignRoleToAgent: boolean };
|
|
||||||
|
|
||||||
export type RemoveRoleFromAgentMutationVariables = Exact<{
|
|
||||||
agentId: Scalars['UUID'];
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type RemoveRoleFromAgentMutation = { __typename?: 'Mutation', removeRoleFromAgent: boolean };
|
|
||||||
|
|
||||||
export type UpdateOneAgentMutationVariables = Exact<{
|
|
||||||
input: UpdateAgentInput;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
|
|
||||||
export type UpdateOneAgentMutation = { __typename?: 'Mutation', updateOneAgent: { __typename?: 'Agent', id: any, name: string, description?: string | null, prompt: string, modelId: string, responseFormat?: any | null } };
|
|
||||||
|
|
||||||
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 SubmitFormStepMutationVariables = Exact<{
|
export type SubmitFormStepMutationVariables = Exact<{
|
||||||
input: SubmitFormStepInput;
|
input: SubmitFormStepInput;
|
||||||
}>;
|
}>;
|
||||||
@ -4169,6 +4169,38 @@ ${ObjectPermissionFragmentFragmentDoc}
|
|||||||
${WorkspaceUrlsFragmentFragmentDoc}
|
${WorkspaceUrlsFragmentFragmentDoc}
|
||||||
${RoleFragmentFragmentDoc}
|
${RoleFragmentFragmentDoc}
|
||||||
${AvailableWorkspacesFragmentFragmentDoc}`;
|
${AvailableWorkspacesFragmentFragmentDoc}`;
|
||||||
|
export const AssignRoleToAgentDocument = gql`
|
||||||
|
mutation AssignRoleToAgent($agentId: UUID!, $roleId: UUID!) {
|
||||||
|
assignRoleToAgent(agentId: $agentId, roleId: $roleId)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type AssignRoleToAgentMutationFn = Apollo.MutationFunction<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useAssignRoleToAgentMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useAssignRoleToAgentMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useAssignRoleToAgentMutation` 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 [assignRoleToAgentMutation, { data, loading, error }] = useAssignRoleToAgentMutation({
|
||||||
|
* variables: {
|
||||||
|
* agentId: // value for 'agentId'
|
||||||
|
* roleId: // value for 'roleId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useAssignRoleToAgentMutation(baseOptions?: Apollo.MutationHookOptions<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>(AssignRoleToAgentDocument, options);
|
||||||
|
}
|
||||||
|
export type AssignRoleToAgentMutationHookResult = ReturnType<typeof useAssignRoleToAgentMutation>;
|
||||||
|
export type AssignRoleToAgentMutationResult = Apollo.MutationResult<AssignRoleToAgentMutation>;
|
||||||
|
export type AssignRoleToAgentMutationOptions = Apollo.BaseMutationOptions<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>;
|
||||||
export const CreateAgentChatThreadDocument = gql`
|
export const CreateAgentChatThreadDocument = gql`
|
||||||
mutation CreateAgentChatThread($input: CreateAgentChatThreadInput!) {
|
mutation CreateAgentChatThread($input: CreateAgentChatThreadInput!) {
|
||||||
createAgentChatThread(input: $input) {
|
createAgentChatThread(input: $input) {
|
||||||
@ -4206,6 +4238,116 @@ export function useCreateAgentChatThreadMutation(baseOptions?: Apollo.MutationHo
|
|||||||
export type CreateAgentChatThreadMutationHookResult = ReturnType<typeof useCreateAgentChatThreadMutation>;
|
export type CreateAgentChatThreadMutationHookResult = ReturnType<typeof useCreateAgentChatThreadMutation>;
|
||||||
export type CreateAgentChatThreadMutationResult = Apollo.MutationResult<CreateAgentChatThreadMutation>;
|
export type CreateAgentChatThreadMutationResult = Apollo.MutationResult<CreateAgentChatThreadMutation>;
|
||||||
export type CreateAgentChatThreadMutationOptions = Apollo.BaseMutationOptions<CreateAgentChatThreadMutation, CreateAgentChatThreadMutationVariables>;
|
export type CreateAgentChatThreadMutationOptions = Apollo.BaseMutationOptions<CreateAgentChatThreadMutation, CreateAgentChatThreadMutationVariables>;
|
||||||
|
export const RemoveRoleFromAgentDocument = gql`
|
||||||
|
mutation RemoveRoleFromAgent($agentId: UUID!) {
|
||||||
|
removeRoleFromAgent(agentId: $agentId)
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type RemoveRoleFromAgentMutationFn = Apollo.MutationFunction<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useRemoveRoleFromAgentMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useRemoveRoleFromAgentMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useRemoveRoleFromAgentMutation` 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 [removeRoleFromAgentMutation, { data, loading, error }] = useRemoveRoleFromAgentMutation({
|
||||||
|
* variables: {
|
||||||
|
* agentId: // value for 'agentId'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useRemoveRoleFromAgentMutation(baseOptions?: Apollo.MutationHookOptions<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>(RemoveRoleFromAgentDocument, options);
|
||||||
|
}
|
||||||
|
export type RemoveRoleFromAgentMutationHookResult = ReturnType<typeof useRemoveRoleFromAgentMutation>;
|
||||||
|
export type RemoveRoleFromAgentMutationResult = Apollo.MutationResult<RemoveRoleFromAgentMutation>;
|
||||||
|
export type RemoveRoleFromAgentMutationOptions = Apollo.BaseMutationOptions<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
|
||||||
|
export const UpdateOneAgentDocument = gql`
|
||||||
|
mutation UpdateOneAgent($input: UpdateAgentInput!) {
|
||||||
|
updateOneAgent(input: $input) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
prompt
|
||||||
|
modelId
|
||||||
|
responseFormat
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
export type UpdateOneAgentMutationFn = Apollo.MutationFunction<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useUpdateOneAgentMutation__
|
||||||
|
*
|
||||||
|
* To run a mutation, you first call `useUpdateOneAgentMutation` within a React component and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useUpdateOneAgentMutation` 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 [updateOneAgentMutation, { data, loading, error }] = useUpdateOneAgentMutation({
|
||||||
|
* variables: {
|
||||||
|
* input: // value for 'input'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useUpdateOneAgentMutation(baseOptions?: Apollo.MutationHookOptions<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useMutation<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>(UpdateOneAgentDocument, options);
|
||||||
|
}
|
||||||
|
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}) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
description
|
||||||
|
prompt
|
||||||
|
modelId
|
||||||
|
responseFormat
|
||||||
|
roleId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __useFindOneAgentQuery__
|
||||||
|
*
|
||||||
|
* To run a query within a React component, call `useFindOneAgentQuery` and pass it any options that fit your needs.
|
||||||
|
* When your component renders, `useFindOneAgentQuery` 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 } = useFindOneAgentQuery({
|
||||||
|
* variables: {
|
||||||
|
* id: // value for 'id'
|
||||||
|
* },
|
||||||
|
* });
|
||||||
|
*/
|
||||||
|
export function useFindOneAgentQuery(baseOptions: Apollo.QueryHookOptions<FindOneAgentQuery, FindOneAgentQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useQuery<FindOneAgentQuery, FindOneAgentQueryVariables>(FindOneAgentDocument, options);
|
||||||
|
}
|
||||||
|
export function useFindOneAgentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindOneAgentQuery, FindOneAgentQueryVariables>) {
|
||||||
|
const options = {...defaultOptions, ...baseOptions}
|
||||||
|
return Apollo.useLazyQuery<FindOneAgentQuery, FindOneAgentQueryVariables>(FindOneAgentDocument, options);
|
||||||
|
}
|
||||||
|
export type FindOneAgentQueryHookResult = ReturnType<typeof useFindOneAgentQuery>;
|
||||||
|
export type FindOneAgentLazyQueryHookResult = ReturnType<typeof useFindOneAgentLazyQuery>;
|
||||||
|
export type FindOneAgentQueryResult = Apollo.QueryResult<FindOneAgentQuery, FindOneAgentQueryVariables>;
|
||||||
export const GetAgentChatMessagesDocument = gql`
|
export const GetAgentChatMessagesDocument = gql`
|
||||||
query GetAgentChatMessages($threadId: String!) {
|
query GetAgentChatMessages($threadId: String!) {
|
||||||
agentChatMessages(threadId: $threadId) {
|
agentChatMessages(threadId: $threadId) {
|
||||||
@ -8463,148 +8605,6 @@ export function useUpdateWorkflowVersionStepMutation(baseOptions?: Apollo.Mutati
|
|||||||
export type UpdateWorkflowVersionStepMutationHookResult = ReturnType<typeof useUpdateWorkflowVersionStepMutation>;
|
export type UpdateWorkflowVersionStepMutationHookResult = ReturnType<typeof useUpdateWorkflowVersionStepMutation>;
|
||||||
export type UpdateWorkflowVersionStepMutationResult = Apollo.MutationResult<UpdateWorkflowVersionStepMutation>;
|
export type UpdateWorkflowVersionStepMutationResult = Apollo.MutationResult<UpdateWorkflowVersionStepMutation>;
|
||||||
export type UpdateWorkflowVersionStepMutationOptions = Apollo.BaseMutationOptions<UpdateWorkflowVersionStepMutation, UpdateWorkflowVersionStepMutationVariables>;
|
export type UpdateWorkflowVersionStepMutationOptions = Apollo.BaseMutationOptions<UpdateWorkflowVersionStepMutation, UpdateWorkflowVersionStepMutationVariables>;
|
||||||
export const AssignRoleToAgentDocument = gql`
|
|
||||||
mutation AssignRoleToAgent($agentId: UUID!, $roleId: UUID!) {
|
|
||||||
assignRoleToAgent(agentId: $agentId, roleId: $roleId)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export type AssignRoleToAgentMutationFn = Apollo.MutationFunction<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useAssignRoleToAgentMutation__
|
|
||||||
*
|
|
||||||
* To run a mutation, you first call `useAssignRoleToAgentMutation` within a React component and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useAssignRoleToAgentMutation` 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 [assignRoleToAgentMutation, { data, loading, error }] = useAssignRoleToAgentMutation({
|
|
||||||
* variables: {
|
|
||||||
* agentId: // value for 'agentId'
|
|
||||||
* roleId: // value for 'roleId'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useAssignRoleToAgentMutation(baseOptions?: Apollo.MutationHookOptions<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useMutation<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>(AssignRoleToAgentDocument, options);
|
|
||||||
}
|
|
||||||
export type AssignRoleToAgentMutationHookResult = ReturnType<typeof useAssignRoleToAgentMutation>;
|
|
||||||
export type AssignRoleToAgentMutationResult = Apollo.MutationResult<AssignRoleToAgentMutation>;
|
|
||||||
export type AssignRoleToAgentMutationOptions = Apollo.BaseMutationOptions<AssignRoleToAgentMutation, AssignRoleToAgentMutationVariables>;
|
|
||||||
export const RemoveRoleFromAgentDocument = gql`
|
|
||||||
mutation RemoveRoleFromAgent($agentId: UUID!) {
|
|
||||||
removeRoleFromAgent(agentId: $agentId)
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export type RemoveRoleFromAgentMutationFn = Apollo.MutationFunction<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useRemoveRoleFromAgentMutation__
|
|
||||||
*
|
|
||||||
* To run a mutation, you first call `useRemoveRoleFromAgentMutation` within a React component and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useRemoveRoleFromAgentMutation` 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 [removeRoleFromAgentMutation, { data, loading, error }] = useRemoveRoleFromAgentMutation({
|
|
||||||
* variables: {
|
|
||||||
* agentId: // value for 'agentId'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useRemoveRoleFromAgentMutation(baseOptions?: Apollo.MutationHookOptions<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useMutation<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>(RemoveRoleFromAgentDocument, options);
|
|
||||||
}
|
|
||||||
export type RemoveRoleFromAgentMutationHookResult = ReturnType<typeof useRemoveRoleFromAgentMutation>;
|
|
||||||
export type RemoveRoleFromAgentMutationResult = Apollo.MutationResult<RemoveRoleFromAgentMutation>;
|
|
||||||
export type RemoveRoleFromAgentMutationOptions = Apollo.BaseMutationOptions<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
|
|
||||||
export const UpdateOneAgentDocument = gql`
|
|
||||||
mutation UpdateOneAgent($input: UpdateAgentInput!) {
|
|
||||||
updateOneAgent(input: $input) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
description
|
|
||||||
prompt
|
|
||||||
modelId
|
|
||||||
responseFormat
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
export type UpdateOneAgentMutationFn = Apollo.MutationFunction<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useUpdateOneAgentMutation__
|
|
||||||
*
|
|
||||||
* To run a mutation, you first call `useUpdateOneAgentMutation` within a React component and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useUpdateOneAgentMutation` 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 [updateOneAgentMutation, { data, loading, error }] = useUpdateOneAgentMutation({
|
|
||||||
* variables: {
|
|
||||||
* input: // value for 'input'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useUpdateOneAgentMutation(baseOptions?: Apollo.MutationHookOptions<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useMutation<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>(UpdateOneAgentDocument, options);
|
|
||||||
}
|
|
||||||
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}) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
description
|
|
||||||
prompt
|
|
||||||
modelId
|
|
||||||
responseFormat
|
|
||||||
roleId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* __useFindOneAgentQuery__
|
|
||||||
*
|
|
||||||
* To run a query within a React component, call `useFindOneAgentQuery` and pass it any options that fit your needs.
|
|
||||||
* When your component renders, `useFindOneAgentQuery` 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 } = useFindOneAgentQuery({
|
|
||||||
* variables: {
|
|
||||||
* id: // value for 'id'
|
|
||||||
* },
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
export function useFindOneAgentQuery(baseOptions: Apollo.QueryHookOptions<FindOneAgentQuery, FindOneAgentQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useQuery<FindOneAgentQuery, FindOneAgentQueryVariables>(FindOneAgentDocument, options);
|
|
||||||
}
|
|
||||||
export function useFindOneAgentLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindOneAgentQuery, FindOneAgentQueryVariables>) {
|
|
||||||
const options = {...defaultOptions, ...baseOptions}
|
|
||||||
return Apollo.useLazyQuery<FindOneAgentQuery, FindOneAgentQueryVariables>(FindOneAgentDocument, options);
|
|
||||||
}
|
|
||||||
export type FindOneAgentQueryHookResult = ReturnType<typeof useFindOneAgentQuery>;
|
|
||||||
export type FindOneAgentLazyQueryHookResult = ReturnType<typeof useFindOneAgentLazyQuery>;
|
|
||||||
export type FindOneAgentQueryResult = Apollo.QueryResult<FindOneAgentQuery, FindOneAgentQueryVariables>;
|
|
||||||
export const SubmitFormStepDocument = gql`
|
export const SubmitFormStepDocument = gql`
|
||||||
mutation SubmitFormStep($input: SubmitFormStepInput!) {
|
mutation SubmitFormStep($input: SubmitFormStepInput!) {
|
||||||
submitFormStep(input: $input)
|
submitFormStep(input: $input)
|
||||||
|
|||||||
@ -3,8 +3,8 @@ import styled from '@emotion/styled';
|
|||||||
import { Avatar, IconDotsVertical, IconSparkles } from 'twenty-ui/display';
|
import { Avatar, IconDotsVertical, IconSparkles } from 'twenty-ui/display';
|
||||||
|
|
||||||
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
|
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
|
||||||
import { AgentChatFilePreview } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/AgentChatFilePreview';
|
import { AgentChatFilePreview } from '@/ai/components/internal/AgentChatFilePreview';
|
||||||
import { AgentChatMessageRole } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/agent-chat-message-role';
|
import { AgentChatMessageRole } from '@/ai/constants/agent-chat-message-role';
|
||||||
|
|
||||||
import { AgentChatMessage } from '~/generated/graphql';
|
import { AgentChatMessage } from '~/generated/graphql';
|
||||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
|
|||||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||||
import { AgentChatFileUpload } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/AgentChatFileUpload';
|
import { AgentChatFileUploadButton } from '@/ai/components/internal/AgentChatFileUploadButton';
|
||||||
|
|
||||||
import { AIChatEmptyState } from '@/ai/components/AIChatEmptyState';
|
import { AIChatEmptyState } from '@/ai/components/AIChatEmptyState';
|
||||||
import { AIChatMessage } from '@/ai/components/AIChatMessage';
|
import { AIChatMessage } from '@/ai/components/AIChatMessage';
|
||||||
@ -16,8 +16,12 @@ import { t } from '@lingui/core/macro';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
import { useAgentChat } from '../hooks/useAgentChat';
|
import { useAgentChat } from '../hooks/useAgentChat';
|
||||||
import { AIChatSkeletonLoader } from './AIChatSkeletonLoader';
|
import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoader';
|
||||||
import { AgentChatSelectedFilesPreview } from './AgentChatSelectedFilesPreview';
|
import { AgentChatContextPreview } from '@/ai/components/internal/AgentChatContextPreview';
|
||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||||
|
import { SendMessageWithRecordsContextButton } from '@/ai/components/internal/SendMessageWithRecordsContextButton';
|
||||||
|
import { SendMessageButton } from '@/ai/components/internal/SendMessageButton';
|
||||||
|
|
||||||
const StyledContainer = styled.div<{ isDraggingFile: boolean }>`
|
const StyledContainer = styled.div<{ isDraggingFile: boolean }>`
|
||||||
background: ${({ theme }) => theme.background.primary};
|
background: ${({ theme }) => theme.background.primary};
|
||||||
@ -63,10 +67,13 @@ export const AIChatTab = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
||||||
|
|
||||||
|
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
|
||||||
|
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
messages,
|
messages,
|
||||||
isLoading,
|
isLoading,
|
||||||
handleSendMessage,
|
|
||||||
input,
|
input,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
agentStreamingMessage,
|
agentStreamingMessage,
|
||||||
@ -105,7 +112,7 @@ export const AIChatTab = ({
|
|||||||
{isLoading && messages.length === 0 && <AIChatSkeletonLoader />}
|
{isLoading && messages.length === 0 && <AIChatSkeletonLoader />}
|
||||||
|
|
||||||
<StyledInputArea>
|
<StyledInputArea>
|
||||||
<AgentChatSelectedFilesPreview agentId={agentId} />
|
<AgentChatContextPreview agentId={agentId} />
|
||||||
<TextArea
|
<TextArea
|
||||||
textAreaId={`${agentId}-chat-input`}
|
textAreaId={`${agentId}-chat-input`}
|
||||||
placeholder={t`Enter a question...`}
|
placeholder={t`Enter a question...`}
|
||||||
@ -135,16 +142,12 @@ export const AIChatTab = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<AgentChatFileUpload agentId={agentId} />
|
<AgentChatFileUploadButton agentId={agentId} />
|
||||||
<Button
|
{contextStoreCurrentObjectMetadataItemId ? (
|
||||||
variant="primary"
|
<SendMessageWithRecordsContextButton agentId={agentId} />
|
||||||
accent="blue"
|
) : (
|
||||||
size="small"
|
<SendMessageButton agentId={agentId} />
|
||||||
hotkeys={input && !isLoading ? ['⏎'] : undefined}
|
)}
|
||||||
disabled={!input || isLoading}
|
|
||||||
title={t`Send`}
|
|
||||||
onClick={handleSendMessage}
|
|
||||||
/>
|
|
||||||
</StyledButtonsContainer>
|
</StyledButtonsContainer>
|
||||||
</StyledInputArea>
|
</StyledInputArea>
|
||||||
</>
|
</>
|
||||||
@ -5,7 +5,7 @@ import { AIChatThreadsListEffect } from '@/ai/components/AIChatThreadsListEffect
|
|||||||
import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
|
import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
|
||||||
import { groupThreadsByDate } from '@/ai/utils/groupThreadsByDate';
|
import { groupThreadsByDate } from '@/ai/utils/groupThreadsByDate';
|
||||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { AIChatSkeletonLoader } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/AIChatSkeletonLoader';
|
import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoader';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
|
|||||||
@ -0,0 +1,93 @@
|
|||||||
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
|
import { agentChatUploadedFilesComponentState } from '@/ai/states/agentChatUploadedFilesComponentState';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { AgentChatFilePreview } from './AgentChatFilePreview';
|
||||||
|
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||||
|
import { useLingui } from '@lingui/react/macro';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
import { agentChatSelectedFilesComponentState } from '@/ai/states/agentChatSelectedFilesComponentState';
|
||||||
|
import { useDeleteFileMutation } from '~/generated-metadata/graphql';
|
||||||
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
|
import { AgentChatContextRecordPreview } from '@/ai/components/internal/AgentChatContextRecordPreview';
|
||||||
|
|
||||||
|
const StyledContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledPreviewsContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: ${({ theme }) => theme.spacing(1)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AgentChatContextPreview = ({ agentId }: { agentId: string }) => {
|
||||||
|
const { t } = useLingui();
|
||||||
|
const [agentChatSelectedFiles, setAgentChatSelectedFiles] =
|
||||||
|
useRecoilComponentStateV2(agentChatSelectedFilesComponentState, agentId);
|
||||||
|
const [agentChatUploadedFiles, setAgentChatUploadedFiles] =
|
||||||
|
useRecoilComponentStateV2(agentChatUploadedFilesComponentState, agentId);
|
||||||
|
|
||||||
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
|
||||||
|
const [deleteFile] = useDeleteFileMutation();
|
||||||
|
|
||||||
|
const handleRemoveUploadedFile = async (fileId: string) => {
|
||||||
|
const originalFiles = agentChatUploadedFiles;
|
||||||
|
|
||||||
|
setAgentChatUploadedFiles(
|
||||||
|
agentChatUploadedFiles.filter((f) => f.id !== fileId),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await deleteFile({ variables: { fileId } });
|
||||||
|
} catch (error) {
|
||||||
|
setAgentChatUploadedFiles(originalFiles);
|
||||||
|
enqueueErrorSnackBar({
|
||||||
|
message: t`Failed to remove file`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
|
||||||
|
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledContainer>
|
||||||
|
<StyledPreviewsContainer>
|
||||||
|
{agentChatSelectedFiles.map((file) => (
|
||||||
|
<AgentChatFilePreview
|
||||||
|
file={file}
|
||||||
|
key={file.name}
|
||||||
|
onRemove={() => {
|
||||||
|
setAgentChatSelectedFiles(
|
||||||
|
agentChatSelectedFiles.filter((f) => f.name !== file.name),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
isUploading
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{agentChatUploadedFiles.map((file) => (
|
||||||
|
<AgentChatFilePreview
|
||||||
|
file={file}
|
||||||
|
key={file.id}
|
||||||
|
onRemove={() => handleRemoveUploadedFile(file.id)}
|
||||||
|
isUploading={false}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{contextStoreCurrentObjectMetadataItemId && (
|
||||||
|
<AgentChatContextRecordPreview
|
||||||
|
agentId={agentId}
|
||||||
|
contextStoreCurrentObjectMetadataItemId={
|
||||||
|
contextStoreCurrentObjectMetadataItemId
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StyledPreviewsContainer>
|
||||||
|
</StyledContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,104 @@
|
|||||||
|
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||||
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
|
import { isAgentChatCurrentContextActiveState } from '@/ai/states/isAgentChatCurrentContextActiveState';
|
||||||
|
import { MultipleAvatarChip } from 'twenty-ui/components';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { IconReload, IconX } from 'twenty-ui/display';
|
||||||
|
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
|
||||||
|
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
|
const StyledRightIconContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
border-left: 1px solid ${({ theme }) => theme.border.color.light};
|
||||||
|
|
||||||
|
svg {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledChipWrapper = styled.div<{ isActive: boolean }>`
|
||||||
|
opacity: ${({ isActive }) => (isActive ? 1 : 0.7)};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AgentChatContextRecordPreview = ({
|
||||||
|
agentId,
|
||||||
|
contextStoreCurrentObjectMetadataItemId,
|
||||||
|
}: {
|
||||||
|
agentId: string;
|
||||||
|
contextStoreCurrentObjectMetadataItemId: string;
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const { records, totalCount } = useFindManyRecordsSelectedInContextStore({
|
||||||
|
limit: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||||
|
objectId: contextStoreCurrentObjectMetadataItemId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [isAgentChatCurrentContextActive, setIsAgentChatCurrentContextActive] =
|
||||||
|
useRecoilComponentStateV2(isAgentChatCurrentContextActiveState, agentId);
|
||||||
|
|
||||||
|
const Avatars = records.map((record) => (
|
||||||
|
// @todo move this components to be less specific. (Outside of CommandMenu
|
||||||
|
<CommandMenuContextRecordChipAvatars
|
||||||
|
objectMetadataItem={objectMetadataItem}
|
||||||
|
key={record.id}
|
||||||
|
record={record}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
|
||||||
|
const recordSelectionContextChip = {
|
||||||
|
// @todo move this utils outside of CommandMenu
|
||||||
|
text: getSelectedRecordsContextText(
|
||||||
|
objectMetadataItem,
|
||||||
|
records,
|
||||||
|
totalCount ?? 0,
|
||||||
|
),
|
||||||
|
Icons: Avatars,
|
||||||
|
withIconBackground: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleIsAgentChatCurrentContextActive = () => {
|
||||||
|
setIsAgentChatCurrentContextActive(!isAgentChatCurrentContextActive);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{records.length !== 0 && (
|
||||||
|
<StyledChipWrapper isActive={isAgentChatCurrentContextActive}>
|
||||||
|
<MultipleAvatarChip
|
||||||
|
Icons={recordSelectionContextChip.Icons}
|
||||||
|
text={
|
||||||
|
isAgentChatCurrentContextActive
|
||||||
|
? recordSelectionContextChip.text
|
||||||
|
: t`Context`
|
||||||
|
}
|
||||||
|
maxWidth={180}
|
||||||
|
rightComponent={
|
||||||
|
<StyledRightIconContainer>
|
||||||
|
{isAgentChatCurrentContextActive ? (
|
||||||
|
<IconX
|
||||||
|
size={theme.icon.size.sm}
|
||||||
|
color={theme.font.color.secondary}
|
||||||
|
onClick={toggleIsAgentChatCurrentContextActive}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<IconReload
|
||||||
|
size={theme.icon.size.sm}
|
||||||
|
color={theme.font.color.secondary}
|
||||||
|
onClick={toggleIsAgentChatCurrentContextActive}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</StyledRightIconContainer>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</StyledChipWrapper>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import { getFileType } from '@/activities/files/utils/getFileType';
|
||||||
|
import { IconMapping, useFileTypeColors } from '@/file/utils/fileIconMappings';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import { File as FileDocument } from '~/generated-metadata/graphql';
|
||||||
|
import { Chip, ChipVariant, AvatarChip } from 'twenty-ui/components';
|
||||||
|
import { IconX } from 'twenty-ui/display';
|
||||||
|
import { Loader } from 'twenty-ui/feedback';
|
||||||
|
|
||||||
|
export const AgentChatFilePreview = ({
|
||||||
|
file,
|
||||||
|
onRemove,
|
||||||
|
isUploading,
|
||||||
|
}: {
|
||||||
|
file: File | FileDocument;
|
||||||
|
onRemove?: () => void;
|
||||||
|
isUploading?: boolean;
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const iconColors = useFileTypeColors();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Chip
|
||||||
|
label={file.name}
|
||||||
|
variant={ChipVariant.Static}
|
||||||
|
leftComponent={
|
||||||
|
isUploading ? (
|
||||||
|
<Loader color="yellow" />
|
||||||
|
) : (
|
||||||
|
<AvatarChip
|
||||||
|
Icon={IconMapping[getFileType(file.name)]}
|
||||||
|
IconBackgroundColor={iconColors[getFileType(file.name)]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
rightComponent={
|
||||||
|
onRemove ? (
|
||||||
|
<AvatarChip
|
||||||
|
Icon={IconX}
|
||||||
|
IconColor={theme.font.color.secondary}
|
||||||
|
onClick={onRemove}
|
||||||
|
divider={'left'}
|
||||||
|
/>
|
||||||
|
) : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload';
|
import { useAIChatFileUpload } from '@/ai/hooks/useAIChatFileUpload';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { agentChatSelectedFilesComponentState } from '@/ai/states/agentChatSelectedFilesComponentState';
|
||||||
import { agentChatSelectedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatSelectedFilesComponentState';
|
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import React, { useRef } from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { IconPaperclip } from 'twenty-ui/display';
|
import { IconPaperclip } from 'twenty-ui/display';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
|
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||||
|
|
||||||
const StyledFileUploadContainer = styled.div`
|
const StyledFileUploadContainer = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -16,8 +16,8 @@ const StyledFileInput = styled.input`
|
|||||||
display: none;
|
display: none;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const AgentChatFileUpload = ({ agentId }: { agentId: string }) => {
|
export const AgentChatFileUploadButton = ({ agentId }: { agentId: string }) => {
|
||||||
const [, setAgentChatSelectedFiles] = useRecoilComponentStateV2(
|
const setAgentChatSelectedFiles = useSetRecoilComponentStateV2(
|
||||||
agentChatSelectedFilesComponentState,
|
agentChatSelectedFilesComponentState,
|
||||||
agentId,
|
agentId,
|
||||||
);
|
);
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
import { Button } from 'twenty-ui/input';
|
||||||
|
import { t } from '@lingui/core/macro';
|
||||||
|
import { useAgentChat } from '@/ai/hooks/useAgentChat';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
|
||||||
|
export const SendMessageButton = ({
|
||||||
|
records,
|
||||||
|
agentId,
|
||||||
|
}: {
|
||||||
|
agentId: string;
|
||||||
|
records?: ObjectRecord[];
|
||||||
|
}) => {
|
||||||
|
const { isLoading, handleSendMessage, input } = useAgentChat(
|
||||||
|
agentId,
|
||||||
|
records,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="primary"
|
||||||
|
accent="blue"
|
||||||
|
size="small"
|
||||||
|
hotkeys={input && !isLoading ? ['⏎'] : undefined}
|
||||||
|
disabled={!input || isLoading}
|
||||||
|
title={t`Send`}
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||||
|
import { SendMessageButton } from '@/ai/components/internal/SendMessageButton';
|
||||||
|
|
||||||
|
export const SendMessageWithRecordsContextButton = ({
|
||||||
|
agentId,
|
||||||
|
}: {
|
||||||
|
agentId: string;
|
||||||
|
}) => {
|
||||||
|
const { records } = useFindManyRecordsSelectedInContextStore({
|
||||||
|
limit: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
return <SendMessageButton records={records} agentId={agentId} />;
|
||||||
|
};
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
|
||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||||
import { agentChatSelectedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatSelectedFilesComponentState';
|
import { agentChatSelectedFilesComponentState } from '@/ai/states/agentChatSelectedFilesComponentState';
|
||||||
import { agentChatUploadedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatUploadedFilesComponentState';
|
import { agentChatUploadedFilesComponentState } from '@/ai/states/agentChatUploadedFilesComponentState';
|
||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -8,10 +8,10 @@ import { currentAIChatThreadComponentState } from '@/ai/states/currentAIChatThre
|
|||||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||||
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
|
||||||
import { STREAM_CHAT_QUERY } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/api/agent-chat-apollo.api';
|
import { STREAM_CHAT_QUERY } from '@/ai/rest-api/agent-chat-apollo.api';
|
||||||
import { AgentChatMessageRole } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/agent-chat-message-role';
|
import { AgentChatMessageRole } from '@/ai/constants/agent-chat-message-role';
|
||||||
import { agentChatSelectedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatSelectedFilesComponentState';
|
import { agentChatSelectedFilesComponentState } from '@/ai/states/agentChatSelectedFilesComponentState';
|
||||||
import { agentChatUploadedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatUploadedFilesComponentState';
|
import { agentChatUploadedFilesComponentState } from '@/ai/states/agentChatUploadedFilesComponentState';
|
||||||
import { useApolloClient } from '@apollo/client';
|
import { useApolloClient } from '@apollo/client';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
@ -24,20 +24,43 @@ import { agentChatInputState } from '../states/agentChatInputState';
|
|||||||
import { agentChatMessagesComponentState } from '../states/agentChatMessagesComponentState';
|
import { agentChatMessagesComponentState } from '../states/agentChatMessagesComponentState';
|
||||||
import { agentStreamingMessageState } from '../states/agentStreamingMessageState';
|
import { agentStreamingMessageState } from '../states/agentStreamingMessageState';
|
||||||
import { parseAgentStreamingChunk } from '../utils/parseAgentStreamingChunk';
|
import { parseAgentStreamingChunk } from '../utils/parseAgentStreamingChunk';
|
||||||
|
import {
|
||||||
|
agentChatObjectMetadataAndRecordContextState,
|
||||||
|
AIChatObjectMetadataAndRecordContext,
|
||||||
|
} from '@/ai/states/agentChatObjectMetadataAndRecordContextState';
|
||||||
|
import { contextStoreCurrentObjectMetadataItemIdComponentState } from '@/context-store/states/contextStoreCurrentObjectMetadataItemIdComponentState';
|
||||||
|
import { useGetObjectMetadataItemById } from '@/object-metadata/hooks/useGetObjectMetadataItemById';
|
||||||
|
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
|
||||||
|
import { isAgentChatCurrentContextActiveState } from '@/ai/states/isAgentChatCurrentContextActiveState';
|
||||||
|
|
||||||
type OptimisticMessage = AgentChatMessage & {
|
type OptimisticMessage = AgentChatMessage & {
|
||||||
isPending: boolean;
|
isPending: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useAgentChat = (agentId: string) => {
|
export const useAgentChat = (agentId: string, records?: ObjectRecord[]) => {
|
||||||
const apolloClient = useApolloClient();
|
const apolloClient = useApolloClient();
|
||||||
const { enqueueErrorSnackBar } = useSnackBar();
|
const { enqueueErrorSnackBar } = useSnackBar();
|
||||||
|
const { getObjectMetadataItemById } = useGetObjectMetadataItemById();
|
||||||
|
|
||||||
|
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
|
||||||
|
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isAgentChatCurrentContextActive = useRecoilComponentValueV2(
|
||||||
|
isAgentChatCurrentContextActiveState,
|
||||||
|
agentId,
|
||||||
|
);
|
||||||
|
|
||||||
const agentChatSelectedFiles = useRecoilComponentValueV2(
|
const agentChatSelectedFiles = useRecoilComponentValueV2(
|
||||||
agentChatSelectedFilesComponentState,
|
agentChatSelectedFilesComponentState,
|
||||||
agentId,
|
agentId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [agentChatContext, setAgentChatContext] = useRecoilComponentStateV2(
|
||||||
|
agentChatObjectMetadataAndRecordContextState,
|
||||||
|
agentId,
|
||||||
|
);
|
||||||
|
|
||||||
const [currentThreadId, setCurrentThreadId] = useRecoilComponentStateV2(
|
const [currentThreadId, setCurrentThreadId] = useRecoilComponentStateV2(
|
||||||
currentAIChatThreadComponentState,
|
currentAIChatThreadComponentState,
|
||||||
agentId,
|
agentId,
|
||||||
@ -128,6 +151,21 @@ export const useAgentChat = (agentId: string) => {
|
|||||||
|
|
||||||
setIsStreaming(true);
|
setIsStreaming(true);
|
||||||
|
|
||||||
|
const recordIdsByObjectMetadataNameSingular = [];
|
||||||
|
|
||||||
|
if (
|
||||||
|
isAgentChatCurrentContextActive === true &&
|
||||||
|
isDefined(records) &&
|
||||||
|
isDefined(contextStoreCurrentObjectMetadataItemId)
|
||||||
|
) {
|
||||||
|
recordIdsByObjectMetadataNameSingular.push({
|
||||||
|
objectMetadataNameSingular: getObjectMetadataItemById(
|
||||||
|
contextStoreCurrentObjectMetadataItemId,
|
||||||
|
).nameSingular,
|
||||||
|
recordIds: records.map(({ id }) => id),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
await apolloClient.query({
|
await apolloClient.query({
|
||||||
query: STREAM_CHAT_QUERY,
|
query: STREAM_CHAT_QUERY,
|
||||||
variables: {
|
variables: {
|
||||||
@ -135,6 +173,8 @@ export const useAgentChat = (agentId: string) => {
|
|||||||
threadId: currentThreadId,
|
threadId: currentThreadId,
|
||||||
userMessage: content,
|
userMessage: content,
|
||||||
fileIds: agentChatUploadedFiles.map((file) => file.id),
|
fileIds: agentChatUploadedFiles.map((file) => file.id),
|
||||||
|
recordIdsByObjectMetadataNameSingular:
|
||||||
|
recordIdsByObjectMetadataNameSingular,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
context: {
|
context: {
|
||||||
@ -200,6 +240,12 @@ export const useAgentChat = (agentId: string) => {
|
|||||||
await sendChatMessage(content);
|
await sendChatMessage(content);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetContext = async (
|
||||||
|
items: Array<AIChatObjectMetadataAndRecordContext>,
|
||||||
|
) => {
|
||||||
|
setAgentChatContext(items);
|
||||||
|
};
|
||||||
|
|
||||||
useHotkeysOnFocusedElement({
|
useHotkeysOnFocusedElement({
|
||||||
keys: [Key.Enter],
|
keys: [Key.Enter],
|
||||||
callback: (event: KeyboardEvent) => {
|
callback: (event: KeyboardEvent) => {
|
||||||
@ -219,6 +265,8 @@ export const useAgentChat = (agentId: string) => {
|
|||||||
handleInputChange: (value: string) => setAgentChatInput(value),
|
handleInputChange: (value: string) => setAgentChatInput(value),
|
||||||
messages: agentChatMessages,
|
messages: agentChatMessages,
|
||||||
input: agentChatInput,
|
input: agentChatInput,
|
||||||
|
context: agentChatContext,
|
||||||
|
handleSetContext,
|
||||||
handleSendMessage,
|
handleSendMessage,
|
||||||
isLoading,
|
isLoading,
|
||||||
agentStreamingMessage,
|
agentStreamingMessage,
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { WorkflowAiAgentAction } from '@/workflow/types/Workflow';
|
import { WorkflowAiAgentAction } from '@/workflow/types/Workflow';
|
||||||
import { OutputSchemaField } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/output-field-type-options';
|
import { OutputSchemaField } from '@/ai/constants/output-field-type-options';
|
||||||
import { BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
import { BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { AgentChatMessagesComponentInstanceContext } from '@/ai/states/agentChatMessagesComponentState';
|
||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
|
||||||
|
export type AIChatObjectMetadataAndRecordContext = {
|
||||||
|
type: 'objectMetadataId' | 'recordId';
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const agentChatObjectMetadataAndRecordContextState =
|
||||||
|
createComponentStateV2<Array<AIChatObjectMetadataAndRecordContext>>({
|
||||||
|
defaultValue: [],
|
||||||
|
key: 'agentChatObjectMetadataAndRecordContextState',
|
||||||
|
componentInstanceContext: AgentChatMessagesComponentInstanceContext,
|
||||||
|
});
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
import { AgentChatMessagesComponentInstanceContext } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatMessagesComponentState';
|
import { AgentChatMessagesComponentInstanceContext } from '@/ai/states/agentChatMessagesComponentState';
|
||||||
|
|
||||||
export const agentChatSelectedFilesComponentState = createComponentStateV2<
|
export const agentChatSelectedFilesComponentState = createComponentStateV2<
|
||||||
File[]
|
File[]
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
import { AgentChatMessagesComponentInstanceContext } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatMessagesComponentState';
|
import { AgentChatMessagesComponentInstanceContext } from '@/ai/states/agentChatMessagesComponentState';
|
||||||
import { File } from '~/generated-metadata/graphql';
|
import { File } from '~/generated-metadata/graphql';
|
||||||
|
|
||||||
export const agentChatUploadedFilesComponentState = createComponentStateV2<
|
export const agentChatUploadedFilesComponentState = createComponentStateV2<
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
import { createComponentStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentStateV2';
|
||||||
|
import { createComponentInstanceContext } from '@/ui/utilities/state/component-state/utils/createComponentInstanceContext';
|
||||||
|
|
||||||
|
export const IsAgentChatCurrentContextActiveInstanceContext =
|
||||||
|
createComponentInstanceContext();
|
||||||
|
|
||||||
|
export const isAgentChatCurrentContextActiveState =
|
||||||
|
createComponentStateV2<boolean>({
|
||||||
|
defaultValue: true,
|
||||||
|
key: 'isAgentChatCurrentContextActiveState',
|
||||||
|
componentInstanceContext: IsAgentChatCurrentContextActiveInstanceContext,
|
||||||
|
});
|
||||||
@ -1,88 +0,0 @@
|
|||||||
import styled from '@emotion/styled';
|
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
|
||||||
import { Fragment } from 'react/jsx-runtime';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
import { OverflowingTextWithTooltip } from 'twenty-ui/display';
|
|
||||||
|
|
||||||
const StyledChip = styled.button<{
|
|
||||||
withText: boolean;
|
|
||||||
maxWidth?: string;
|
|
||||||
onClick?: () => void;
|
|
||||||
}>`
|
|
||||||
all: unset;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: ${({ theme }) => theme.background.transparent.light};
|
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.medium};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
|
||||||
height: ${({ theme }) => theme.spacing(6)};
|
|
||||||
/* If the chip has text, we add extra padding to have a more balanced design */
|
|
||||||
padding: 0
|
|
||||||
${({ theme, withText }) => (withText ? theme.spacing(2) : theme.spacing(1))};
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
|
||||||
font-weight: ${({ theme }) => theme.font.weight.medium};
|
|
||||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
|
||||||
color: ${({ theme }) => theme.font.color.primary};
|
|
||||||
cursor: ${({ onClick }) => (isDefined(onClick) ? 'pointer' : 'default')};
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background: ${({ onClick, theme }) =>
|
|
||||||
isDefined(onClick)
|
|
||||||
? theme.background.transparent.medium
|
|
||||||
: theme.background.transparent.light};
|
|
||||||
}
|
|
||||||
max-width: ${({ maxWidth }) => maxWidth};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledIconsContainer = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledEmptyText = styled.div`
|
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export type CommandMenuContextChipProps = {
|
|
||||||
Icons: React.ReactNode[];
|
|
||||||
text?: string;
|
|
||||||
onClick?: () => void;
|
|
||||||
testId?: string;
|
|
||||||
maxWidth?: string;
|
|
||||||
forceEmptyText?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const CommandMenuContextChip = ({
|
|
||||||
Icons,
|
|
||||||
text,
|
|
||||||
onClick,
|
|
||||||
testId,
|
|
||||||
maxWidth,
|
|
||||||
forceEmptyText = false,
|
|
||||||
}: CommandMenuContextChipProps) => {
|
|
||||||
return (
|
|
||||||
<StyledChip
|
|
||||||
withText={isNonEmptyString(text)}
|
|
||||||
onClick={onClick}
|
|
||||||
data-testid={testId}
|
|
||||||
maxWidth={maxWidth}
|
|
||||||
>
|
|
||||||
<StyledIconsContainer>
|
|
||||||
{Icons.map((Icon, index) => (
|
|
||||||
<Fragment key={index}>{Icon}</Fragment>
|
|
||||||
))}
|
|
||||||
</StyledIconsContainer>
|
|
||||||
{text?.trim?.() ? (
|
|
||||||
<OverflowingTextWithTooltip text={text} />
|
|
||||||
) : !forceEmptyText ? (
|
|
||||||
<StyledEmptyText>Untitled</StyledEmptyText>
|
|
||||||
) : (
|
|
||||||
''
|
|
||||||
)}
|
|
||||||
</StyledChip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -6,14 +6,14 @@ import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
|||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { MenuItem } from 'twenty-ui/navigation';
|
import { MenuItem } from 'twenty-ui/navigation';
|
||||||
import {
|
import {
|
||||||
CommandMenuContextChip,
|
MultipleAvatarChip,
|
||||||
CommandMenuContextChipProps,
|
MultipleAvatarChipProps,
|
||||||
} from './CommandMenuContextChip';
|
} from 'twenty-ui/components';
|
||||||
|
|
||||||
export const CommandMenuContextChipGroups = ({
|
export const CommandMenuContextChipGroups = ({
|
||||||
contextChips,
|
contextChips,
|
||||||
}: {
|
}: {
|
||||||
contextChips: CommandMenuContextChipProps[];
|
contextChips: MultipleAvatarChipProps[];
|
||||||
}) => {
|
}) => {
|
||||||
const { closeDropdown } = useCloseDropdown();
|
const { closeDropdown } = useCloseDropdown();
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ export const CommandMenuContextChipGroups = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{contextChips.map((chip, index) => (
|
{contextChips.map((chip, index) => (
|
||||||
<CommandMenuContextChip
|
<MultipleAvatarChip
|
||||||
key={index}
|
key={index}
|
||||||
maxWidth={'180px'}
|
maxWidth={180}
|
||||||
Icons={chip.Icons}
|
Icons={chip.Icons}
|
||||||
text={chip.text}
|
text={chip.text}
|
||||||
onClick={chip.onClick}
|
onClick={chip.onClick}
|
||||||
@ -46,7 +46,7 @@ export const CommandMenuContextChipGroups = ({
|
|||||||
{firstChips.length > 0 && (
|
{firstChips.length > 0 && (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
<CommandMenuContextChip
|
<MultipleAvatarChip
|
||||||
Icons={firstThreeChips.map((chip) => chip.Icons?.[0])}
|
Icons={firstThreeChips.map((chip) => chip.Icons?.[0])}
|
||||||
onClick={() => {}}
|
onClick={() => {}}
|
||||||
text={`${firstChips.length}`}
|
text={`${firstChips.length}`}
|
||||||
@ -77,11 +77,11 @@ export const CommandMenuContextChipGroups = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{isDefined(lastChip) && (
|
{isDefined(lastChip) && (
|
||||||
<CommandMenuContextChip
|
<MultipleAvatarChip
|
||||||
Icons={lastChip.Icons}
|
Icons={lastChip.Icons}
|
||||||
text={lastChip.text}
|
text={lastChip.text}
|
||||||
onClick={lastChip.onClick}
|
onClick={lastChip.onClick}
|
||||||
maxWidth={'180px'}
|
maxWidth={180}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -4,14 +4,14 @@ import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
|||||||
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
||||||
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
import { CommandMenuContextChipProps } from './CommandMenuContextChip';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
import { MultipleAvatarChipProps } from 'twenty-ui/components';
|
||||||
|
|
||||||
export const CommandMenuContextChipGroupsWithRecordSelection = ({
|
export const CommandMenuContextChipGroupsWithRecordSelection = ({
|
||||||
contextChips,
|
contextChips,
|
||||||
objectMetadataItemId,
|
objectMetadataItemId,
|
||||||
}: {
|
}: {
|
||||||
contextChips: CommandMenuContextChipProps[];
|
contextChips: MultipleAvatarChipProps[];
|
||||||
objectMetadataItemId: string;
|
objectMetadataItemId: string;
|
||||||
}) => {
|
}) => {
|
||||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { CommandMenuContextChip } from '@/command-menu/components/CommandMenuContextChip';
|
|
||||||
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
|
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
|
||||||
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
||||||
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||||
|
import { MultipleAvatarChip } from 'twenty-ui/components';
|
||||||
|
|
||||||
export const CommandMenuContextRecordsChip = ({
|
export const CommandMenuContextRecordsChip = ({
|
||||||
objectMetadataItemId,
|
objectMetadataItemId,
|
||||||
@ -34,7 +34,7 @@ export const CommandMenuContextRecordsChip = ({
|
|||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommandMenuContextChip
|
<MultipleAvatarChip
|
||||||
text={getSelectedRecordsContextText(
|
text={getSelectedRecordsContextText(
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
records,
|
records,
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { CommandMenuContextChip } from '@/command-menu/components/CommandMenuContextChip';
|
|
||||||
import { CommandMenuContextChipGroups } from '@/command-menu/components/CommandMenuContextChipGroups';
|
import { CommandMenuContextChipGroups } from '@/command-menu/components/CommandMenuContextChipGroups';
|
||||||
import { CommandMenuContextChipGroupsWithRecordSelection } from '@/command-menu/components/CommandMenuContextChipGroupsWithRecordSelection';
|
import { CommandMenuContextChipGroupsWithRecordSelection } from '@/command-menu/components/CommandMenuContextChipGroupsWithRecordSelection';
|
||||||
import { CommandMenuTopBarInputFocusEffect } from '@/command-menu/components/CommandMenuTopBarInputFocusEffect';
|
import { CommandMenuTopBarInputFocusEffect } from '@/command-menu/components/CommandMenuTopBarInputFocusEffect';
|
||||||
@ -22,6 +21,7 @@ import { useLocation } from 'react-router-dom';
|
|||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { IconChevronLeft, IconX } from 'twenty-ui/display';
|
import { IconChevronLeft, IconX } from 'twenty-ui/display';
|
||||||
|
import { MultipleAvatarChip } from 'twenty-ui/components';
|
||||||
import { Button } from 'twenty-ui/input';
|
import { Button } from 'twenty-ui/input';
|
||||||
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
|
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
|
||||||
|
|
||||||
@ -122,7 +122,7 @@ export const CommandMenuTopBar = () => {
|
|||||||
duration: backButtonAnimationDuration,
|
duration: backButtonAnimationDuration,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CommandMenuContextChip
|
<MultipleAvatarChip
|
||||||
Icons={[<IconChevronLeft size={theme.icon.size.sm} />]}
|
Icons={[<IconChevronLeft size={theme.icon.size.sm} />]}
|
||||||
onClick={goBackFromCommandMenu}
|
onClick={goBackFromCommandMenu}
|
||||||
testId="command-menu-go-back-button"
|
testId="command-menu-go-back-button"
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||||
import { AIChatTab } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/AIChatTab';
|
import { AIChatTab } from '@/ai/components/AIChatTab';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
|||||||
@ -1,19 +1,8 @@
|
|||||||
import { AttachmentType } from '@/activities/files/types/Attachment';
|
import { AttachmentType } from '@/activities/files/types/Attachment';
|
||||||
|
import { IconMapping, useFileTypeColors } from '@/file/utils/fileIconMappings';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import {
|
|
||||||
IconComponent,
|
|
||||||
IconFile,
|
|
||||||
IconFileText,
|
|
||||||
IconFileZip,
|
|
||||||
IconHeadphones,
|
|
||||||
IconPhoto,
|
|
||||||
IconPresentation,
|
|
||||||
IconTable,
|
|
||||||
IconVideo,
|
|
||||||
} from 'twenty-ui/display';
|
|
||||||
|
|
||||||
const StyledIconContainer = styled.div<{ background: string }>`
|
const StyledIconContainer = styled.div<{ background: string }>`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ background }) => background};
|
background: ${({ background }) => background};
|
||||||
@ -25,35 +14,14 @@ const StyledIconContainer = styled.div<{ background: string }>`
|
|||||||
padding: ${({ theme }) => theme.spacing(1.25)};
|
padding: ${({ theme }) => theme.spacing(1.25)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const IconMapping: { [key in AttachmentType]: IconComponent } = {
|
|
||||||
Archive: IconFileZip,
|
|
||||||
Audio: IconHeadphones,
|
|
||||||
Image: IconPhoto,
|
|
||||||
Presentation: IconPresentation,
|
|
||||||
Spreadsheet: IconTable,
|
|
||||||
TextDocument: IconFileText,
|
|
||||||
Video: IconVideo,
|
|
||||||
Other: IconFile,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const FileIcon = ({ fileType }: { fileType: AttachmentType }) => {
|
export const FileIcon = ({ fileType }: { fileType: AttachmentType }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const iconColors = useFileTypeColors();
|
||||||
const IconColors: { [key in AttachmentType]: string } = {
|
|
||||||
Archive: theme.color.gray,
|
|
||||||
Audio: theme.color.pink,
|
|
||||||
Image: theme.color.yellow,
|
|
||||||
Presentation: theme.color.orange,
|
|
||||||
Spreadsheet: theme.color.turquoise,
|
|
||||||
TextDocument: theme.color.blue,
|
|
||||||
Video: theme.color.purple,
|
|
||||||
Other: theme.color.gray,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Icon = IconMapping[fileType];
|
const Icon = IconMapping[fileType];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledIconContainer background={IconColors[fileType]}>
|
<StyledIconContainer background={iconColors[fileType]}>
|
||||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||||
</StyledIconContainer>
|
</StyledIconContainer>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { AttachmentType } from '@/activities/files/types/Attachment';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
import {
|
||||||
|
IconComponent,
|
||||||
|
IconFile,
|
||||||
|
IconFileText,
|
||||||
|
IconFileZip,
|
||||||
|
IconHeadphones,
|
||||||
|
IconPhoto,
|
||||||
|
IconPresentation,
|
||||||
|
IconTable,
|
||||||
|
IconVideo,
|
||||||
|
} from 'twenty-ui/display';
|
||||||
|
|
||||||
|
export const IconMapping: { [key in AttachmentType]: IconComponent } = {
|
||||||
|
Archive: IconFileZip,
|
||||||
|
Audio: IconHeadphones,
|
||||||
|
Image: IconPhoto,
|
||||||
|
Presentation: IconPresentation,
|
||||||
|
Spreadsheet: IconTable,
|
||||||
|
TextDocument: IconFileText,
|
||||||
|
Video: IconVideo,
|
||||||
|
Other: IconFile,
|
||||||
|
};
|
||||||
|
|
||||||
|
const getIconColors = (theme: any): { [key in AttachmentType]: string } => ({
|
||||||
|
Archive: theme.color.gray,
|
||||||
|
Audio: theme.color.pink,
|
||||||
|
Image: theme.color.yellow,
|
||||||
|
Presentation: theme.color.orange,
|
||||||
|
Spreadsheet: theme.color.turquoise,
|
||||||
|
TextDocument: theme.color.blue,
|
||||||
|
Video: theme.color.purple,
|
||||||
|
Other: theme.color.gray,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useFileTypeColors = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
return getIconColors(theme);
|
||||||
|
};
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { CustomError } from '@/error-handler/CustomError';
|
||||||
|
import { objectMetadataItemsState } from '@/object-metadata/states/objectMetadataItemsState';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
export const useGetObjectMetadataItemById = () => {
|
||||||
|
const objectMetadataItems = useRecoilValue(objectMetadataItemsState);
|
||||||
|
|
||||||
|
const getObjectMetadataItemById = (objectId: string) => {
|
||||||
|
const objectMetadataItem = objectMetadataItems.find(
|
||||||
|
(objectMetadataItem) => objectMetadataItem.id === objectId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isDefined(objectMetadataItem)) {
|
||||||
|
throw new CustomError(
|
||||||
|
`Object metadata item not found for id ${objectId}`,
|
||||||
|
'OBJECT_METADATA_ITEM_NOT_FOUND',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objectMetadataItem;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
getObjectMetadataItemById,
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -10,11 +10,11 @@ import { MouseEvent } from 'react';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import {
|
import {
|
||||||
|
Chip,
|
||||||
AvatarChip,
|
AvatarChip,
|
||||||
AvatarChipVariant,
|
|
||||||
ChipSize,
|
ChipSize,
|
||||||
ChipVariant,
|
ChipVariant,
|
||||||
LinkAvatarChip,
|
LinkChip,
|
||||||
} from 'twenty-ui/components';
|
} from 'twenty-ui/components';
|
||||||
import { TriggerEventType } from 'twenty-ui/utilities';
|
import { TriggerEventType } from 'twenty-ui/utilities';
|
||||||
|
|
||||||
@ -22,7 +22,7 @@ export type RecordChipProps = {
|
|||||||
objectNameSingular: string;
|
objectNameSingular: string;
|
||||||
record: ObjectRecord;
|
record: ObjectRecord;
|
||||||
className?: string;
|
className?: string;
|
||||||
variant?: AvatarChipVariant;
|
variant?: ChipVariant.Highlighted | ChipVariant.Transparent;
|
||||||
forceDisableClick?: boolean;
|
forceDisableClick?: boolean;
|
||||||
maxWidth?: number;
|
maxWidth?: number;
|
||||||
to?: string | undefined;
|
to?: string | undefined;
|
||||||
@ -72,39 +72,42 @@ export const RecordChip = ({
|
|||||||
|
|
||||||
// TODO temporary until we create a record show page for Workspaces members
|
// TODO temporary until we create a record show page for Workspaces members
|
||||||
|
|
||||||
|
const avatarChip = (
|
||||||
|
<AvatarChip
|
||||||
|
placeholder={recordChipData.name}
|
||||||
|
placeholderColorSeed={record.id}
|
||||||
|
avatarType={recordChipData.avatarType}
|
||||||
|
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
forceDisableClick ||
|
forceDisableClick ||
|
||||||
objectNameSingular === CoreObjectNameSingular.WorkspaceMember
|
objectNameSingular === CoreObjectNameSingular.WorkspaceMember
|
||||||
) {
|
) {
|
||||||
return (
|
return (
|
||||||
<AvatarChip
|
<Chip
|
||||||
|
label={recordChipData.name}
|
||||||
size={size}
|
size={size}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
placeholderColorSeed={record.id}
|
|
||||||
name={recordChipData.name}
|
|
||||||
avatarType={recordChipData.avatarType}
|
|
||||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
|
||||||
className={className}
|
className={className}
|
||||||
variant={ChipVariant.Transparent}
|
variant={ChipVariant.Transparent}
|
||||||
|
leftComponent={avatarChip}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LinkAvatarChip
|
<LinkChip
|
||||||
size={size}
|
size={size}
|
||||||
maxWidth={maxWidth}
|
maxWidth={maxWidth}
|
||||||
placeholderColorSeed={record.id}
|
label={recordChipData.name}
|
||||||
name={recordChipData.name}
|
|
||||||
isLabelHidden={isLabelHidden}
|
isLabelHidden={isLabelHidden}
|
||||||
avatarType={recordChipData.avatarType}
|
leftComponent={avatarChip}
|
||||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
|
||||||
className={className}
|
className={className}
|
||||||
variant={
|
variant={
|
||||||
variant ??
|
variant ??
|
||||||
(!forceDisableClick
|
(!forceDisableClick ? ChipVariant.Highlighted : ChipVariant.Transparent)
|
||||||
? AvatarChipVariant.Regular
|
|
||||||
: AvatarChipVariant.Transparent)
|
|
||||||
}
|
}
|
||||||
to={to ?? getLinkToShowPage(objectNameSingular, record)}
|
to={to ?? getLinkToShowPage(objectNameSingular, record)}
|
||||||
onClick={handleCustomClick}
|
onClick={handleCustomClick}
|
||||||
|
|||||||
@ -19,9 +19,9 @@ import styled from '@emotion/styled';
|
|||||||
import { Dispatch, SetStateAction, useContext } from 'react';
|
import { Dispatch, SetStateAction, useContext } from 'react';
|
||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { AvatarChipVariant } from 'twenty-ui/components';
|
|
||||||
import { IconEye, IconEyeOff } from 'twenty-ui/display';
|
import { IconEye, IconEyeOff } from 'twenty-ui/display';
|
||||||
import { Checkbox, CheckboxVariant, LightIconButton } from 'twenty-ui/input';
|
import { Checkbox, CheckboxVariant, LightIconButton } from 'twenty-ui/input';
|
||||||
|
import { ChipVariant } from 'twenty-ui/components';
|
||||||
|
|
||||||
const StyledCompactIconContainer = styled.div`
|
const StyledCompactIconContainer = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -80,7 +80,7 @@ export const RecordBoardCardHeader = ({
|
|||||||
<RecordChip
|
<RecordChip
|
||||||
objectNameSingular={objectMetadataItem.nameSingular}
|
objectNameSingular={objectMetadataItem.nameSingular}
|
||||||
record={record}
|
record={record}
|
||||||
variant={AvatarChipVariant.Transparent}
|
variant={ChipVariant.Transparent}
|
||||||
maxWidth={150}
|
maxWidth={150}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
activateBoardCard({ rowIndex, columnIndex });
|
activateBoardCard({ rowIndex, columnIndex });
|
||||||
|
|||||||
@ -20,7 +20,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
|||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||||
import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils';
|
import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils';
|
||||||
import { AvatarChip } from 'twenty-ui/components';
|
import { Chip, AvatarChip } from 'twenty-ui/components';
|
||||||
import {
|
import {
|
||||||
H2Title,
|
H2Title,
|
||||||
IconEyeShare,
|
IconEyeShare,
|
||||||
@ -137,15 +137,19 @@ export const SettingsAdminWorkspaceContent = ({
|
|||||||
Icon: IconHome,
|
Icon: IconHome,
|
||||||
label: t`Name`,
|
label: t`Name`,
|
||||||
value: (
|
value: (
|
||||||
<AvatarChip
|
<Chip
|
||||||
name={activeWorkspace?.name ?? ''}
|
label={activeWorkspace?.name ?? ''}
|
||||||
avatarUrl={
|
leftComponent={
|
||||||
getImageAbsoluteURI({
|
<AvatarChip
|
||||||
imageUrl: isNonEmptyString(activeWorkspace?.logo)
|
avatarUrl={
|
||||||
? activeWorkspace?.logo
|
getImageAbsoluteURI({
|
||||||
: DEFAULT_WORKSPACE_LOGO,
|
imageUrl: isNonEmptyString(activeWorkspace?.logo)
|
||||||
baseUrl: REACT_APP_SERVER_BASE_URL,
|
? activeWorkspace?.logo
|
||||||
}) ?? ''
|
: DEFAULT_WORKSPACE_LOGO,
|
||||||
|
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||||
|
}) ?? ''
|
||||||
|
}
|
||||||
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { FieldActorValue } from '@/object-record/record-field/types/FieldMetadat
|
|||||||
|
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
import { AvatarChip } from 'twenty-ui/components';
|
import { Chip, AvatarChip } from 'twenty-ui/components';
|
||||||
import {
|
import {
|
||||||
IconApi,
|
IconApi,
|
||||||
IconCalendar,
|
IconCalendar,
|
||||||
@ -71,13 +71,18 @@ export const ActorDisplay = ({
|
|||||||
source === 'API' || source === 'IMPORT' || source === 'SYSTEM';
|
source === 'API' || source === 'IMPORT' || source === 'SYSTEM';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AvatarChip
|
<Chip
|
||||||
placeholderColorSeed={workspaceMemberId ?? undefined}
|
label={name ?? ''}
|
||||||
name={name ?? ''}
|
leftComponent={
|
||||||
avatarType={workspaceMemberId ? 'rounded' : 'squared'}
|
<AvatarChip
|
||||||
LeftIcon={LeftIcon}
|
placeholderColorSeed={workspaceMemberId ?? undefined}
|
||||||
avatarUrl={avatarUrl ?? undefined}
|
avatarType={workspaceMemberId ? 'rounded' : 'squared'}
|
||||||
isIconInverted={isIconInverted}
|
placeholder={name}
|
||||||
|
Icon={LeftIcon}
|
||||||
|
avatarUrl={avatarUrl ?? undefined}
|
||||||
|
isIconInverted={isIconInverted}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,103 +0,0 @@
|
|||||||
import { getFileType } from '@/activities/files/utils/getFileType';
|
|
||||||
import { FileIcon } from '@/file/components/FileIcon';
|
|
||||||
import { formatFileSize } from '@/file/utils/formatFileSize';
|
|
||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { IconX } from 'twenty-ui/display';
|
|
||||||
import { Loader } from 'twenty-ui/feedback';
|
|
||||||
import { File as FileDocument } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
const StyledFileChip = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
background: ${({ theme }) => theme.background.transparent.lighter};
|
|
||||||
border-radius: ${({ theme }) => theme.border.radius.md};
|
|
||||||
height: 40px;
|
|
||||||
border: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledFileIconContainer = styled.div`
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
padding-inline: ${({ theme }) => theme.spacing(2)};
|
|
||||||
overflow: hidden;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledFileInfo = styled.div`
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: ${({ theme }) => theme.spacing(1)};
|
|
||||||
border-left: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
padding-inline: ${({ theme }) => theme.spacing(2)};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledFileName = styled.div`
|
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
|
||||||
font-weight: 500;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
max-width: 125px;
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledFileSize = styled.div`
|
|
||||||
color: ${({ theme }) => theme.font.color.secondary};
|
|
||||||
font-size: ${({ theme }) => theme.font.size.sm};
|
|
||||||
`;
|
|
||||||
|
|
||||||
const StyledRemoveIconContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
height: 100%;
|
|
||||||
padding-inline: ${({ theme }) => theme.spacing(3)};
|
|
||||||
border-left: 1px solid ${({ theme }) => theme.border.color.light};
|
|
||||||
|
|
||||||
svg {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AgentChatFilePreview = ({
|
|
||||||
file,
|
|
||||||
onRemove,
|
|
||||||
isUploading,
|
|
||||||
}: {
|
|
||||||
file: File | FileDocument;
|
|
||||||
onRemove?: () => void;
|
|
||||||
isUploading?: boolean;
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<StyledFileChip key={file.name}>
|
|
||||||
<StyledFileIconContainer>
|
|
||||||
{isUploading ? (
|
|
||||||
<Loader color="yellow" />
|
|
||||||
) : (
|
|
||||||
<FileIcon fileType={getFileType(file.name)} />
|
|
||||||
)}
|
|
||||||
</StyledFileIconContainer>
|
|
||||||
<StyledFileInfo>
|
|
||||||
<StyledFileName title={file.name}>{file.name}</StyledFileName>
|
|
||||||
<StyledFileSize>{formatFileSize(file.size)}</StyledFileSize>
|
|
||||||
</StyledFileInfo>
|
|
||||||
{onRemove && (
|
|
||||||
<StyledRemoveIconContainer>
|
|
||||||
<IconX
|
|
||||||
size={theme.icon.size.md}
|
|
||||||
color={theme.font.color.secondary}
|
|
||||||
onClick={onRemove}
|
|
||||||
/>
|
|
||||||
</StyledRemoveIconContainer>
|
|
||||||
)}
|
|
||||||
</StyledFileChip>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
|
||||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
|
||||||
import { AgentChatFilePreview } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/AgentChatFilePreview';
|
|
||||||
import { agentChatSelectedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatSelectedFilesComponentState';
|
|
||||||
import { agentChatUploadedFilesComponentState } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatUploadedFilesComponentState';
|
|
||||||
import styled from '@emotion/styled';
|
|
||||||
import { useLingui } from '@lingui/react/macro';
|
|
||||||
import { useDeleteFileMutation } from '~/generated-metadata/graphql';
|
|
||||||
|
|
||||||
const StyledPreviewContainer = styled.div`
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: ${({ theme }) => theme.spacing(2)};
|
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
|
||||||
width: 100%;
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AgentChatSelectedFilesPreview = ({
|
|
||||||
agentId,
|
|
||||||
}: {
|
|
||||||
agentId: string;
|
|
||||||
}) => {
|
|
||||||
const { t } = useLingui();
|
|
||||||
const [agentChatSelectedFiles, setAgentChatSelectedFiles] =
|
|
||||||
useRecoilComponentStateV2(agentChatSelectedFilesComponentState, agentId);
|
|
||||||
const [agentChatUploadedFiles, setAgentChatUploadedFiles] =
|
|
||||||
useRecoilComponentStateV2(agentChatUploadedFilesComponentState, agentId);
|
|
||||||
|
|
||||||
const { enqueueErrorSnackBar } = useSnackBar();
|
|
||||||
|
|
||||||
const [deleteFile] = useDeleteFileMutation();
|
|
||||||
|
|
||||||
const handleRemoveUploadedFile = async (fileId: string) => {
|
|
||||||
const originalFiles = agentChatUploadedFiles;
|
|
||||||
|
|
||||||
setAgentChatUploadedFiles(
|
|
||||||
agentChatUploadedFiles.filter((f) => f.id !== fileId),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await deleteFile({ variables: { fileId } });
|
|
||||||
} catch (error) {
|
|
||||||
setAgentChatUploadedFiles(originalFiles);
|
|
||||||
enqueueErrorSnackBar({
|
|
||||||
message: t`Failed to remove file`,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return [...agentChatSelectedFiles, ...agentChatUploadedFiles].length > 0 ? (
|
|
||||||
<StyledPreviewContainer>
|
|
||||||
{agentChatSelectedFiles.map((file) => (
|
|
||||||
<AgentChatFilePreview
|
|
||||||
file={file}
|
|
||||||
key={file.name}
|
|
||||||
onRemove={() => {
|
|
||||||
setAgentChatSelectedFiles(
|
|
||||||
agentChatSelectedFiles.filter((f) => f.name !== file.name),
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
isUploading
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{agentChatUploadedFiles.map((file) => (
|
|
||||||
<AgentChatFilePreview
|
|
||||||
file={file}
|
|
||||||
key={file.id}
|
|
||||||
onRemove={() => handleRemoveUploadedFile(file.id)}
|
|
||||||
isUploading={false}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</StyledPreviewContainer>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
@ -6,7 +6,7 @@ import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/
|
|||||||
import { WorkflowAiAgentAction } from '@/workflow/types/Workflow';
|
import { WorkflowAiAgentAction } from '@/workflow/types/Workflow';
|
||||||
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||||
import { AIChatTab } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/AIChatTab';
|
import { AIChatTab } from '@/ai/components/AIChatTab';
|
||||||
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
|
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
|
||||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||||
import { BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
import { BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
||||||
@ -23,10 +23,10 @@ import {
|
|||||||
WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID,
|
WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID,
|
||||||
WorkflowAiAgentTabId,
|
WorkflowAiAgentTabId,
|
||||||
} from '../constants/workflow-ai-agent-tabs';
|
} from '../constants/workflow-ai-agent-tabs';
|
||||||
import { useAgentRoleAssignment } from '../hooks/useAgentRoleAssignment';
|
import { useAgentRoleAssignment } from '@/ai/hooks/useAgentRoleAssignment';
|
||||||
import { useAgentUpdateFormState } from '../hooks/useAgentUpdateFormState';
|
import { useAgentUpdateFormState } from '@/ai/hooks/useAgentUpdateFormState';
|
||||||
import { useAiAgentOutputSchema } from '../hooks/useAiAgentOutputSchema';
|
import { useAiAgentOutputSchema } from '@/ai/hooks/useAiAgentOutputSchema';
|
||||||
import { useAiModelOptions } from '../hooks/useAiModelOptions';
|
import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions';
|
||||||
import { WorkflowOutputSchemaBuilder } from './WorkflowOutputSchemaBuilder';
|
import { WorkflowOutputSchemaBuilder } from './WorkflowOutputSchemaBuilder';
|
||||||
|
|
||||||
const StyledErrorMessage = styled.div`
|
const StyledErrorMessage = styled.div`
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Select } from '@/ui/input/components/Select';
|
import { Select } from '@/ui/input/components/Select';
|
||||||
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
|
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
import { OUTPUT_FIELD_TYPE_OPTIONS } from '../constants/output-field-type-options';
|
import { OUTPUT_FIELD_TYPE_OPTIONS } from '@/ai/constants/output-field-type-options';
|
||||||
|
|
||||||
type WorkflowOutputFieldTypeSelectorProps = {
|
type WorkflowOutputFieldTypeSelectorProps = {
|
||||||
value?: InputSchemaPropertyType;
|
value?: InputSchemaPropertyType;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/form-types/comp
|
|||||||
|
|
||||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||||
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
|
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
|
||||||
import { OutputSchemaField } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/output-field-type-options';
|
import { OutputSchemaField } from '@/ai/constants/output-field-type-options';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
|
|||||||
@ -15,6 +15,9 @@ import { RestApiExceptionFilter } from 'src/engine/api/rest/rest-api-exception.f
|
|||||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||||
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
|
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
|
||||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||||
|
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||||
|
|
||||||
import { AgentChatService } from './agent-chat.service';
|
import { AgentChatService } from './agent-chat.service';
|
||||||
import { AgentStreamingService } from './agent-streaming.service';
|
import { AgentStreamingService } from './agent-streaming.service';
|
||||||
@ -58,8 +61,14 @@ export class AgentChatController {
|
|||||||
@Post('stream')
|
@Post('stream')
|
||||||
async streamAgentChat(
|
async streamAgentChat(
|
||||||
@Body()
|
@Body()
|
||||||
body: { threadId: string; userMessage: string; fileIds?: string[] },
|
body: {
|
||||||
|
threadId: string;
|
||||||
|
userMessage: string;
|
||||||
|
fileIds?: string[];
|
||||||
|
recordIdsByObjectMetadataNameSingular?: RecordIdsByObjectMetadataNameSingularType;
|
||||||
|
},
|
||||||
@AuthUserWorkspaceId() userWorkspaceId: string,
|
@AuthUserWorkspaceId() userWorkspaceId: string,
|
||||||
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Res() res: Response,
|
@Res() res: Response,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
@ -67,7 +76,10 @@ export class AgentChatController {
|
|||||||
threadId: body.threadId,
|
threadId: body.threadId,
|
||||||
userMessage: body.userMessage,
|
userMessage: body.userMessage,
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
|
workspace,
|
||||||
fileIds: body.fileIds || [],
|
fileIds: body.fileIds || [],
|
||||||
|
recordIdsByObjectMetadataNameSingular:
|
||||||
|
body.recordIdsByObjectMetadataNameSingular || [],
|
||||||
res,
|
res,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
generateText,
|
generateText,
|
||||||
ImagePart,
|
ImagePart,
|
||||||
streamText,
|
streamText,
|
||||||
TextPart,
|
UserContent,
|
||||||
} from 'ai';
|
} from 'ai';
|
||||||
import { In, Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
|
|
||||||
@ -37,9 +37,13 @@ import { convertOutputSchemaToZod } from 'src/engine/metadata-modules/agent/util
|
|||||||
import { OutputSchema } from 'src/modules/workflow/workflow-builder/workflow-schema/types/output-schema.type';
|
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 { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||||
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
|
|
||||||
import { AgentEntity } from './agent.entity';
|
|
||||||
import { AgentException, AgentExceptionCode } from './agent.exception';
|
import { AgentException, AgentExceptionCode } from './agent.exception';
|
||||||
|
import { AgentEntity } from './agent.entity';
|
||||||
|
|
||||||
export interface AgentExecutionResult {
|
export interface AgentExecutionResult {
|
||||||
result: {
|
result: {
|
||||||
@ -61,6 +65,8 @@ export class AgentExecutionService {
|
|||||||
private readonly twentyConfigService: TwentyConfigService,
|
private readonly twentyConfigService: TwentyConfigService,
|
||||||
private readonly agentToolService: AgentToolService,
|
private readonly agentToolService: AgentToolService,
|
||||||
private readonly fileService: FileService,
|
private readonly fileService: FileService,
|
||||||
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||||
@InjectRepository(AgentEntity, 'core')
|
@InjectRepository(AgentEntity, 'core')
|
||||||
private readonly agentRepository: Repository<AgentEntity>,
|
private readonly agentRepository: Repository<AgentEntity>,
|
||||||
@ -184,34 +190,88 @@ export class AgentExecutionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async buildUserMessageWithFiles(
|
private async buildUserMessageWithFiles(
|
||||||
userMessage: string,
|
fileIds: string[],
|
||||||
fileIds?: string[],
|
): Promise<(ImagePart | FilePart)[]> {
|
||||||
): Promise<CoreUserMessage> {
|
|
||||||
if (!fileIds || fileIds.length === 0) {
|
|
||||||
return { role: AgentChatMessageRole.USER, content: userMessage };
|
|
||||||
}
|
|
||||||
|
|
||||||
const files = await this.fileRepository.find({
|
const files = await this.fileRepository.find({
|
||||||
where: {
|
where: {
|
||||||
id: In(fileIds),
|
id: In(fileIds),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const textPart: TextPart = {
|
return await Promise.all(files.map((file) => this.createFilePart(file)));
|
||||||
type: 'text',
|
}
|
||||||
text: userMessage,
|
|
||||||
};
|
|
||||||
|
|
||||||
const fileParts = await Promise.all(
|
private async buildUserMessage(
|
||||||
files.map((file) => this.createFilePart(file)),
|
userMessage: string,
|
||||||
);
|
fileIds: string[],
|
||||||
|
): Promise<CoreUserMessage> {
|
||||||
|
const content: Exclude<UserContent, string> = [
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
text: userMessage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (fileIds.length !== 0) {
|
||||||
|
content.push(...(await this.buildUserMessageWithFiles(fileIds)));
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
role: AgentChatMessageRole.USER,
|
role: AgentChatMessageRole.USER,
|
||||||
content: [textPart, ...fileParts],
|
content,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getContextForSystemPrompt(
|
||||||
|
workspace: Workspace,
|
||||||
|
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType,
|
||||||
|
userWorkspaceId: string,
|
||||||
|
) {
|
||||||
|
const roleId =
|
||||||
|
await this.workspacePermissionsCacheService.getRoleIdFromUserWorkspaceId({
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
userWorkspaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!roleId) {
|
||||||
|
throw new AgentException(
|
||||||
|
'Failed to retrieve user role.',
|
||||||
|
AgentExceptionCode.ROLE_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceDataSource =
|
||||||
|
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
||||||
|
workspaceId: workspace.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const contextObject = (
|
||||||
|
await Promise.all(
|
||||||
|
recordIdsByObjectMetadataNameSingular.map(
|
||||||
|
(recordsWithObjectMetadataNameSingular) => {
|
||||||
|
if (recordsWithObjectMetadataNameSingular.recordIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const repository = workspaceDataSource.getRepository(
|
||||||
|
recordsWithObjectMetadataNameSingular.objectMetadataNameSingular,
|
||||||
|
false,
|
||||||
|
roleId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return repository.find({
|
||||||
|
where: {
|
||||||
|
id: In(recordsWithObjectMetadataNameSingular.recordIds),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
).flat(2);
|
||||||
|
|
||||||
|
return JSON.stringify(contextObject);
|
||||||
|
}
|
||||||
|
|
||||||
private async createFilePart(
|
private async createFilePart(
|
||||||
file: FileEntity,
|
file: FileEntity,
|
||||||
): Promise<ImagePart | FilePart> {
|
): Promise<ImagePart | FilePart> {
|
||||||
@ -241,15 +301,21 @@ export class AgentExecutionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async streamChatResponse({
|
async streamChatResponse({
|
||||||
|
workspace,
|
||||||
|
userWorkspaceId,
|
||||||
agentId,
|
agentId,
|
||||||
userMessage,
|
userMessage,
|
||||||
messages,
|
messages,
|
||||||
fileIds,
|
fileIds,
|
||||||
|
recordIdsByObjectMetadataNameSingular,
|
||||||
}: {
|
}: {
|
||||||
|
workspace: Workspace;
|
||||||
|
userWorkspaceId: string;
|
||||||
agentId: string;
|
agentId: string;
|
||||||
userMessage: string;
|
userMessage: string;
|
||||||
messages: AgentChatMessageEntity[];
|
messages: AgentChatMessageEntity[];
|
||||||
fileIds?: string[];
|
fileIds: string[];
|
||||||
|
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType;
|
||||||
}) {
|
}) {
|
||||||
const agent = await this.agentRepository.findOneOrFail({
|
const agent = await this.agentRepository.findOneOrFail({
|
||||||
where: { id: agentId },
|
where: { id: agentId },
|
||||||
@ -260,7 +326,19 @@ export class AgentExecutionService {
|
|||||||
content,
|
content,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const userMessageWithFiles = await this.buildUserMessageWithFiles(
|
let contextString = '';
|
||||||
|
|
||||||
|
if (recordIdsByObjectMetadataNameSingular.length > 0) {
|
||||||
|
const contextPart = await this.getContextForSystemPrompt(
|
||||||
|
workspace,
|
||||||
|
recordIdsByObjectMetadataNameSingular,
|
||||||
|
userWorkspaceId,
|
||||||
|
);
|
||||||
|
|
||||||
|
contextString = `\n\nCONTEXT:\n${contextPart}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userMessageWithFiles = await this.buildUserMessage(
|
||||||
userMessage,
|
userMessage,
|
||||||
fileIds,
|
fileIds,
|
||||||
);
|
);
|
||||||
@ -268,7 +346,7 @@ export class AgentExecutionService {
|
|||||||
llmMessages.push(userMessageWithFiles);
|
llmMessages.push(userMessageWithFiles);
|
||||||
|
|
||||||
const aiRequestConfig = await this.prepareAIRequestConfig({
|
const aiRequestConfig = await this.prepareAIRequestConfig({
|
||||||
system: `${AGENT_SYSTEM_PROMPTS.AGENT_CHAT}\n\n${agent.prompt}`,
|
system: `${AGENT_SYSTEM_PROMPTS.AGENT_CHAT}\n\n${agent.prompt}${contextString}`,
|
||||||
agent,
|
agent,
|
||||||
messages: llmMessages,
|
messages: llmMessages,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -12,21 +12,19 @@ import {
|
|||||||
AgentException,
|
AgentException,
|
||||||
AgentExceptionCode,
|
AgentExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/agent/agent.exception';
|
} from 'src/engine/metadata-modules/agent/agent.exception';
|
||||||
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
|
import { RecordIdsByObjectMetadataNameSingularType } from 'src/engine/metadata-modules/agent/types/recordIdsByObjectMetadataNameSingular.type';
|
||||||
|
|
||||||
export type StreamAgentChatOptions = {
|
export type StreamAgentChatOptions = {
|
||||||
threadId: string;
|
threadId: string;
|
||||||
userMessage: string;
|
userMessage: string;
|
||||||
userWorkspaceId: string;
|
userWorkspaceId: string;
|
||||||
fileIds?: string[];
|
workspace: Workspace;
|
||||||
|
fileIds: string[];
|
||||||
|
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType;
|
||||||
res: Response;
|
res: Response;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type StreamAgentChatResult = {
|
|
||||||
success: boolean;
|
|
||||||
error?: string;
|
|
||||||
aiResponse?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AgentStreamingService {
|
export class AgentStreamingService {
|
||||||
private readonly logger = new Logger(AgentStreamingService.name);
|
private readonly logger = new Logger(AgentStreamingService.name);
|
||||||
@ -42,7 +40,9 @@ export class AgentStreamingService {
|
|||||||
threadId,
|
threadId,
|
||||||
userMessage,
|
userMessage,
|
||||||
userWorkspaceId,
|
userWorkspaceId,
|
||||||
fileIds = [],
|
workspace,
|
||||||
|
fileIds,
|
||||||
|
recordIdsByObjectMetadataNameSingular,
|
||||||
res,
|
res,
|
||||||
}: StreamAgentChatOptions) {
|
}: StreamAgentChatOptions) {
|
||||||
try {
|
try {
|
||||||
@ -65,10 +65,13 @@ export class AgentStreamingService {
|
|||||||
|
|
||||||
const { fullStream } =
|
const { fullStream } =
|
||||||
await this.agentExecutionService.streamChatResponse({
|
await this.agentExecutionService.streamChatResponse({
|
||||||
|
workspace,
|
||||||
agentId: thread.agent.id,
|
agentId: thread.agent.id,
|
||||||
|
userWorkspaceId,
|
||||||
userMessage,
|
userMessage,
|
||||||
messages: thread.messages,
|
messages: thread.messages,
|
||||||
fileIds,
|
fileIds,
|
||||||
|
recordIdsByObjectMetadataNameSingular,
|
||||||
});
|
});
|
||||||
|
|
||||||
let aiResponse = '';
|
let aiResponse = '';
|
||||||
|
|||||||
@ -12,4 +12,5 @@ export enum AgentExceptionCode {
|
|||||||
AGENT_EXECUTION_FAILED = 'AGENT_EXECUTION_FAILED',
|
AGENT_EXECUTION_FAILED = 'AGENT_EXECUTION_FAILED',
|
||||||
API_KEY_NOT_CONFIGURED = 'API_KEY_NOT_CONFIGURED',
|
API_KEY_NOT_CONFIGURED = 'API_KEY_NOT_CONFIGURED',
|
||||||
USER_WORKSPACE_ID_NOT_FOUND = 'USER_WORKSPACE_ID_NOT_FOUND',
|
USER_WORKSPACE_ID_NOT_FOUND = 'USER_WORKSPACE_ID_NOT_FOUND',
|
||||||
|
ROLE_NOT_FOUND = 'ROLE_NOT_FOUND',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
export type RecordIdsByObjectMetadataNameSingularType = Array<{
|
||||||
|
objectMetadataNameSingular: string;
|
||||||
|
recordIds: string[];
|
||||||
|
}>;
|
||||||
@ -1,38 +1,114 @@
|
|||||||
import { AvatarChipsLeftComponent } from '@ui/components/avatar-chip/AvatarChipLeftComponent';
|
import { useTheme } from '@emotion/react';
|
||||||
import { AvatarChipsCommonProps } from '@ui/components/avatar-chip/types/AvatarChipsCommonProps.type';
|
import styled from '@emotion/styled';
|
||||||
import { Chip, ChipVariant } from '@ui/components/chip/Chip';
|
import { Avatar } from '@ui/display/avatar/components/Avatar';
|
||||||
|
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
|
||||||
|
import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
||||||
|
import { Nullable } from '@ui/utilities';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
const StyledIconWithBackgroundContainer = styled.div<{
|
||||||
|
backgroundColor: string;
|
||||||
|
}>`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: ${({ backgroundColor }) => backgroundColor};
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledAvatarChipWrapper = styled.div<{
|
||||||
|
isClickable: boolean;
|
||||||
|
divider: AvatarChipProps['divider'];
|
||||||
|
theme: any;
|
||||||
|
}>`
|
||||||
|
${({ divider, theme }) => {
|
||||||
|
const borderStyle = (side: 'left' | 'right') =>
|
||||||
|
`border-${side}: 1px solid ${theme.border.color.light};`;
|
||||||
|
return divider ? borderStyle(divider) : '';
|
||||||
|
}}
|
||||||
|
|
||||||
|
cursor: ${({ isClickable }) => (isClickable ? 'pointer' : 'inherit')};
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type AvatarChipProps = {
|
||||||
|
placeholder?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
avatarType?: Nullable<AvatarType>;
|
||||||
|
Icon?: IconComponent;
|
||||||
|
IconColor?: string;
|
||||||
|
IconBackgroundColor?: string;
|
||||||
|
isIconInverted?: boolean;
|
||||||
|
placeholderColorSeed?: string;
|
||||||
|
divider?: 'right' | 'left';
|
||||||
|
onClick?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
export type AvatarChipProps = AvatarChipsCommonProps;
|
|
||||||
export const AvatarChip = ({
|
export const AvatarChip = ({
|
||||||
name,
|
Icon,
|
||||||
LeftIcon,
|
placeholderColorSeed,
|
||||||
LeftIconColor,
|
|
||||||
avatarType,
|
avatarType,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
className,
|
placeholder,
|
||||||
isIconInverted,
|
isIconInverted = false,
|
||||||
maxWidth,
|
IconColor,
|
||||||
placeholderColorSeed,
|
IconBackgroundColor,
|
||||||
size,
|
onClick,
|
||||||
variant = ChipVariant.Transparent,
|
divider,
|
||||||
}: AvatarChipProps) => (
|
}: AvatarChipProps) => {
|
||||||
<Chip
|
const theme = useTheme();
|
||||||
label={name}
|
if (!isDefined(Icon)) {
|
||||||
variant={variant}
|
return (
|
||||||
size={size}
|
<Avatar
|
||||||
leftComponent={
|
|
||||||
<AvatarChipsLeftComponent
|
|
||||||
name={name}
|
|
||||||
LeftIcon={LeftIcon}
|
|
||||||
LeftIconColor={LeftIconColor}
|
|
||||||
avatarType={avatarType}
|
|
||||||
avatarUrl={avatarUrl}
|
avatarUrl={avatarUrl}
|
||||||
isIconInverted={isIconInverted}
|
|
||||||
placeholderColorSeed={placeholderColorSeed}
|
placeholderColorSeed={placeholderColorSeed}
|
||||||
|
placeholder={placeholder}
|
||||||
|
size="sm"
|
||||||
|
type={avatarType}
|
||||||
|
onClick={onClick}
|
||||||
/>
|
/>
|
||||||
}
|
);
|
||||||
clickable={false}
|
}
|
||||||
className={className}
|
|
||||||
maxWidth={maxWidth}
|
const isClickable = isDefined(onClick);
|
||||||
/>
|
|
||||||
);
|
if (isIconInverted || isDefined(IconBackgroundColor)) {
|
||||||
|
return (
|
||||||
|
<StyledAvatarChipWrapper
|
||||||
|
isClickable={isClickable}
|
||||||
|
divider={divider}
|
||||||
|
theme={theme}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<StyledIconWithBackgroundContainer
|
||||||
|
backgroundColor={
|
||||||
|
IconBackgroundColor ?? theme.background.invertedSecondary
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
color={theme.font.color.inverted}
|
||||||
|
size={theme.icon.size.sm}
|
||||||
|
stroke={theme.icon.stroke.sm}
|
||||||
|
/>
|
||||||
|
</StyledIconWithBackgroundContainer>
|
||||||
|
</StyledAvatarChipWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledAvatarChipWrapper
|
||||||
|
isClickable={isClickable}
|
||||||
|
divider={divider}
|
||||||
|
theme={theme}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
<Icon
|
||||||
|
size={theme.icon.size.sm}
|
||||||
|
stroke={theme.icon.stroke.sm}
|
||||||
|
color={IconColor || 'currentColor'}
|
||||||
|
/>
|
||||||
|
</StyledAvatarChipWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@ -1,74 +0,0 @@
|
|||||||
import { useTheme } from '@emotion/react';
|
|
||||||
import { styled } from '@linaria/react';
|
|
||||||
import { Avatar } from '@ui/display/avatar/components/Avatar';
|
|
||||||
import { AvatarType } from '@ui/display/avatar/types/AvatarType';
|
|
||||||
import { IconComponent } from '@ui/display/icon/types/IconComponent';
|
|
||||||
import { Nullable } from '@ui/utilities';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
|
||||||
|
|
||||||
const StyledInvertedIconContainer = styled.div<{ backgroundColor: string }>`
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: ${({ backgroundColor }) => backgroundColor};
|
|
||||||
`;
|
|
||||||
|
|
||||||
export type AvatarChipsLeftComponentProps = {
|
|
||||||
name: string;
|
|
||||||
avatarUrl?: string;
|
|
||||||
avatarType?: Nullable<AvatarType>;
|
|
||||||
LeftIcon?: IconComponent;
|
|
||||||
LeftIconColor?: string;
|
|
||||||
isIconInverted?: boolean;
|
|
||||||
placeholderColorSeed?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AvatarChipsLeftComponent: React.FC<
|
|
||||||
AvatarChipsLeftComponentProps
|
|
||||||
> = ({
|
|
||||||
LeftIcon,
|
|
||||||
placeholderColorSeed,
|
|
||||||
avatarType,
|
|
||||||
avatarUrl,
|
|
||||||
name,
|
|
||||||
isIconInverted = false,
|
|
||||||
LeftIconColor,
|
|
||||||
}) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
if (!isDefined(LeftIcon)) {
|
|
||||||
return (
|
|
||||||
<Avatar
|
|
||||||
avatarUrl={avatarUrl}
|
|
||||||
placeholderColorSeed={placeholderColorSeed}
|
|
||||||
placeholder={name}
|
|
||||||
size="sm"
|
|
||||||
type={avatarType}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isIconInverted) {
|
|
||||||
return (
|
|
||||||
<StyledInvertedIconContainer
|
|
||||||
backgroundColor={theme.background.invertedSecondary}
|
|
||||||
>
|
|
||||||
<LeftIcon
|
|
||||||
color={theme.font.color.inverted}
|
|
||||||
size={theme.icon.size.sm}
|
|
||||||
stroke={theme.icon.stroke.sm}
|
|
||||||
/>
|
|
||||||
</StyledInvertedIconContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<LeftIcon
|
|
||||||
size={theme.icon.size.sm}
|
|
||||||
stroke={theme.icon.stroke.sm}
|
|
||||||
color={LeftIconColor || 'currentColor'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import { AvatarChipsLeftComponent } from '@ui/components/avatar-chip/AvatarChipLeftComponent';
|
|
||||||
import { AvatarChipsCommonProps } from '@ui/components/avatar-chip/types/AvatarChipsCommonProps.type';
|
|
||||||
import { AvatarChipVariant } from '@ui/components/avatar-chip/types/AvatarChipsVariant.type';
|
|
||||||
import { ChipVariant } from '@ui/components/chip/Chip';
|
|
||||||
import { LinkChip, LinkChipProps } from '@ui/components/chip/LinkChip';
|
|
||||||
import { TriggerEventType } from '@ui/utilities';
|
|
||||||
|
|
||||||
export type LinkAvatarChipProps = Omit<
|
|
||||||
AvatarChipsCommonProps,
|
|
||||||
'clickable' | 'variant'
|
|
||||||
> & {
|
|
||||||
to: string;
|
|
||||||
onClick?: LinkChipProps['onClick'];
|
|
||||||
onMouseDown?: LinkChipProps['onMouseDown'];
|
|
||||||
variant?: AvatarChipVariant;
|
|
||||||
isLabelHidden?: boolean;
|
|
||||||
triggerEvent?: TriggerEventType;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const LinkAvatarChip = ({
|
|
||||||
to,
|
|
||||||
onClick,
|
|
||||||
name,
|
|
||||||
LeftIcon,
|
|
||||||
LeftIconColor,
|
|
||||||
avatarType,
|
|
||||||
avatarUrl,
|
|
||||||
className,
|
|
||||||
isIconInverted,
|
|
||||||
maxWidth,
|
|
||||||
placeholderColorSeed,
|
|
||||||
size,
|
|
||||||
variant,
|
|
||||||
isLabelHidden,
|
|
||||||
triggerEvent,
|
|
||||||
}: LinkAvatarChipProps) => (
|
|
||||||
<LinkChip
|
|
||||||
to={to}
|
|
||||||
onClick={onClick}
|
|
||||||
label={name}
|
|
||||||
isLabelHidden={isLabelHidden}
|
|
||||||
variant={
|
|
||||||
//Regular but Highlighted -> missleading
|
|
||||||
variant === AvatarChipVariant.Regular
|
|
||||||
? ChipVariant.Highlighted
|
|
||||||
: ChipVariant.Regular
|
|
||||||
}
|
|
||||||
size={size}
|
|
||||||
leftComponent={
|
|
||||||
<AvatarChipsLeftComponent
|
|
||||||
name={name}
|
|
||||||
LeftIcon={LeftIcon}
|
|
||||||
LeftIconColor={LeftIconColor}
|
|
||||||
avatarType={avatarType}
|
|
||||||
avatarUrl={avatarUrl}
|
|
||||||
isIconInverted={isIconInverted}
|
|
||||||
placeholderColorSeed={placeholderColorSeed}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
className={className}
|
|
||||||
maxWidth={maxWidth}
|
|
||||||
triggerEvent={triggerEvent}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
@ -0,0 +1,61 @@
|
|||||||
|
import { Fragment } from 'react/jsx-runtime';
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
|
|
||||||
|
import { Chip, ChipVariant } from '@ui/components/chip/Chip';
|
||||||
|
|
||||||
|
const StyledIconsContainer = styled.div`
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const StyledChipContainer = styled.div`
|
||||||
|
display: inline-flex;
|
||||||
|
font-size: ${({ theme }) => theme.font.size.sm};
|
||||||
|
`;
|
||||||
|
|
||||||
|
export type MultipleAvatarChipProps = {
|
||||||
|
Icons: React.ReactNode[];
|
||||||
|
text?: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
testId?: string;
|
||||||
|
maxWidth?: number;
|
||||||
|
forceEmptyText?: boolean;
|
||||||
|
variant?: ChipVariant;
|
||||||
|
rightComponent?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MultipleAvatarChip = ({
|
||||||
|
Icons,
|
||||||
|
text,
|
||||||
|
onClick,
|
||||||
|
testId,
|
||||||
|
maxWidth,
|
||||||
|
rightComponent,
|
||||||
|
variant = ChipVariant.Static,
|
||||||
|
forceEmptyText = false,
|
||||||
|
}: MultipleAvatarChipProps) => {
|
||||||
|
const leftComponent = (
|
||||||
|
<StyledIconsContainer>
|
||||||
|
{Icons.map((Icon, index) => (
|
||||||
|
<Fragment key={index}>{Icon}</Fragment>
|
||||||
|
))}
|
||||||
|
</StyledIconsContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StyledChipContainer onClick={onClick} data-testid={testId}>
|
||||||
|
<Chip
|
||||||
|
label={text || ''}
|
||||||
|
forceEmptyText={forceEmptyText}
|
||||||
|
isLabelHidden={!isNonEmptyString(text) && forceEmptyText}
|
||||||
|
variant={variant}
|
||||||
|
leftComponent={leftComponent}
|
||||||
|
rightComponent={rightComponent}
|
||||||
|
clickable={isDefined(onClick)}
|
||||||
|
maxWidth={maxWidth}
|
||||||
|
/>
|
||||||
|
</StyledChipContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
|
import { ComponentDecorator } from '@ui/testing';
|
||||||
|
import { IconBuildingSkyscraper, IconUser } from '@ui/display';
|
||||||
|
import { AvatarChip } from '../AvatarChip';
|
||||||
|
|
||||||
|
const meta: Meta<typeof AvatarChip> = {
|
||||||
|
title: 'UI/Components/AvatarChip',
|
||||||
|
component: AvatarChip,
|
||||||
|
decorators: [ComponentDecorator],
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof AvatarChip>;
|
||||||
|
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
placeholder: 'JD',
|
||||||
|
placeholderColorSeed: 'John Doe',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithAvatar: Story = {
|
||||||
|
args: {
|
||||||
|
avatarUrl: 'https://i.pravatar.cc/300',
|
||||||
|
placeholder: 'JD',
|
||||||
|
placeholderColorSeed: 'John Doe',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithIcon: Story = {
|
||||||
|
args: {
|
||||||
|
Icon: IconUser,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithIconBackground: Story = {
|
||||||
|
args: {
|
||||||
|
Icon: IconBuildingSkyscraper,
|
||||||
|
isIconInverted: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithInvertedIcon: Story = {
|
||||||
|
args: {
|
||||||
|
Icon: IconUser,
|
||||||
|
isIconInverted: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithRightDivider: Story = {
|
||||||
|
args: {
|
||||||
|
placeholder: 'JD',
|
||||||
|
placeholderColorSeed: 'John Doe',
|
||||||
|
divider: 'right',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithLeftDivider: Story = {
|
||||||
|
args: {
|
||||||
|
Icon: IconUser,
|
||||||
|
divider: 'left',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Clickable: Story = {
|
||||||
|
args: {
|
||||||
|
placeholder: 'JD',
|
||||||
|
placeholderColorSeed: 'John Doe',
|
||||||
|
onClick: () => alert('AvatarChip clicked'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ClickableIcon: Story = {
|
||||||
|
args: {
|
||||||
|
Icon: IconBuildingSkyscraper,
|
||||||
|
isIconInverted: true,
|
||||||
|
onClick: () => alert('Icon AvatarChip clicked'),
|
||||||
|
},
|
||||||
|
};
|
||||||
@ -1,16 +1,16 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
import { Meta, StoryObj } from '@storybook/react';
|
||||||
import { CommandMenuContextChip } from '../CommandMenuContextChip';
|
import { ComponentDecorator } from '@ui/testing';
|
||||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
import { IconBuildingSkyscraper, IconUser } from '@ui/display';
|
||||||
import { IconBuildingSkyscraper, IconUser } from 'twenty-ui/display';
|
import { MultipleAvatarChip } from '@ui/components';
|
||||||
|
|
||||||
const meta: Meta<typeof CommandMenuContextChip> = {
|
const meta: Meta<typeof MultipleAvatarChip> = {
|
||||||
title: 'Modules/CommandMenu/CommandMenuContextChip',
|
title: 'UI/Components/MultipleAvatarChip',
|
||||||
component: CommandMenuContextChip,
|
component: MultipleAvatarChip,
|
||||||
decorators: [ComponentDecorator],
|
decorators: [ComponentDecorator],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default meta;
|
export default meta;
|
||||||
type Story = StoryObj<typeof CommandMenuContextChip>;
|
type Story = StoryObj<typeof MultipleAvatarChip>;
|
||||||
|
|
||||||
export const SingleIcon: Story = {
|
export const SingleIcon: Story = {
|
||||||
args: {
|
args: {
|
||||||
@ -1,9 +0,0 @@
|
|||||||
import { AvatarChipsLeftComponentProps } from '@ui/components/avatar-chip/AvatarChipLeftComponent';
|
|
||||||
import { ChipSize, ChipVariant } from '@ui/components/chip/Chip';
|
|
||||||
|
|
||||||
export type AvatarChipsCommonProps = {
|
|
||||||
size?: ChipSize;
|
|
||||||
className?: string;
|
|
||||||
maxWidth?: number;
|
|
||||||
variant?: ChipVariant;
|
|
||||||
} & AvatarChipsLeftComponentProps;
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
export enum AvatarChipVariant {
|
|
||||||
Regular = 'regular',
|
|
||||||
Transparent = 'transparent',
|
|
||||||
}
|
|
||||||
@ -32,8 +32,9 @@ export type ChipProps = {
|
|||||||
variant?: ChipVariant;
|
variant?: ChipVariant;
|
||||||
accent?: ChipAccent;
|
accent?: ChipAccent;
|
||||||
leftComponent?: ReactNode | null;
|
leftComponent?: ReactNode | null;
|
||||||
rightComponent?: (() => ReactNode) | null;
|
rightComponent?: (() => ReactNode) | ReactNode | null;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
forceEmptyText?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledDiv = withTheme(styled.div<{ theme: Theme }>`
|
const StyledDiv = withTheme(styled.div<{ theme: Theme }>`
|
||||||
@ -125,6 +126,18 @@ const StyledContainer = withTheme(styled.div<
|
|||||||
: 'var(--chip-horizontal-padding)'};
|
: 'var(--chip-horizontal-padding)'};
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const renderRightComponent = (
|
||||||
|
rightComponent: (() => ReactNode) | ReactNode | null,
|
||||||
|
) => {
|
||||||
|
if (!rightComponent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof rightComponent === 'function'
|
||||||
|
? rightComponent()
|
||||||
|
: rightComponent;
|
||||||
|
};
|
||||||
|
|
||||||
export const Chip = ({
|
export const Chip = ({
|
||||||
size = ChipSize.Small,
|
size = ChipSize.Small,
|
||||||
label,
|
label,
|
||||||
@ -137,6 +150,7 @@ export const Chip = ({
|
|||||||
accent = ChipAccent.TextPrimary,
|
accent = ChipAccent.TextPrimary,
|
||||||
className,
|
className,
|
||||||
maxWidth,
|
maxWidth,
|
||||||
|
forceEmptyText = false,
|
||||||
}: ChipProps) => {
|
}: ChipProps) => {
|
||||||
return (
|
return (
|
||||||
<StyledContainer
|
<StyledContainer
|
||||||
@ -152,10 +166,12 @@ export const Chip = ({
|
|||||||
{leftComponent}
|
{leftComponent}
|
||||||
{!isLabelHidden && label && label.trim() ? (
|
{!isLabelHidden && label && label.trim() ? (
|
||||||
<OverflowingTextWithTooltip size={size} text={label} />
|
<OverflowingTextWithTooltip size={size} text={label} />
|
||||||
) : (
|
) : !forceEmptyText ? (
|
||||||
<StyledDiv>Untitled</StyledDiv>
|
<StyledDiv>Untitled</StyledDiv>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
)}
|
)}
|
||||||
{rightComponent?.()}
|
{renderRightComponent(rightComponent)}
|
||||||
</StyledContainer>
|
</StyledContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import { Meta, StoryObj } from '@storybook/react';
|
|
||||||
import { AvatarChip } from '@ui/components/avatar-chip/AvatarChip';
|
|
||||||
|
|
||||||
import {
|
|
||||||
ComponentDecorator,
|
|
||||||
RecoilRootDecorator,
|
|
||||||
RouterDecorator,
|
|
||||||
} from '@ui/testing';
|
|
||||||
|
|
||||||
const meta: Meta<typeof AvatarChip> = {
|
|
||||||
title: 'UI/Display/Chip/AvatarChip',
|
|
||||||
component: AvatarChip,
|
|
||||||
decorators: [RouterDecorator, ComponentDecorator, RecoilRootDecorator],
|
|
||||||
args: {
|
|
||||||
name: 'Entity name',
|
|
||||||
avatarType: 'squared',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default meta;
|
|
||||||
type Story = StoryObj<typeof AvatarChip>;
|
|
||||||
|
|
||||||
export const Default: Story = {};
|
|
||||||
|
|
||||||
export const Empty: Story = {
|
|
||||||
args: {
|
|
||||||
name: '',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -9,12 +9,8 @@
|
|||||||
|
|
||||||
export type { AvatarChipProps } from './avatar-chip/AvatarChip';
|
export type { AvatarChipProps } from './avatar-chip/AvatarChip';
|
||||||
export { AvatarChip } from './avatar-chip/AvatarChip';
|
export { AvatarChip } from './avatar-chip/AvatarChip';
|
||||||
export type { AvatarChipsLeftComponentProps } from './avatar-chip/AvatarChipLeftComponent';
|
export type { MultipleAvatarChipProps } from './avatar-chip/MultipleAvatarChip';
|
||||||
export { AvatarChipsLeftComponent } from './avatar-chip/AvatarChipLeftComponent';
|
export { MultipleAvatarChip } from './avatar-chip/MultipleAvatarChip';
|
||||||
export type { LinkAvatarChipProps } from './avatar-chip/LinkAvatarChip';
|
|
||||||
export { LinkAvatarChip } from './avatar-chip/LinkAvatarChip';
|
|
||||||
export type { AvatarChipsCommonProps } from './avatar-chip/types/AvatarChipsCommonProps.type';
|
|
||||||
export { AvatarChipVariant } from './avatar-chip/types/AvatarChipsVariant.type';
|
|
||||||
export type { ChipProps } from './chip/Chip';
|
export type { ChipProps } from './chip/Chip';
|
||||||
export { ChipSize, ChipAccent, ChipVariant, Chip } from './chip/Chip';
|
export { ChipSize, ChipAccent, ChipVariant, Chip } from './chip/Chip';
|
||||||
export { LINK_CHIP_CLICK_OUTSIDE_ID } from './chip/constants/LinkChipClickOutsideId';
|
export { LINK_CHIP_CLICK_OUTSIDE_ID } from './chip/constants/LinkChipClickOutsideId';
|
||||||
|
|||||||
Reference in New Issue
Block a user