[AI] agent node prompt tab new design + refactor (#19012)
This commit is contained in:
+29
-14
@@ -1,17 +1,23 @@
|
||||
import { type VariablePickerComponent } from '@/object-record/record-field/ui/form-types/types/VariablePickerComponent';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { WorkflowVariablesDropdown } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdown';
|
||||
import { SEARCH_VARIABLES_DROPDOWN_ID } from '@/workflow/workflow-variables/constants/SearchVariablesDropdownId';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledSearchVariablesDropdownContainer = styled.div<{
|
||||
multiline?: boolean;
|
||||
isReadonly?: boolean;
|
||||
isUnfolded?: boolean;
|
||||
multiline?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ multiline }) =>
|
||||
multiline
|
||||
? 'transparent'
|
||||
: themeCssVariables.background.transparent.lighter};
|
||||
background-color: ${({ isUnfolded, multiline }) =>
|
||||
isUnfolded
|
||||
? themeCssVariables.background.transparent.light
|
||||
: multiline
|
||||
? 'transparent'
|
||||
: themeCssVariables.background.transparent.lighter};
|
||||
border: ${({ multiline }) =>
|
||||
multiline ? 'none' : `1px solid ${themeCssVariables.border.color.medium}`};
|
||||
|
||||
@@ -21,28 +27,31 @@ const StyledSearchVariablesDropdownContainer = styled.div<{
|
||||
: `0 ${themeCssVariables.border.radius.sm} ${themeCssVariables.border.radius.sm} 0`};
|
||||
|
||||
display: flex;
|
||||
height: ${({ multiline }) =>
|
||||
multiline ? themeCssVariables.spacing[7] : 'auto'};
|
||||
|
||||
justify-content: center;
|
||||
|
||||
padding: ${({ multiline }) =>
|
||||
multiline
|
||||
? `${themeCssVariables.spacing[0.5]} ${themeCssVariables.spacing[0]}`
|
||||
: '0'};
|
||||
margin: ${({ multiline }) =>
|
||||
multiline ? `${themeCssVariables.spacing[1]}` : '0'};
|
||||
|
||||
position: ${({ multiline }) => (multiline ? 'absolute' : 'static')};
|
||||
right: ${({ multiline }) =>
|
||||
multiline ? themeCssVariables.spacing[0] : 'auto'};
|
||||
top: ${({ multiline }) =>
|
||||
multiline ? themeCssVariables.spacing[0] : 'auto'};
|
||||
width: ${({ multiline }) =>
|
||||
multiline ? themeCssVariables.spacing[7] : 'auto'};
|
||||
|
||||
&:hover {
|
||||
background-color: ${({ isReadonly, multiline }) => {
|
||||
background-color: ${({ isReadonly, isUnfolded, multiline }) => {
|
||||
if (isReadonly === true) {
|
||||
return multiline
|
||||
? 'transparent'
|
||||
: themeCssVariables.background.transparent.lighter;
|
||||
}
|
||||
return themeCssVariables.background.transparent.light;
|
||||
return isUnfolded
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: themeCssVariables.background.transparent.light;
|
||||
}};
|
||||
}
|
||||
`;
|
||||
@@ -55,10 +64,17 @@ export const WorkflowVariablePicker: VariablePickerComponent = ({
|
||||
shouldDisplayRecordObjects = false,
|
||||
shouldDisplayRecordFields = true,
|
||||
}) => {
|
||||
const dropdownId = `${SEARCH_VARIABLES_DROPDOWN_ID}-${instanceId}`;
|
||||
const isDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledSearchVariablesDropdownContainer
|
||||
multiline={multiline}
|
||||
isReadonly={disabled}
|
||||
isUnfolded={isDropdownOpen}
|
||||
multiline={multiline}
|
||||
>
|
||||
<WorkflowVariablesDropdown
|
||||
instanceId={instanceId}
|
||||
@@ -66,7 +82,6 @@ export const WorkflowVariablePicker: VariablePickerComponent = ({
|
||||
disabled={disabled}
|
||||
shouldDisplayRecordObjects={shouldDisplayRecordObjects}
|
||||
shouldDisplayRecordFields={shouldDisplayRecordFields}
|
||||
multiline={multiline}
|
||||
/>
|
||||
</StyledSearchVariablesDropdownContainer>
|
||||
);
|
||||
|
||||
+19
-46
@@ -1,11 +1,9 @@
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { isDropdownOpenComponentState } from '@/ui/layout/dropdown/states/isDropdownOpenComponentState';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { type InputSchemaPropertyType } from 'twenty-shared/workflow';
|
||||
import { WorkflowVariablesDropdownStepItems } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownStepItems';
|
||||
import { WorkflowVariablesDropdownSteps } from '@/workflow/workflow-variables/components/WorkflowVariablesDropdownSteps';
|
||||
import { SEARCH_VARIABLES_DROPDOWN_ID } from '@/workflow/workflow-variables/constants/SearchVariablesDropdownId';
|
||||
import { type InputSchemaPropertyType } from 'twenty-shared/workflow';
|
||||
|
||||
import { useAvailableVariablesInWorkflowStep } from '@/workflow/workflow-variables/hooks/useAvailableVariablesInWorkflowStep';
|
||||
import { type StepOutputSchemaV2 } from '@/workflow/workflow-variables/types/StepOutputSchemaV2';
|
||||
@@ -13,59 +11,42 @@ import { styled } from '@linaria/react';
|
||||
import { useContext, useState } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { IconVariablePlus } from 'twenty-ui/display';
|
||||
import { themeCssVariables, ThemeContext } from 'twenty-ui/theme-constants';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledDropdownVariableButtonContainer = styled.div<{
|
||||
isUnfolded?: boolean;
|
||||
transparentBackground?: boolean;
|
||||
disabled?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
background-color: ${({ transparentBackground }) =>
|
||||
transparentBackground
|
||||
? 'transparent'
|
||||
: themeCssVariables.background.transparent.lighter};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
background-color: transparent;
|
||||
border-bottom-right-radius: ${themeCssVariables.border.radius.sm};
|
||||
border-top-right-radius: ${themeCssVariables.border.radius.sm};
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
cursor: pointer;
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
user-select: none;
|
||||
&:hover {
|
||||
background: ${({ isUnfolded, transparentBackground }) =>
|
||||
transparentBackground
|
||||
? 'transparent'
|
||||
: isUnfolded
|
||||
? themeCssVariables.background.transparent.medium
|
||||
: themeCssVariables.background.transparent.light};
|
||||
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
|
||||
}
|
||||
`;
|
||||
|
||||
export const WorkflowVariablesDropdown = ({
|
||||
clickableComponent,
|
||||
disabled,
|
||||
fieldTypesToExclude,
|
||||
instanceId,
|
||||
onVariableSelect,
|
||||
disabled,
|
||||
shouldDisplayRecordFields,
|
||||
shouldDisplayRecordObjects,
|
||||
fieldTypesToExclude,
|
||||
multiline,
|
||||
clickableComponent,
|
||||
}: {
|
||||
clickableComponent?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
fieldTypesToExclude?: InputSchemaPropertyType[];
|
||||
instanceId: string;
|
||||
onVariableSelect: (variableName: string) => void;
|
||||
shouldDisplayRecordFields: boolean;
|
||||
shouldDisplayRecordObjects: boolean;
|
||||
fieldTypesToExclude?: InputSchemaPropertyType[];
|
||||
disabled?: boolean;
|
||||
multiline?: boolean;
|
||||
clickableComponent?: React.ReactNode;
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const dropdownId = `${SEARCH_VARIABLES_DROPDOWN_ID}-${instanceId}`;
|
||||
const isDropdownOpen = useAtomComponentStateValue(
|
||||
isDropdownOpenComponentState,
|
||||
dropdownId,
|
||||
);
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
const availableVariablesInWorkflowStep = useAvailableVariablesInWorkflowStep({
|
||||
shouldDisplayRecordFields,
|
||||
@@ -102,13 +83,9 @@ export const WorkflowVariablesDropdown = ({
|
||||
|
||||
if (disabled === true || noAvailableVariables) {
|
||||
return (
|
||||
<StyledDropdownVariableButtonContainer
|
||||
isUnfolded={isDropdownOpen}
|
||||
disabled={true}
|
||||
transparentBackground
|
||||
>
|
||||
<StyledDropdownVariableButtonContainer disabled={true}>
|
||||
<IconVariablePlus
|
||||
size={theme.icon.size.sm}
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.light}
|
||||
/>
|
||||
</StyledDropdownVariableButtonContainer>
|
||||
@@ -121,11 +98,8 @@ export const WorkflowVariablesDropdown = ({
|
||||
isDropdownInModal={true}
|
||||
clickableComponent={
|
||||
clickableComponent ?? (
|
||||
<StyledDropdownVariableButtonContainer
|
||||
isUnfolded={isDropdownOpen}
|
||||
transparentBackground
|
||||
>
|
||||
<IconVariablePlus size={theme.icon.size.sm} />
|
||||
<StyledDropdownVariableButtonContainer>
|
||||
<IconVariablePlus size={theme.icon.size.md} />
|
||||
</StyledDropdownVariableButtonContainer>
|
||||
)
|
||||
}
|
||||
@@ -147,8 +121,7 @@ export const WorkflowVariablesDropdown = ({
|
||||
}
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{
|
||||
x: 2,
|
||||
y: parseInt(theme.spacing[multiline ? 11 : 1], 10),
|
||||
y: parseInt(theme.spacing[1], 10),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
+18
-1
@@ -44,12 +44,29 @@ export const useStepsOutputSchema = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove this fallback after upgrade command
|
||||
// `upgrade:1-21:migrate-ai-agent-text-to-json-response-format`
|
||||
// has run on all workspaces.
|
||||
const persistedOutputSchema =
|
||||
step.type === 'AI_AGENT' &&
|
||||
(!isDefined(step.settings?.outputSchema) ||
|
||||
Object.keys(step.settings.outputSchema).length === 0)
|
||||
? {
|
||||
response: {
|
||||
isLeaf: true,
|
||||
type: 'string',
|
||||
label: 'Response',
|
||||
value: null,
|
||||
},
|
||||
}
|
||||
: step.settings?.outputSchema;
|
||||
|
||||
const outputSchema = shouldComputeOnFrontend
|
||||
? computeStepOutputSchema({
|
||||
step,
|
||||
objectMetadataItems,
|
||||
})
|
||||
: step.settings?.outputSchema;
|
||||
: persistedOutputSchema;
|
||||
|
||||
const stepOutputSchema: StepOutputSchemaV2 = {
|
||||
id: step.id,
|
||||
|
||||
+4
-11
@@ -409,20 +409,13 @@ describe('computeStepOutputSchema', () => {
|
||||
});
|
||||
|
||||
describe('AI_AGENT step', () => {
|
||||
it('should return response schema', () => {
|
||||
it('should return undefined for AI_AGENT step type', () => {
|
||||
const result = computeStepOutputSchema({
|
||||
step: { type: 'AI_AGENT', settings: {} } as any,
|
||||
objectMetadataItems: [],
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
response: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Response',
|
||||
value: null,
|
||||
},
|
||||
});
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -461,8 +454,8 @@ describe('shouldComputeOutputSchemaOnFrontend', () => {
|
||||
expect(shouldComputeOutputSchemaOnFrontend('HTTP_REQUEST')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for AI_AGENT', () => {
|
||||
expect(shouldComputeOutputSchemaOnFrontend('AI_AGENT')).toBe(true);
|
||||
it('should return false for AI_AGENT', () => {
|
||||
expect(shouldComputeOutputSchemaOnFrontend('AI_AGENT')).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false for WEBHOOK', () => {
|
||||
|
||||
+1
-11
@@ -14,6 +14,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { DatabaseEventAction } from '~/generated-metadata/graphql';
|
||||
|
||||
const PERSISTED_OUTPUT_SCHEMA_TYPES = [
|
||||
'AI_AGENT',
|
||||
'CODE',
|
||||
'HTTP_REQUEST',
|
||||
'WEBHOOK',
|
||||
@@ -190,17 +191,6 @@ export const computeStepOutputSchema = ({
|
||||
return generateFormOutputSchema(formFields, objectMetadataItems);
|
||||
}
|
||||
|
||||
case 'AI_AGENT': {
|
||||
return {
|
||||
response: {
|
||||
isLeaf: true,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Response',
|
||||
value: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'SEND_EMAIL':
|
||||
case 'DRAFT_EMAIL': {
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user