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;
|
||||
};
|
||||
|
||||
export type AssignRoleToAgentMutationVariables = Exact<{
|
||||
agentId: Scalars['UUID'];
|
||||
roleId: Scalars['UUID'];
|
||||
}>;
|
||||
|
||||
|
||||
export type AssignRoleToAgentMutation = { __typename?: 'Mutation', assignRoleToAgent: boolean };
|
||||
|
||||
export type CreateAgentChatThreadMutationVariables = Exact<{
|
||||
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 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<{
|
||||
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 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<{
|
||||
input: SubmitFormStepInput;
|
||||
}>;
|
||||
@ -4169,6 +4169,38 @@ ${ObjectPermissionFragmentFragmentDoc}
|
||||
${WorkspaceUrlsFragmentFragmentDoc}
|
||||
${RoleFragmentFragmentDoc}
|
||||
${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`
|
||||
mutation CreateAgentChatThread($input: CreateAgentChatThreadInput!) {
|
||||
createAgentChatThread(input: $input) {
|
||||
@ -4206,6 +4238,116 @@ export function useCreateAgentChatThreadMutation(baseOptions?: Apollo.MutationHo
|
||||
export type CreateAgentChatThreadMutationHookResult = ReturnType<typeof useCreateAgentChatThreadMutation>;
|
||||
export type CreateAgentChatThreadMutationResult = Apollo.MutationResult<CreateAgentChatThreadMutation>;
|
||||
export type CreateAgentChatThreadMutationOptions = Apollo.BaseMutationOptions<CreateAgentChatThreadMutation, CreateAgentChatThreadMutationVariables>;
|
||||
export const 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`
|
||||
query GetAgentChatMessages($threadId: String!) {
|
||||
agentChatMessages(threadId: $threadId) {
|
||||
@ -8463,148 +8605,6 @@ export function useUpdateWorkflowVersionStepMutation(baseOptions?: Apollo.Mutati
|
||||
export type UpdateWorkflowVersionStepMutationHookResult = ReturnType<typeof useUpdateWorkflowVersionStepMutation>;
|
||||
export type UpdateWorkflowVersionStepMutationResult = Apollo.MutationResult<UpdateWorkflowVersionStepMutation>;
|
||||
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`
|
||||
mutation SubmitFormStep($input: SubmitFormStepInput!) {
|
||||
submitFormStep(input: $input)
|
||||
|
||||
@ -3,8 +3,8 @@ import styled from '@emotion/styled';
|
||||
import { Avatar, IconDotsVertical, IconSparkles } from 'twenty-ui/display';
|
||||
|
||||
import { LightCopyIconButton } from '@/object-record/record-field/components/LightCopyIconButton';
|
||||
import { AgentChatFilePreview } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/AgentChatFilePreview';
|
||||
import { AgentChatMessageRole } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/agent-chat-message-role';
|
||||
import { AgentChatFilePreview } from '@/ai/components/internal/AgentChatFilePreview';
|
||||
import { AgentChatMessageRole } from '@/ai/constants/agent-chat-message-role';
|
||||
|
||||
import { AgentChatMessage } from '~/generated/graphql';
|
||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||
|
||||
@ -7,7 +7,7 @@ import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
|
||||
import { useCommandMenu } from '@/command-menu/hooks/useCommandMenu';
|
||||
import { CommandMenuPages } from '@/command-menu/types/CommandMenuPages';
|
||||
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 { AIChatMessage } from '@/ai/components/AIChatMessage';
|
||||
@ -16,8 +16,12 @@ import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { useAgentChat } from '../hooks/useAgentChat';
|
||||
import { AIChatSkeletonLoader } from './AIChatSkeletonLoader';
|
||||
import { AgentChatSelectedFilesPreview } from './AgentChatSelectedFilesPreview';
|
||||
import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoader';
|
||||
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 }>`
|
||||
background: ${({ theme }) => theme.background.primary};
|
||||
@ -63,10 +67,13 @@ export const AIChatTab = ({
|
||||
}) => {
|
||||
const [isDraggingFile, setIsDraggingFile] = useState(false);
|
||||
|
||||
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
|
||||
const {
|
||||
messages,
|
||||
isLoading,
|
||||
handleSendMessage,
|
||||
input,
|
||||
handleInputChange,
|
||||
agentStreamingMessage,
|
||||
@ -105,7 +112,7 @@ export const AIChatTab = ({
|
||||
{isLoading && messages.length === 0 && <AIChatSkeletonLoader />}
|
||||
|
||||
<StyledInputArea>
|
||||
<AgentChatSelectedFilesPreview agentId={agentId} />
|
||||
<AgentChatContextPreview agentId={agentId} />
|
||||
<TextArea
|
||||
textAreaId={`${agentId}-chat-input`}
|
||||
placeholder={t`Enter a question...`}
|
||||
@ -135,16 +142,12 @@ export const AIChatTab = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<AgentChatFileUpload agentId={agentId} />
|
||||
<Button
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
hotkeys={input && !isLoading ? ['⏎'] : undefined}
|
||||
disabled={!input || isLoading}
|
||||
title={t`Send`}
|
||||
onClick={handleSendMessage}
|
||||
/>
|
||||
<AgentChatFileUploadButton agentId={agentId} />
|
||||
{contextStoreCurrentObjectMetadataItemId ? (
|
||||
<SendMessageWithRecordsContextButton agentId={agentId} />
|
||||
) : (
|
||||
<SendMessageButton agentId={agentId} />
|
||||
)}
|
||||
</StyledButtonsContainer>
|
||||
</StyledInputArea>
|
||||
</>
|
||||
@ -5,7 +5,7 @@ import { AIChatThreadsListEffect } from '@/ai/components/AIChatThreadsListEffect
|
||||
import { useCreateNewAIChatThread } from '@/ai/hooks/useCreateNewAIChatThread';
|
||||
import { groupThreadsByDate } from '@/ai/utils/groupThreadsByDate';
|
||||
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 { capitalize } from 'twenty-shared/utils';
|
||||
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 { 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 styled from '@emotion/styled';
|
||||
import React, { useRef } from 'react';
|
||||
import { IconPaperclip } from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { useSetRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useSetRecoilComponentStateV2';
|
||||
|
||||
const StyledFileUploadContainer = styled.div`
|
||||
display: flex;
|
||||
@ -16,8 +16,8 @@ const StyledFileInput = styled.input`
|
||||
display: none;
|
||||
`;
|
||||
|
||||
export const AgentChatFileUpload = ({ agentId }: { agentId: string }) => {
|
||||
const [, setAgentChatSelectedFiles] = useRecoilComponentStateV2(
|
||||
export const AgentChatFileUploadButton = ({ agentId }: { agentId: string }) => {
|
||||
const setAgentChatSelectedFiles = useSetRecoilComponentStateV2(
|
||||
agentChatSelectedFilesComponentState,
|
||||
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 { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useRecoilComponentStateV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentStateV2';
|
||||
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 { agentChatSelectedFilesComponentState } from '@/ai/states/agentChatSelectedFilesComponentState';
|
||||
import { agentChatUploadedFilesComponentState } from '@/ai/states/agentChatUploadedFilesComponentState';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
|
||||
@ -8,10 +8,10 @@ import { currentAIChatThreadComponentState } from '@/ai/states/currentAIChatThre
|
||||
import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement';
|
||||
import { useScrollWrapperElement } from '@/ui/utilities/scroll/hooks/useScrollWrapperElement';
|
||||
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 { AgentChatMessageRole } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/constants/agent-chat-message-role';
|
||||
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 { STREAM_CHAT_QUERY } from '@/ai/rest-api/agent-chat-apollo.api';
|
||||
import { AgentChatMessageRole } from '@/ai/constants/agent-chat-message-role';
|
||||
import { agentChatSelectedFilesComponentState } from '@/ai/states/agentChatSelectedFilesComponentState';
|
||||
import { agentChatUploadedFilesComponentState } from '@/ai/states/agentChatUploadedFilesComponentState';
|
||||
import { useApolloClient } from '@apollo/client';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
@ -24,20 +24,43 @@ import { agentChatInputState } from '../states/agentChatInputState';
|
||||
import { agentChatMessagesComponentState } from '../states/agentChatMessagesComponentState';
|
||||
import { agentStreamingMessageState } from '../states/agentStreamingMessageState';
|
||||
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 & {
|
||||
isPending: boolean;
|
||||
};
|
||||
|
||||
export const useAgentChat = (agentId: string) => {
|
||||
export const useAgentChat = (agentId: string, records?: ObjectRecord[]) => {
|
||||
const apolloClient = useApolloClient();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
const { getObjectMetadataItemById } = useGetObjectMetadataItemById();
|
||||
|
||||
const contextStoreCurrentObjectMetadataItemId = useRecoilComponentValueV2(
|
||||
contextStoreCurrentObjectMetadataItemIdComponentState,
|
||||
);
|
||||
|
||||
const isAgentChatCurrentContextActive = useRecoilComponentValueV2(
|
||||
isAgentChatCurrentContextActiveState,
|
||||
agentId,
|
||||
);
|
||||
|
||||
const agentChatSelectedFiles = useRecoilComponentValueV2(
|
||||
agentChatSelectedFilesComponentState,
|
||||
agentId,
|
||||
);
|
||||
|
||||
const [agentChatContext, setAgentChatContext] = useRecoilComponentStateV2(
|
||||
agentChatObjectMetadataAndRecordContextState,
|
||||
agentId,
|
||||
);
|
||||
|
||||
const [currentThreadId, setCurrentThreadId] = useRecoilComponentStateV2(
|
||||
currentAIChatThreadComponentState,
|
||||
agentId,
|
||||
@ -128,6 +151,21 @@ export const useAgentChat = (agentId: string) => {
|
||||
|
||||
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({
|
||||
query: STREAM_CHAT_QUERY,
|
||||
variables: {
|
||||
@ -135,6 +173,8 @@ export const useAgentChat = (agentId: string) => {
|
||||
threadId: currentThreadId,
|
||||
userMessage: content,
|
||||
fileIds: agentChatUploadedFiles.map((file) => file.id),
|
||||
recordIdsByObjectMetadataNameSingular:
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
},
|
||||
},
|
||||
context: {
|
||||
@ -200,6 +240,12 @@ export const useAgentChat = (agentId: string) => {
|
||||
await sendChatMessage(content);
|
||||
};
|
||||
|
||||
const handleSetContext = async (
|
||||
items: Array<AIChatObjectMetadataAndRecordContext>,
|
||||
) => {
|
||||
setAgentChatContext(items);
|
||||
};
|
||||
|
||||
useHotkeysOnFocusedElement({
|
||||
keys: [Key.Enter],
|
||||
callback: (event: KeyboardEvent) => {
|
||||
@ -219,6 +265,8 @@ export const useAgentChat = (agentId: string) => {
|
||||
handleInputChange: (value: string) => setAgentChatInput(value),
|
||||
messages: agentChatMessages,
|
||||
input: agentChatInput,
|
||||
context: agentChatContext,
|
||||
handleSetContext,
|
||||
handleSendMessage,
|
||||
isLoading,
|
||||
agentStreamingMessage,
|
||||
@ -1,5 +1,5 @@
|
||||
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 { useState } from 'react';
|
||||
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 { AgentChatMessagesComponentInstanceContext } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/states/agentChatMessagesComponentState';
|
||||
import { AgentChatMessagesComponentInstanceContext } from '@/ai/states/agentChatMessagesComponentState';
|
||||
|
||||
export const agentChatSelectedFilesComponentState = createComponentStateV2<
|
||||
File[]
|
||||
@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
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 { MenuItem } from 'twenty-ui/navigation';
|
||||
import {
|
||||
CommandMenuContextChip,
|
||||
CommandMenuContextChipProps,
|
||||
} from './CommandMenuContextChip';
|
||||
MultipleAvatarChip,
|
||||
MultipleAvatarChipProps,
|
||||
} from 'twenty-ui/components';
|
||||
|
||||
export const CommandMenuContextChipGroups = ({
|
||||
contextChips,
|
||||
}: {
|
||||
contextChips: CommandMenuContextChipProps[];
|
||||
contextChips: MultipleAvatarChipProps[];
|
||||
}) => {
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
@ -25,9 +25,9 @@ export const CommandMenuContextChipGroups = ({
|
||||
return (
|
||||
<>
|
||||
{contextChips.map((chip, index) => (
|
||||
<CommandMenuContextChip
|
||||
<MultipleAvatarChip
|
||||
key={index}
|
||||
maxWidth={'180px'}
|
||||
maxWidth={180}
|
||||
Icons={chip.Icons}
|
||||
text={chip.text}
|
||||
onClick={chip.onClick}
|
||||
@ -46,7 +46,7 @@ export const CommandMenuContextChipGroups = ({
|
||||
{firstChips.length > 0 && (
|
||||
<Dropdown
|
||||
clickableComponent={
|
||||
<CommandMenuContextChip
|
||||
<MultipleAvatarChip
|
||||
Icons={firstThreeChips.map((chip) => chip.Icons?.[0])}
|
||||
onClick={() => {}}
|
||||
text={`${firstChips.length}`}
|
||||
@ -77,11 +77,11 @@ export const CommandMenuContextChipGroups = ({
|
||||
)}
|
||||
|
||||
{isDefined(lastChip) && (
|
||||
<CommandMenuContextChip
|
||||
<MultipleAvatarChip
|
||||
Icons={lastChip.Icons}
|
||||
text={lastChip.text}
|
||||
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 { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { CommandMenuContextChipProps } from './CommandMenuContextChip';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { MultipleAvatarChipProps } from 'twenty-ui/components';
|
||||
|
||||
export const CommandMenuContextChipGroupsWithRecordSelection = ({
|
||||
contextChips,
|
||||
objectMetadataItemId,
|
||||
}: {
|
||||
contextChips: CommandMenuContextChipProps[];
|
||||
contextChips: MultipleAvatarChipProps[];
|
||||
objectMetadataItemId: string;
|
||||
}) => {
|
||||
const { objectMetadataItem } = useObjectMetadataItemById({
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { CommandMenuContextChip } from '@/command-menu/components/CommandMenuContextChip';
|
||||
import { CommandMenuContextRecordChipAvatars } from '@/command-menu/components/CommandMenuContextRecordChipAvatars';
|
||||
import { getSelectedRecordsContextText } from '@/command-menu/utils/getRecordContextText';
|
||||
import { useFindManyRecordsSelectedInContextStore } from '@/context-store/hooks/useFindManyRecordsSelectedInContextStore';
|
||||
import { useObjectMetadataItemById } from '@/object-metadata/hooks/useObjectMetadataItemById';
|
||||
import { MultipleAvatarChip } from 'twenty-ui/components';
|
||||
|
||||
export const CommandMenuContextRecordsChip = ({
|
||||
objectMetadataItemId,
|
||||
@ -34,7 +34,7 @@ export const CommandMenuContextRecordsChip = ({
|
||||
));
|
||||
|
||||
return (
|
||||
<CommandMenuContextChip
|
||||
<MultipleAvatarChip
|
||||
text={getSelectedRecordsContextText(
|
||||
objectMetadataItem,
|
||||
records,
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import { CommandMenuContextChip } from '@/command-menu/components/CommandMenuContextChip';
|
||||
import { CommandMenuContextChipGroups } from '@/command-menu/components/CommandMenuContextChipGroups';
|
||||
import { CommandMenuContextChipGroupsWithRecordSelection } from '@/command-menu/components/CommandMenuContextChipGroupsWithRecordSelection';
|
||||
import { CommandMenuTopBarInputFocusEffect } from '@/command-menu/components/CommandMenuTopBarInputFocusEffect';
|
||||
@ -22,6 +21,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconChevronLeft, IconX } from 'twenty-ui/display';
|
||||
import { MultipleAvatarChip } from 'twenty-ui/components';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { getOsControlSymbol, useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
@ -122,7 +122,7 @@ export const CommandMenuTopBar = () => {
|
||||
duration: backButtonAnimationDuration,
|
||||
}}
|
||||
>
|
||||
<CommandMenuContextChip
|
||||
<MultipleAvatarChip
|
||||
Icons={[<IconChevronLeft size={theme.icon.size.sm} />]}
|
||||
onClick={goBackFromCommandMenu}
|
||||
testId="command-menu-go-back-button"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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 { useRecoilValue } from 'recoil';
|
||||
|
||||
|
||||
@ -1,19 +1,8 @@
|
||||
import { AttachmentType } from '@/activities/files/types/Attachment';
|
||||
import { IconMapping, useFileTypeColors } from '@/file/utils/fileIconMappings';
|
||||
import { useTheme } from '@emotion/react';
|
||||
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 }>`
|
||||
align-items: center;
|
||||
background: ${({ background }) => background};
|
||||
@ -25,35 +14,14 @@ const StyledIconContainer = styled.div<{ background: string }>`
|
||||
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 }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
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 iconColors = useFileTypeColors();
|
||||
|
||||
const Icon = IconMapping[fileType];
|
||||
|
||||
return (
|
||||
<StyledIconContainer background={IconColors[fileType]}>
|
||||
<StyledIconContainer background={iconColors[fileType]}>
|
||||
{Icon && <Icon size={theme.icon.size.sm} />}
|
||||
</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 { isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
Chip,
|
||||
AvatarChip,
|
||||
AvatarChipVariant,
|
||||
ChipSize,
|
||||
ChipVariant,
|
||||
LinkAvatarChip,
|
||||
LinkChip,
|
||||
} from 'twenty-ui/components';
|
||||
import { TriggerEventType } from 'twenty-ui/utilities';
|
||||
|
||||
@ -22,7 +22,7 @@ export type RecordChipProps = {
|
||||
objectNameSingular: string;
|
||||
record: ObjectRecord;
|
||||
className?: string;
|
||||
variant?: AvatarChipVariant;
|
||||
variant?: ChipVariant.Highlighted | ChipVariant.Transparent;
|
||||
forceDisableClick?: boolean;
|
||||
maxWidth?: number;
|
||||
to?: string | undefined;
|
||||
@ -72,39 +72,42 @@ export const RecordChip = ({
|
||||
|
||||
// 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 (
|
||||
forceDisableClick ||
|
||||
objectNameSingular === CoreObjectNameSingular.WorkspaceMember
|
||||
) {
|
||||
return (
|
||||
<AvatarChip
|
||||
<Chip
|
||||
label={recordChipData.name}
|
||||
size={size}
|
||||
maxWidth={maxWidth}
|
||||
placeholderColorSeed={record.id}
|
||||
name={recordChipData.name}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
className={className}
|
||||
variant={ChipVariant.Transparent}
|
||||
leftComponent={avatarChip}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LinkAvatarChip
|
||||
<LinkChip
|
||||
size={size}
|
||||
maxWidth={maxWidth}
|
||||
placeholderColorSeed={record.id}
|
||||
name={recordChipData.name}
|
||||
label={recordChipData.name}
|
||||
isLabelHidden={isLabelHidden}
|
||||
avatarType={recordChipData.avatarType}
|
||||
avatarUrl={recordChipData.avatarUrl ?? ''}
|
||||
leftComponent={avatarChip}
|
||||
className={className}
|
||||
variant={
|
||||
variant ??
|
||||
(!forceDisableClick
|
||||
? AvatarChipVariant.Regular
|
||||
: AvatarChipVariant.Transparent)
|
||||
(!forceDisableClick ? ChipVariant.Highlighted : ChipVariant.Transparent)
|
||||
}
|
||||
to={to ?? getLinkToShowPage(objectNameSingular, record)}
|
||||
onClick={handleCustomClick}
|
||||
|
||||
@ -19,9 +19,9 @@ import styled from '@emotion/styled';
|
||||
import { Dispatch, SetStateAction, useContext } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { AvatarChipVariant } from 'twenty-ui/components';
|
||||
import { IconEye, IconEyeOff } from 'twenty-ui/display';
|
||||
import { Checkbox, CheckboxVariant, LightIconButton } from 'twenty-ui/input';
|
||||
import { ChipVariant } from 'twenty-ui/components';
|
||||
|
||||
const StyledCompactIconContainer = styled.div`
|
||||
align-items: center;
|
||||
@ -80,7 +80,7 @@ export const RecordBoardCardHeader = ({
|
||||
<RecordChip
|
||||
objectNameSingular={objectMetadataItem.nameSingular}
|
||||
record={record}
|
||||
variant={AvatarChipVariant.Transparent}
|
||||
variant={ChipVariant.Transparent}
|
||||
maxWidth={150}
|
||||
onClick={() => {
|
||||
activateBoardCard({ rowIndex, columnIndex });
|
||||
|
||||
@ -20,7 +20,7 @@ import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { getImageAbsoluteURI, isDefined } from 'twenty-shared/utils';
|
||||
import { AvatarChip } from 'twenty-ui/components';
|
||||
import { Chip, AvatarChip } from 'twenty-ui/components';
|
||||
import {
|
||||
H2Title,
|
||||
IconEyeShare,
|
||||
@ -137,15 +137,19 @@ export const SettingsAdminWorkspaceContent = ({
|
||||
Icon: IconHome,
|
||||
label: t`Name`,
|
||||
value: (
|
||||
<AvatarChip
|
||||
name={activeWorkspace?.name ?? ''}
|
||||
avatarUrl={
|
||||
getImageAbsoluteURI({
|
||||
imageUrl: isNonEmptyString(activeWorkspace?.logo)
|
||||
? activeWorkspace?.logo
|
||||
: DEFAULT_WORKSPACE_LOGO,
|
||||
baseUrl: REACT_APP_SERVER_BASE_URL,
|
||||
}) ?? ''
|
||||
<Chip
|
||||
label={activeWorkspace?.name ?? ''}
|
||||
leftComponent={
|
||||
<AvatarChip
|
||||
avatarUrl={
|
||||
getImageAbsoluteURI({
|
||||
imageUrl: isNonEmptyString(activeWorkspace?.logo)
|
||||
? 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 { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { AvatarChip } from 'twenty-ui/components';
|
||||
import { Chip, AvatarChip } from 'twenty-ui/components';
|
||||
import {
|
||||
IconApi,
|
||||
IconCalendar,
|
||||
@ -71,13 +71,18 @@ export const ActorDisplay = ({
|
||||
source === 'API' || source === 'IMPORT' || source === 'SYSTEM';
|
||||
|
||||
return (
|
||||
<AvatarChip
|
||||
placeholderColorSeed={workspaceMemberId ?? undefined}
|
||||
name={name ?? ''}
|
||||
avatarType={workspaceMemberId ? 'rounded' : 'squared'}
|
||||
LeftIcon={LeftIcon}
|
||||
avatarUrl={avatarUrl ?? undefined}
|
||||
isIconInverted={isIconInverted}
|
||||
<Chip
|
||||
label={name ?? ''}
|
||||
leftComponent={
|
||||
<AvatarChip
|
||||
placeholderColorSeed={workspaceMemberId ?? undefined}
|
||||
avatarType={workspaceMemberId ? 'rounded' : 'squared'}
|
||||
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 { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
|
||||
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 { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
|
||||
@ -23,10 +23,10 @@ import {
|
||||
WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID,
|
||||
WorkflowAiAgentTabId,
|
||||
} from '../constants/workflow-ai-agent-tabs';
|
||||
import { useAgentRoleAssignment } from '../hooks/useAgentRoleAssignment';
|
||||
import { useAgentUpdateFormState } from '../hooks/useAgentUpdateFormState';
|
||||
import { useAiAgentOutputSchema } from '../hooks/useAiAgentOutputSchema';
|
||||
import { useAiModelOptions } from '../hooks/useAiModelOptions';
|
||||
import { useAgentRoleAssignment } from '@/ai/hooks/useAgentRoleAssignment';
|
||||
import { useAgentUpdateFormState } from '@/ai/hooks/useAgentUpdateFormState';
|
||||
import { useAiAgentOutputSchema } from '@/ai/hooks/useAiAgentOutputSchema';
|
||||
import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions';
|
||||
import { WorkflowOutputSchemaBuilder } from './WorkflowOutputSchemaBuilder';
|
||||
|
||||
const StyledErrorMessage = styled.div`
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { InputSchemaPropertyType } from '@/workflow/types/InputSchema';
|
||||
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 = {
|
||||
value?: InputSchemaPropertyType;
|
||||
|
||||
@ -3,7 +3,7 @@ import { FormTextFieldInput } from '@/object-record/record-field/form-types/comp
|
||||
|
||||
import { InputLabel } from '@/ui/input/components/InputLabel';
|
||||
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 styled from '@emotion/styled';
|
||||
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 { JwtAuthGuard } from 'src/engine/guards/jwt-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 { AgentStreamingService } from './agent-streaming.service';
|
||||
@ -58,8 +61,14 @@ export class AgentChatController {
|
||||
@Post('stream')
|
||||
async streamAgentChat(
|
||||
@Body()
|
||||
body: { threadId: string; userMessage: string; fileIds?: string[] },
|
||||
body: {
|
||||
threadId: string;
|
||||
userMessage: string;
|
||||
fileIds?: string[];
|
||||
recordIdsByObjectMetadataNameSingular?: RecordIdsByObjectMetadataNameSingularType;
|
||||
},
|
||||
@AuthUserWorkspaceId() userWorkspaceId: string,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
@Res() res: Response,
|
||||
) {
|
||||
try {
|
||||
@ -67,7 +76,10 @@ export class AgentChatController {
|
||||
threadId: body.threadId,
|
||||
userMessage: body.userMessage,
|
||||
userWorkspaceId,
|
||||
workspace,
|
||||
fileIds: body.fileIds || [],
|
||||
recordIdsByObjectMetadataNameSingular:
|
||||
body.recordIdsByObjectMetadataNameSingular || [],
|
||||
res,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
@ -13,7 +13,7 @@ import {
|
||||
generateText,
|
||||
ImagePart,
|
||||
streamText,
|
||||
TextPart,
|
||||
UserContent,
|
||||
} from 'ai';
|
||||
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 { resolveInput } from 'src/modules/workflow/workflow-executor/utils/variable-resolver.util';
|
||||
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 { AgentEntity } from './agent.entity';
|
||||
|
||||
export interface AgentExecutionResult {
|
||||
result: {
|
||||
@ -61,6 +65,8 @@ export class AgentExecutionService {
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly agentToolService: AgentToolService,
|
||||
private readonly fileService: FileService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
private readonly aiModelRegistryService: AiModelRegistryService,
|
||||
@InjectRepository(AgentEntity, 'core')
|
||||
private readonly agentRepository: Repository<AgentEntity>,
|
||||
@ -184,34 +190,88 @@ export class AgentExecutionService {
|
||||
}
|
||||
|
||||
private async buildUserMessageWithFiles(
|
||||
userMessage: string,
|
||||
fileIds?: string[],
|
||||
): Promise<CoreUserMessage> {
|
||||
if (!fileIds || fileIds.length === 0) {
|
||||
return { role: AgentChatMessageRole.USER, content: userMessage };
|
||||
}
|
||||
|
||||
fileIds: string[],
|
||||
): Promise<(ImagePart | FilePart)[]> {
|
||||
const files = await this.fileRepository.find({
|
||||
where: {
|
||||
id: In(fileIds),
|
||||
},
|
||||
});
|
||||
|
||||
const textPart: TextPart = {
|
||||
type: 'text',
|
||||
text: userMessage,
|
||||
};
|
||||
return await Promise.all(files.map((file) => this.createFilePart(file)));
|
||||
}
|
||||
|
||||
const fileParts = await Promise.all(
|
||||
files.map((file) => this.createFilePart(file)),
|
||||
);
|
||||
private async buildUserMessage(
|
||||
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 {
|
||||
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(
|
||||
file: FileEntity,
|
||||
): Promise<ImagePart | FilePart> {
|
||||
@ -241,15 +301,21 @@ export class AgentExecutionService {
|
||||
}
|
||||
|
||||
async streamChatResponse({
|
||||
workspace,
|
||||
userWorkspaceId,
|
||||
agentId,
|
||||
userMessage,
|
||||
messages,
|
||||
fileIds,
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
userWorkspaceId: string;
|
||||
agentId: string;
|
||||
userMessage: string;
|
||||
messages: AgentChatMessageEntity[];
|
||||
fileIds?: string[];
|
||||
fileIds: string[];
|
||||
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType;
|
||||
}) {
|
||||
const agent = await this.agentRepository.findOneOrFail({
|
||||
where: { id: agentId },
|
||||
@ -260,7 +326,19 @@ export class AgentExecutionService {
|
||||
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,
|
||||
fileIds,
|
||||
);
|
||||
@ -268,7 +346,7 @@ export class AgentExecutionService {
|
||||
llmMessages.push(userMessageWithFiles);
|
||||
|
||||
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,
|
||||
messages: llmMessages,
|
||||
});
|
||||
|
||||
@ -12,21 +12,19 @@ import {
|
||||
AgentException,
|
||||
AgentExceptionCode,
|
||||
} 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 = {
|
||||
threadId: string;
|
||||
userMessage: string;
|
||||
userWorkspaceId: string;
|
||||
fileIds?: string[];
|
||||
workspace: Workspace;
|
||||
fileIds: string[];
|
||||
recordIdsByObjectMetadataNameSingular: RecordIdsByObjectMetadataNameSingularType;
|
||||
res: Response;
|
||||
};
|
||||
|
||||
export type StreamAgentChatResult = {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
aiResponse?: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class AgentStreamingService {
|
||||
private readonly logger = new Logger(AgentStreamingService.name);
|
||||
@ -42,7 +40,9 @@ export class AgentStreamingService {
|
||||
threadId,
|
||||
userMessage,
|
||||
userWorkspaceId,
|
||||
fileIds = [],
|
||||
workspace,
|
||||
fileIds,
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
res,
|
||||
}: StreamAgentChatOptions) {
|
||||
try {
|
||||
@ -65,10 +65,13 @@ export class AgentStreamingService {
|
||||
|
||||
const { fullStream } =
|
||||
await this.agentExecutionService.streamChatResponse({
|
||||
workspace,
|
||||
agentId: thread.agent.id,
|
||||
userWorkspaceId,
|
||||
userMessage,
|
||||
messages: thread.messages,
|
||||
fileIds,
|
||||
recordIdsByObjectMetadataNameSingular,
|
||||
});
|
||||
|
||||
let aiResponse = '';
|
||||
|
||||
@ -12,4 +12,5 @@ export enum AgentExceptionCode {
|
||||
AGENT_EXECUTION_FAILED = 'AGENT_EXECUTION_FAILED',
|
||||
API_KEY_NOT_CONFIGURED = 'API_KEY_NOT_CONFIGURED',
|
||||
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 { AvatarChipsCommonProps } from '@ui/components/avatar-chip/types/AvatarChipsCommonProps.type';
|
||||
import { Chip, ChipVariant } from '@ui/components/chip/Chip';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
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 = ({
|
||||
name,
|
||||
LeftIcon,
|
||||
LeftIconColor,
|
||||
Icon,
|
||||
placeholderColorSeed,
|
||||
avatarType,
|
||||
avatarUrl,
|
||||
className,
|
||||
isIconInverted,
|
||||
maxWidth,
|
||||
placeholderColorSeed,
|
||||
size,
|
||||
variant = ChipVariant.Transparent,
|
||||
}: AvatarChipProps) => (
|
||||
<Chip
|
||||
label={name}
|
||||
variant={variant}
|
||||
size={size}
|
||||
leftComponent={
|
||||
<AvatarChipsLeftComponent
|
||||
name={name}
|
||||
LeftIcon={LeftIcon}
|
||||
LeftIconColor={LeftIconColor}
|
||||
avatarType={avatarType}
|
||||
placeholder,
|
||||
isIconInverted = false,
|
||||
IconColor,
|
||||
IconBackgroundColor,
|
||||
onClick,
|
||||
divider,
|
||||
}: AvatarChipProps) => {
|
||||
const theme = useTheme();
|
||||
if (!isDefined(Icon)) {
|
||||
return (
|
||||
<Avatar
|
||||
avatarUrl={avatarUrl}
|
||||
isIconInverted={isIconInverted}
|
||||
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 { CommandMenuContextChip } from '../CommandMenuContextChip';
|
||||
import { ComponentDecorator } from 'twenty-ui/testing';
|
||||
import { IconBuildingSkyscraper, IconUser } from 'twenty-ui/display';
|
||||
import { ComponentDecorator } from '@ui/testing';
|
||||
import { IconBuildingSkyscraper, IconUser } from '@ui/display';
|
||||
import { MultipleAvatarChip } from '@ui/components';
|
||||
|
||||
const meta: Meta<typeof CommandMenuContextChip> = {
|
||||
title: 'Modules/CommandMenu/CommandMenuContextChip',
|
||||
component: CommandMenuContextChip,
|
||||
const meta: Meta<typeof MultipleAvatarChip> = {
|
||||
title: 'UI/Components/MultipleAvatarChip',
|
||||
component: MultipleAvatarChip,
|
||||
decorators: [ComponentDecorator],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CommandMenuContextChip>;
|
||||
type Story = StoryObj<typeof MultipleAvatarChip>;
|
||||
|
||||
export const SingleIcon: Story = {
|
||||
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;
|
||||
accent?: ChipAccent;
|
||||
leftComponent?: ReactNode | null;
|
||||
rightComponent?: (() => ReactNode) | null;
|
||||
rightComponent?: (() => ReactNode) | ReactNode | null;
|
||||
className?: string;
|
||||
forceEmptyText?: boolean;
|
||||
};
|
||||
|
||||
const StyledDiv = withTheme(styled.div<{ theme: Theme }>`
|
||||
@ -125,6 +126,18 @@ const StyledContainer = withTheme(styled.div<
|
||||
: 'var(--chip-horizontal-padding)'};
|
||||
`);
|
||||
|
||||
const renderRightComponent = (
|
||||
rightComponent: (() => ReactNode) | ReactNode | null,
|
||||
) => {
|
||||
if (!rightComponent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return typeof rightComponent === 'function'
|
||||
? rightComponent()
|
||||
: rightComponent;
|
||||
};
|
||||
|
||||
export const Chip = ({
|
||||
size = ChipSize.Small,
|
||||
label,
|
||||
@ -137,6 +150,7 @@ export const Chip = ({
|
||||
accent = ChipAccent.TextPrimary,
|
||||
className,
|
||||
maxWidth,
|
||||
forceEmptyText = false,
|
||||
}: ChipProps) => {
|
||||
return (
|
||||
<StyledContainer
|
||||
@ -152,10 +166,12 @@ export const Chip = ({
|
||||
{leftComponent}
|
||||
{!isLabelHidden && label && label.trim() ? (
|
||||
<OverflowingTextWithTooltip size={size} text={label} />
|
||||
) : (
|
||||
) : !forceEmptyText ? (
|
||||
<StyledDiv>Untitled</StyledDiv>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{rightComponent?.()}
|
||||
{renderRightComponent(rightComponent)}
|
||||
</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 { AvatarChip } from './avatar-chip/AvatarChip';
|
||||
export type { AvatarChipsLeftComponentProps } from './avatar-chip/AvatarChipLeftComponent';
|
||||
export { AvatarChipsLeftComponent } from './avatar-chip/AvatarChipLeftComponent';
|
||||
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 { MultipleAvatarChipProps } from './avatar-chip/MultipleAvatarChip';
|
||||
export { MultipleAvatarChip } from './avatar-chip/MultipleAvatarChip';
|
||||
export type { ChipProps } from './chip/Chip';
|
||||
export { ChipSize, ChipAccent, ChipVariant, Chip } from './chip/Chip';
|
||||
export { LINK_CHIP_CLICK_OUTSIDE_ID } from './chip/constants/LinkChipClickOutsideId';
|
||||
|
||||
Reference in New Issue
Block a user