[AI] agent node prompt tab new design + refactor (#19012)

This commit is contained in:
nitin
2026-04-02 13:54:58 +05:30
committed by GitHub
parent 19c710b87f
commit ade6ed9c32
28 changed files with 786 additions and 475 deletions
@@ -11,17 +11,13 @@ import { AIChatEditorFocusEffect } from '@/ai/components/internal/AIChatEditorFo
import { AIChatSkeletonLoader } from '@/ai/components/internal/AIChatSkeletonLoader';
import { SendMessageButton } from '@/ai/components/internal/SendMessageButton';
import { useAIChatEditor } from '@/ai/hooks/useAIChatEditor';
import { useAiModelOptions } from '@/ai/hooks/useAiModelOptions';
import { useAgentChatModelId } from '@/ai/hooks/useAgentChatModelId';
import { useWorkspaceAiModelAvailability } from '@/ai/hooks/useWorkspaceAiModelAvailability';
import { agentChatUserSelectedModelState } from '@/ai/states/agentChatUserSelectedModelState';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { aiModelsState } from '@/client-config/states/aiModelsState';
import { getModelIcon } from '@/settings/admin-panel/ai/utils/getModelIcon';
import { Select } from '@/ui/input/components/Select';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { t } from '@lingui/core/macro';
import { type SelectOption } from 'twenty-ui/input';
const StyledInputArea = styled.div<{ isMobile: boolean }>`
align-items: flex-end;
@@ -110,9 +106,18 @@ const StyledRightButtonsContainer = styled.div`
export const AIChatEditorSection = () => {
const isMobile = useIsMobile();
const currentWorkspace = useAtomStateValue(currentWorkspaceState);
const aiModels = useAtomStateValue(aiModelsState);
const { enabledModels } = useWorkspaceAiModelAvailability();
const { options, pinnedOption } = useAiModelOptions({
variant: 'pinned-default',
});
const smartModelOptions: SelectOption<string | null>[] = options;
const defaultPinnedOption: SelectOption<string | null> | undefined =
pinnedOption
? {
...pinnedOption,
value: null,
}
: undefined;
const setAgentChatUserSelectedModel = useSetAtomState(
agentChatUserSelectedModelState,
);
@@ -120,36 +125,6 @@ export const AIChatEditorSection = () => {
const { editor, handleSendAndClear } = useAIChatEditor();
const workspaceSmartModel = aiModels.find(
(model) => model.modelId === currentWorkspace?.smartModel,
);
const resolvedDefaultModelId = enabledModels.find(
(model) =>
model.label === workspaceSmartModel?.label &&
model.providerName === workspaceSmartModel?.providerName,
)?.modelId;
const defaultPinnedOption = workspaceSmartModel
? {
value: null as string | null,
label: workspaceSmartModel.label,
Icon: getModelIcon(
workspaceSmartModel.modelFamily,
workspaceSmartModel.providerName,
),
contextualText: t`default`,
}
: undefined;
const smartModelOptions = enabledModels
.filter((model) => model.modelId !== resolvedDefaultModelId)
.map((model) => ({
value: model.modelId as string | null,
label: model.label,
Icon: getModelIcon(model.modelFamily, model.providerName),
}));
return (
<>
<AIChatEditorFocusEffect editor={editor} />
@@ -0,0 +1,145 @@
import { aiModelsState } from '@/client-config/states/aiModelsState';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { IconBrandX, IconWorld } from 'twenty-ui/display';
import { Checkbox } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
const StyledCheckboxContainer = styled.div<{ disabled: boolean }>`
align-items: center;
border-radius: ${themeCssVariables.border.radius.sm};
cursor: ${({ disabled }) => (disabled ? 'not-allowed' : 'pointer')};
display: flex;
height: ${themeCssVariables.spacing[8]};
justify-content: space-between;
padding-inline: ${themeCssVariables.spacing[2]};
transition: background-color
calc(${themeCssVariables.animation.duration.normal} * 1s) ease;
&:hover {
background-color: ${({ disabled }) =>
disabled
? 'transparent'
: themeCssVariables.background.transparent.light};
}
`;
const StyledCheckboxLabel = styled.div`
align-items: center;
display: flex;
gap: ${themeCssVariables.spacing[1]};
`;
type ModelConfiguration = {
webSearch?: {
enabled: boolean;
configuration?: Record<string, unknown>;
};
twitterSearch?: {
enabled: boolean;
configuration?: Record<string, unknown>;
};
};
type SettingsAgentModelCapabilitiesProps = {
selectedModelId: string;
modelConfiguration: ModelConfiguration;
onConfigurationChange: (configuration: ModelConfiguration) => void;
disabled?: boolean;
};
export const SettingsAgentModelCapabilities = ({
selectedModelId,
modelConfiguration,
onConfigurationChange,
disabled = false,
}: SettingsAgentModelCapabilitiesProps) => {
const { theme } = useContext(ThemeContext);
const aiModels = useAtomStateValue(aiModelsState);
const selectedModel = aiModels.find((m) => m.modelId === selectedModelId);
const nativeCapabilities = selectedModel?.nativeCapabilities;
if (!isDefined(nativeCapabilities)) {
return null;
}
if (!nativeCapabilities.webSearch && !nativeCapabilities.twitterSearch) {
return null;
}
const handleCapabilityToggle = (
capability: 'webSearch' | 'twitterSearch',
enabled: boolean,
) => {
if (disabled) {
return;
}
onConfigurationChange({
...modelConfiguration,
[capability]: {
enabled,
configuration: modelConfiguration[capability]?.configuration || {},
},
});
};
const capabilities = [
...(nativeCapabilities.webSearch
? [
{
key: 'webSearch' as const,
label: t`Web Search`,
Icon: IconWorld,
enabled: modelConfiguration.webSearch?.enabled || false,
},
]
: []),
...(nativeCapabilities.twitterSearch
? [
{
key: 'twitterSearch' as const,
label: t`Twitter/X Search`,
Icon: IconBrandX,
enabled: modelConfiguration.twitterSearch?.enabled || false,
},
]
: []),
];
return (
<Section>
<InputLabel>{t`Enable model-specific features`}</InputLabel>
<div>
{capabilities.map((capability) => (
<StyledCheckboxContainer
disabled={disabled}
key={capability.key}
onClick={() =>
handleCapabilityToggle(capability.key, !capability.enabled)
}
>
<StyledCheckboxLabel>
<capability.Icon size={theme.icon.size.sm} />
<span>{capability.label}</span>
</StyledCheckboxLabel>
<Checkbox
checked={capability.enabled}
onChange={(event) => {
event.stopPropagation();
handleCapabilityToggle(capability.key, event.target.checked);
}}
disabled={disabled}
/>
</StyledCheckboxContainer>
))}
</div>
</Section>
);
};