AI settings tab (#13496)

https://github.com/user-attachments/assets/87f5a556-ff12-4ce0-aaa7-9120c0432151

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
Abdul Rahman
2025-08-01 20:29:28 +05:30
committed by GitHub
parent 75214c3e61
commit c3a9843fcb
60 changed files with 2453 additions and 482 deletions
@@ -1,32 +1,20 @@
import { useAiAgentOutputSchema } from '@/ai/hooks/useAiAgentOutputSchema';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { FormTextFieldInput } from '@/object-record/record-field/form-types/components/FormTextFieldInput';
import { Select } from '@/ui/input/components/Select';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValueV2';
import { WorkflowAiAgentAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
import { AIChatTab } from '@/ai/components/AIChatTab';
import { useWorkflowActionHeader } from '@/workflow/workflow-steps/workflow-actions/hooks/useWorkflowActionHeader';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import { BaseOutputSchema } from '@/workflow/workflow-variables/types/StepOutputSchema';
import styled from '@emotion/styled';
import { t } from '@lingui/core/macro';
import {
IconMessage,
IconSettings,
IconTool,
useIcons,
} from 'twenty-ui/display';
import { useRecoilValue } from 'recoil';
import { useIcons } from 'twenty-ui/display';
import { SelectOption } from 'twenty-ui/input';
import { useFindManyAgentsQuery } from '~/generated-metadata/graphql';
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
import {
WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID,
WorkflowAiAgentTabId,
} from '../constants/workflow-ai-agent-tabs';
import { useAgentRoleAssignment } from '@/ai/hooks/useAgentRoleAssignment';
import { useAgentUpdateFormState } from '@/ai/hooks/useAgentUpdateFormState';
import { useAiAgentOutputSchema } from '@/ai/hooks/useAiAgentOutputSchema';
import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions';
import { WorkflowOutputSchemaBuilder } from './WorkflowOutputSchemaBuilder';
const StyledErrorMessage = styled.div`
@@ -36,11 +24,6 @@ const StyledErrorMessage = styled.div`
margin-top: ${({ theme }) => theme.spacing(1)};
`;
const StyledTabList = styled(TabList)`
background-color: ${({ theme }) => theme.background.secondary};
padding-left: ${({ theme }) => theme.spacing(2)};
`;
type WorkflowEditActionAiAgentProps = {
action: WorkflowAiAgentAction;
actionOptions:
@@ -55,6 +38,7 @@ export const WorkflowEditActionAiAgent = ({
action,
actionOptions,
}: WorkflowEditActionAiAgentProps) => {
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { getIcon } = useIcons();
const { headerTitle, headerIcon, headerIconColor, headerType } =
useWorkflowActionHeader({
@@ -62,13 +46,6 @@ export const WorkflowEditActionAiAgent = ({
defaultTitle: 'AI Agent',
});
const agentId = action.settings.input.agentId;
const { formValues, handleFieldChange, loading } = useAgentUpdateFormState({
agentId,
readonly: actionOptions.readonly === true,
});
const { handleOutputSchemaChange, outputFields } = useAiAgentOutputSchema(
action.settings.outputSchema as BaseOutputSchema,
actionOptions.readonly === true ? undefined : actionOptions.onActionUpdate,
@@ -76,107 +53,98 @@ export const WorkflowEditActionAiAgent = ({
actionOptions.readonly,
);
const modelOptions = useAiModelOptions();
const { data: agentsData, loading: agentsLoading } = useFindManyAgentsQuery();
const noModelsAvailable = modelOptions.length === 0;
const activeTabId = useRecoilComponentValueV2(
activeTabIdComponentState,
WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID,
const agentOptions = (agentsData?.findManyAgents || []).reduce<
SelectOption<string>[]
>(
(acc, agent) => {
if (agent.id !== currentWorkspace?.defaultAgent?.id) {
acc.push({
label: agent.label,
value: agent.id,
Icon: agent.icon ? getIcon(agent.icon) : undefined,
});
}
return acc;
},
[
{
label: t`No Agent`,
value: '',
},
],
);
const { rolesOptions, selectedRole, handleRoleChange } =
useAgentRoleAssignment(agentId);
const noAgentsAvailable = agentOptions.length === 0;
const tabs = [
{
id: WorkflowAiAgentTabId.SETTINGS,
title: t`Settings`,
Icon: IconSettings,
},
{ id: WorkflowAiAgentTabId.TOOLS, title: t`Tools`, Icon: IconTool },
{ id: WorkflowAiAgentTabId.CHAT, title: t`Chat`, Icon: IconMessage },
];
const handleFieldChange = (field: 'agentId' | 'prompt', value: string) => {
if (actionOptions.readonly === true) {
return;
}
actionOptions.onActionUpdate?.({
...action,
settings: {
...action.settings,
input: {
...action.settings.input,
[field]: value,
},
},
});
};
return loading ? (
return agentsLoading ? (
<RightDrawerSkeletonLoader />
) : (
<>
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID}
<WorkflowStepHeader
onTitleChange={(newName: string) => {
if (actionOptions.readonly === true) {
return;
}
actionOptions.onActionUpdate?.({ ...action, name: newName });
}}
Icon={getIcon(headerIcon)}
iconColor={headerIconColor}
initialTitle={headerTitle}
headerType={headerType}
disabled={actionOptions.readonly}
/>
{activeTabId === WorkflowAiAgentTabId.CHAT ? (
<AIChatTab agentId={agentId} isWorkflowAgentNodeChat />
) : (
<>
<WorkflowStepHeader
onTitleChange={(newName: string) => {
if (actionOptions.readonly === true) {
return;
}
actionOptions.onActionUpdate?.({ ...action, name: newName });
}}
Icon={getIcon(headerIcon)}
iconColor={headerIconColor}
initialTitle={headerTitle}
headerType={headerType}
disabled={actionOptions.readonly}
<WorkflowStepBody>
<div>
<Select
dropdownId="select-agent"
label={t`Select Agent`}
options={agentOptions}
value={action.settings.input.agentId || ''}
onChange={(value) => handleFieldChange('agentId', value)}
disabled={actionOptions.readonly || noAgentsAvailable}
/>
<WorkflowStepBody>
{activeTabId === WorkflowAiAgentTabId.SETTINGS && (
<>
<div>
<Select
dropdownId="select-model"
label={t`AI Model`}
options={modelOptions}
value={formValues.modelId}
onChange={(value) => handleFieldChange('modelId', value)}
disabled={actionOptions.readonly || noModelsAvailable}
emptyOption={{
label: t`Auto`,
value: '',
}}
/>
{noModelsAvailable && (
<StyledErrorMessage>
{t`You haven't configured any model provider. Please set up an API Key in your instance's admin panel or as an environment variable.`}
</StyledErrorMessage>
)}
</div>
<FormTextFieldInput
key={`prompt-${formValues.modelId ? action.id : 'empty'}`}
label={t`Instructions for AI`}
placeholder={t`Describe what you want the AI to do...`}
readonly={actionOptions.readonly}
defaultValue={formValues.prompt}
onChange={(value) => handleFieldChange('prompt', value)}
VariablePicker={WorkflowVariablePicker}
multiline
/>
<WorkflowOutputSchemaBuilder
fields={outputFields}
onChange={handleOutputSchemaChange}
readonly={actionOptions.readonly}
/>
</>
)}
{activeTabId === WorkflowAiAgentTabId.TOOLS && (
<Select
dropdownId="select-agent-role"
label={t`Assign Role`}
options={[{ label: t`No role`, value: '' }, ...rolesOptions]}
value={selectedRole || ''}
onChange={handleRoleChange}
disabled={actionOptions.readonly}
/>
)}
</WorkflowStepBody>
</>
)}
{noAgentsAvailable && (
<StyledErrorMessage>
{t`Please create agents in the AI settings to use in workflows.`}
</StyledErrorMessage>
)}
</div>
<FormTextFieldInput
multiline
VariablePicker={WorkflowVariablePicker}
label={t`Instructions for AI`}
placeholder={t`Describe what you want the AI to do...`}
defaultValue={action.settings.input.prompt}
onChange={(value) => handleFieldChange('prompt', value)}
readonly={actionOptions.readonly}
/>
<WorkflowOutputSchemaBuilder
fields={outputFields}
onChange={handleOutputSchemaChange}
readonly={actionOptions.readonly}
/>
</WorkflowStepBody>
</>
);
};
@@ -1,8 +0,0 @@
export enum WorkflowAiAgentTabId {
SETTINGS = 'settings',
TOOLS = 'tools',
CHAT = 'chat',
}
export const WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID =
'WORKFLOW_AI_AGENT_TAB_LIST_COMPONENT_ID';
@@ -1,6 +0,0 @@
import { atom } from 'recoil';
export const workflowAiAgentSelectedRoleState = atom<string | undefined>({
key: 'workflowAiAgentSelectedRoleState',
default: undefined,
});