[AI] Refactor settings search to use SearchInput and restructure Skills tab (#18876)
closes - https://discord.com/channels/1130383047699738754/1480980523315626178
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
@@ -23,7 +23,7 @@ import { Button } from 'twenty-ui/input';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
import { SettingsAIMCP } from './components/SettingsAIMCP';
|
||||
import { SettingsAIModelsTab } from './components/SettingsAIModelsTab';
|
||||
import { SettingsSkillsTable } from './components/SettingsSkillsTable';
|
||||
import { SettingsAgentSkills } from './components/SettingsAgentSkills';
|
||||
import { SettingsToolsTable } from './components/SettingsToolsTable';
|
||||
import { SETTINGS_AI_TABS } from './constants/SettingsAiTabs';
|
||||
|
||||
@@ -84,7 +84,7 @@ export const SettingsAI = () => {
|
||||
componentInstanceId={SETTINGS_AI_TABS.COMPONENT_INSTANCE_ID}
|
||||
/>
|
||||
{isModelsTab && <SettingsAIModelsTab />}
|
||||
{isSkillsTab && <SettingsSkillsTable />}
|
||||
{isSkillsTab && <SettingsAgentSkills />}
|
||||
{isToolsTab && <SettingsToolsTable />}
|
||||
{isMoreTab && (
|
||||
<>
|
||||
|
||||
@@ -2,7 +2,6 @@ import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext, useState } from 'react';
|
||||
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
@@ -12,13 +11,8 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconChevronRight,
|
||||
IconFilter,
|
||||
IconSearch,
|
||||
IconSettingsAutomation,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconChevronRight, IconSettingsAutomation } from 'twenty-ui/display';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { SETTINGS_AI_AGENT_TABLE_METADATA } from '~/pages/settings/ai/constants/SettingsAiAgentTableMetadata';
|
||||
@@ -33,15 +27,8 @@ import {
|
||||
SettingsAIAgentTableRow,
|
||||
} from './SettingsAIAgentTableRow';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
const StyledSearchContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledTableContainer = styled.div`
|
||||
@@ -79,47 +66,36 @@ export const SettingsAIAgentsTable = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="settings-ai-agents-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search an agent...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<Dropdown
|
||||
dropdownId="settings-ai-agents-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconFilter}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Filter`}
|
||||
<StyledSearchContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search an agent...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
filterDropdown={(filterButton) => (
|
||||
<Dropdown
|
||||
dropdownId="settings-ai-agents-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={filterButton}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconSettingsAutomation}
|
||||
onToggleChange={() =>
|
||||
setShowWorkflowAgents(!showWorkflowAgents)
|
||||
}
|
||||
toggled={showWorkflowAgents}
|
||||
text={t`Workflow agents`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconSettingsAutomation}
|
||||
onToggleChange={() =>
|
||||
setShowWorkflowAgents(!showWorkflowAgents)
|
||||
}
|
||||
toggled={showWorkflowAgents}
|
||||
text={t`Workflow agents`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</StyledSearchAndFilterContainer>
|
||||
</StyledSearchContainer>
|
||||
|
||||
<StyledTableContainer>
|
||||
<Table>
|
||||
|
||||
@@ -14,16 +14,11 @@ import { SettingsOptionCardContentToggle } from '@/settings/components/SettingsO
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Select } from '@/ui/input/components/Select';
|
||||
import { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import {
|
||||
H2Title,
|
||||
IconBolt,
|
||||
IconSearch,
|
||||
IconTwentyStar,
|
||||
} from 'twenty-ui/display';
|
||||
import { H2Title, IconBolt, IconTwentyStar } from 'twenty-ui/display';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
@@ -281,13 +276,10 @@ export const SettingsAIModelsTab = () => {
|
||||
/>
|
||||
|
||||
<StyledSearchContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="model-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
<SearchInput
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext, useMemo, useState } from 'react';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { H2Title, IconArchive, IconPlus } from 'twenty-ui/display';
|
||||
import { Button, SearchInput } from 'twenty-ui/input';
|
||||
import { MenuItemToggle, UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { useMutation, useQuery } from '@apollo/client/react';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import {
|
||||
ActivateSkillDocument,
|
||||
DeleteSkillDocument,
|
||||
FindManySkillsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { SETTINGS_SKILL_TABLE_METADATA } from '~/pages/settings/ai/constants/SettingsSkillTableMetadata';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
import { SettingsAgentSkillsTable } from './SettingsAgentSkillsTable';
|
||||
|
||||
const StyledCoverImage = styled.div`
|
||||
background-size: cover;
|
||||
height: 160px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledFooterContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SearchInput)`
|
||||
margin-bottom: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
export const SettingsAgentSkills = () => {
|
||||
const { t } = useLingui();
|
||||
const { colorScheme } = useContext(ThemeContext);
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const { data, loading, refetch } = useQuery(FindManySkillsDocument);
|
||||
const [activateSkill] = useMutation(ActivateSkillDocument);
|
||||
const [deleteSkill] = useMutation(DeleteSkillDocument);
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showDeactivated, setShowDeactivated] = useState(true);
|
||||
|
||||
const skills = data?.skills ?? [];
|
||||
|
||||
const sortedSkills = useSortedArray(skills, SETTINGS_SKILL_TABLE_METADATA);
|
||||
|
||||
const filteredSkills = useMemo(
|
||||
() =>
|
||||
sortedSkills.filter((skill) => {
|
||||
const searchNormalized = normalizeSearchText(searchTerm);
|
||||
const matchesSearch =
|
||||
normalizeSearchText(skill.name).includes(searchNormalized) ||
|
||||
normalizeSearchText(skill.label).includes(searchNormalized);
|
||||
|
||||
if (!matchesSearch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!skill.isActive && !showDeactivated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
[sortedSkills, searchTerm, showDeactivated],
|
||||
);
|
||||
|
||||
const handleActivate = async (skillId: string) => {
|
||||
try {
|
||||
await activateSkill({ variables: { id: skillId } });
|
||||
enqueueSuccessSnackBar({ message: t`Skill activated` });
|
||||
refetch();
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Failed to activate skill` });
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (skillId: string) => {
|
||||
try {
|
||||
await deleteSkill({ variables: { id: skillId } });
|
||||
enqueueSuccessSnackBar({ message: t`Skill deleted` });
|
||||
refetch();
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Failed to delete skill` });
|
||||
}
|
||||
};
|
||||
|
||||
const coverImage =
|
||||
colorScheme === 'light'
|
||||
? '/images/ai/ai-skills-cover-light.png'
|
||||
: '/images/ai/ai-skills-cover-dark.png';
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledCoverImage style={{ backgroundImage: `url('${coverImage}')` }} />
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Skills`}
|
||||
description={t`Use filter to see existing tools or create your own`}
|
||||
/>
|
||||
|
||||
<StyledSearchInput
|
||||
placeholder={t`Search a skill...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
filterDropdown={(filterButton) => (
|
||||
<Dropdown
|
||||
dropdownId="settings-skills-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={filterButton}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() =>
|
||||
setShowDeactivated(!showDeactivated)
|
||||
}
|
||||
toggled={showDeactivated}
|
||||
text={t`Deactivated`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<SettingsAgentSkillsTable
|
||||
skills={filteredSkills}
|
||||
loading={loading}
|
||||
onActivate={handleActivate}
|
||||
onDelete={handleDelete}
|
||||
/>
|
||||
<StyledFooterContainer>
|
||||
<UndecoratedLink to={getSettingsPath(SettingsPath.AINewSkill)}>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`New Skill`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
</StyledFooterContainer>
|
||||
</Section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext } from 'react';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { IconChevronRight } from 'twenty-ui/display';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { type FindManySkillsQuery } from '~/generated-metadata/graphql';
|
||||
import { SettingsSkillInactiveMenuDropDown } from '~/pages/settings/ai/components/SettingsSkillInactiveMenuDropDown';
|
||||
import { SETTINGS_SKILL_TABLE_METADATA } from '~/pages/settings/ai/constants/SettingsSkillTableMetadata';
|
||||
import { SettingsSkillTableRow } from './SettingsSkillTableRow';
|
||||
|
||||
type Skill = FindManySkillsQuery['skills'][number];
|
||||
|
||||
type SettingsAgentSkillsTableProps = {
|
||||
skills: Skill[];
|
||||
loading: boolean;
|
||||
onActivate: (skillId: string) => void;
|
||||
onDelete: (skillId: string) => void;
|
||||
};
|
||||
|
||||
const StyledTableHeaderRowContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const SettingsAgentSkillsTable = ({
|
||||
skills,
|
||||
loading,
|
||||
onActivate,
|
||||
onDelete,
|
||||
}: SettingsAgentSkillsTableProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { t } = useLingui();
|
||||
|
||||
const showSkeleton = loading && skills.length === 0;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<StyledTableHeaderRowContainer>
|
||||
<TableRow gridTemplateColumns="1fr 120px 36px">
|
||||
{SETTINGS_SKILL_TABLE_METADATA.fields.map(
|
||||
(settingsSkillTableMetadataField) => (
|
||||
<SortableTableHeader
|
||||
key={settingsSkillTableMetadataField.fieldName}
|
||||
fieldName={settingsSkillTableMetadataField.fieldName}
|
||||
label={t(settingsSkillTableMetadataField.fieldLabel)}
|
||||
tableId={SETTINGS_SKILL_TABLE_METADATA.tableId}
|
||||
align={settingsSkillTableMetadataField.align}
|
||||
initialSort={SETTINGS_SKILL_TABLE_METADATA.initialSort}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
</StyledTableHeaderRowContainer>
|
||||
{showSkeleton
|
||||
? Array.from({ length: 3 }).map((_, index) => (
|
||||
<Skeleton height={32} borderRadius={4} key={index} />
|
||||
))
|
||||
: skills.map((skill) => (
|
||||
<SettingsSkillTableRow
|
||||
key={skill.id}
|
||||
skill={skill}
|
||||
action={
|
||||
skill.isActive ? (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
) : (
|
||||
<SettingsSkillInactiveMenuDropDown
|
||||
isCustomSkill={skill.isCustom}
|
||||
skillId={skill.id}
|
||||
onActivate={() => onActivate(skill.id)}
|
||||
onDelete={() => onDelete(skill.id)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
link={
|
||||
skill.isActive
|
||||
? getSettingsPath(SettingsPath.AISkillDetail, {
|
||||
skillId: skill.id,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { type ReactNode, useContext } from 'react';
|
||||
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useIcons, OverflowingTextWithTooltip } from 'twenty-ui/display';
|
||||
import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { type Skill } from '~/generated-metadata/graphql';
|
||||
@@ -44,7 +44,7 @@ export const SettingsSkillTableRow = ({
|
||||
overflow="hidden"
|
||||
>
|
||||
<StyledIconContainer>
|
||||
<Icon size={theme.icon.size.md} />
|
||||
<Icon size={theme.icon.size.md} color={theme.color.blue9} />
|
||||
</StyledIconContainer>
|
||||
<OverflowingTextWithTooltip text={skill.label} />
|
||||
</TableCell>
|
||||
|
||||
@@ -1,226 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useContext, useMemo, useState } from 'react';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { useSortedArray } from '@/ui/layout/table/hooks/useSortedArray';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconArchive,
|
||||
IconChevronRight,
|
||||
IconFilter,
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { MenuItemToggle, UndecoratedLink } from 'twenty-ui/navigation';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { useMutation, useQuery } from '@apollo/client/react';
|
||||
import {
|
||||
ActivateSkillDocument,
|
||||
DeleteSkillDocument,
|
||||
FindManySkillsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
import { SettingsSkillInactiveMenuDropDown } from '~/pages/settings/ai/components/SettingsSkillInactiveMenuDropDown';
|
||||
import { SETTINGS_SKILL_TABLE_METADATA } from '~/pages/settings/ai/constants/SettingsSkillTableMetadata';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
import { SettingsSkillTableRow } from './SettingsSkillTableRow';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSearchInputWrapper = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledTableHeaderRowContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledFooterContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
export const SettingsSkillsTable = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { data, loading, refetch } = useQuery(FindManySkillsDocument);
|
||||
const [activateSkill] = useMutation(ActivateSkillDocument);
|
||||
const [deleteSkill] = useMutation(DeleteSkillDocument);
|
||||
|
||||
const { t } = useLingui();
|
||||
const { enqueueSuccessSnackBar, enqueueErrorSnackBar } = useSnackBar();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [showDeactivated, setShowDeactivated] = useState(true);
|
||||
|
||||
const skills = data?.skills ?? [];
|
||||
|
||||
const sortedSkills = useSortedArray(skills, SETTINGS_SKILL_TABLE_METADATA);
|
||||
|
||||
const filteredSkills = useMemo(
|
||||
() =>
|
||||
sortedSkills.filter((skill) => {
|
||||
const searchNormalized = normalizeSearchText(searchTerm);
|
||||
const matchesSearch =
|
||||
normalizeSearchText(skill.name).includes(searchNormalized) ||
|
||||
normalizeSearchText(skill.label).includes(searchNormalized);
|
||||
|
||||
if (!matchesSearch) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!skill.isActive && !showDeactivated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
[sortedSkills, searchTerm, showDeactivated],
|
||||
);
|
||||
|
||||
const showSkeleton = loading && skills.length === 0;
|
||||
|
||||
const handleActivate = async (skillId: string) => {
|
||||
try {
|
||||
await activateSkill({ variables: { id: skillId } });
|
||||
enqueueSuccessSnackBar({ message: t`Skill activated` });
|
||||
refetch();
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Failed to activate skill` });
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (skillId: string) => {
|
||||
try {
|
||||
await deleteSkill({ variables: { id: skillId } });
|
||||
enqueueSuccessSnackBar({ message: t`Skill deleted` });
|
||||
refetch();
|
||||
} catch {
|
||||
enqueueErrorSnackBar({ message: t`Failed to delete skill` });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInputWrapper>
|
||||
<SettingsTextInput
|
||||
instanceId="skill-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a skill...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchInputWrapper>
|
||||
<Dropdown
|
||||
dropdownId="settings-skills-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconFilter}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Filter`}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() => setShowDeactivated(!showDeactivated)}
|
||||
toggled={showDeactivated}
|
||||
text={t`Deactivated`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
</StyledSearchAndFilterContainer>
|
||||
|
||||
<Table>
|
||||
<StyledTableHeaderRowContainer>
|
||||
<TableRow gridTemplateColumns="1fr 120px 36px">
|
||||
{SETTINGS_SKILL_TABLE_METADATA.fields.map(
|
||||
(settingsSkillTableMetadataField) => (
|
||||
<SortableTableHeader
|
||||
key={settingsSkillTableMetadataField.fieldName}
|
||||
fieldName={settingsSkillTableMetadataField.fieldName}
|
||||
label={t(settingsSkillTableMetadataField.fieldLabel)}
|
||||
tableId={SETTINGS_SKILL_TABLE_METADATA.tableId}
|
||||
align={settingsSkillTableMetadataField.align}
|
||||
initialSort={SETTINGS_SKILL_TABLE_METADATA.initialSort}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
<TableHeader />
|
||||
</TableRow>
|
||||
</StyledTableHeaderRowContainer>
|
||||
{showSkeleton
|
||||
? Array.from({ length: 3 }).map((_, index) => (
|
||||
<Skeleton height={32} borderRadius={4} key={index} />
|
||||
))
|
||||
: filteredSkills.map((skill) => (
|
||||
<SettingsSkillTableRow
|
||||
key={skill.id}
|
||||
skill={skill}
|
||||
action={
|
||||
skill.isActive ? (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
) : (
|
||||
<SettingsSkillInactiveMenuDropDown
|
||||
isCustomSkill={skill.isCustom}
|
||||
skillId={skill.id}
|
||||
onActivate={() => handleActivate(skill.id)}
|
||||
onDelete={() => handleDelete(skill.id)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
link={
|
||||
skill.isActive
|
||||
? getSettingsPath(SettingsPath.AISkillDetail, {
|
||||
skillId: skill.id,
|
||||
})
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
|
||||
<StyledFooterContainer>
|
||||
<UndecoratedLink to={getSettingsPath(SettingsPath.AINewSkill)}>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`New Skill`}
|
||||
size="small"
|
||||
variant="secondary"
|
||||
/>
|
||||
</UndecoratedLink>
|
||||
</StyledFooterContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -8,19 +8,13 @@ import { usePersistLogicFunction } from '@/logic-functions/hooks/usePersistLogic
|
||||
import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import {
|
||||
H2Title,
|
||||
IconChevronRight,
|
||||
IconPlus,
|
||||
IconSearch,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { H2Title, IconChevronRight, IconPlus } from 'twenty-ui/display';
|
||||
import { Button, SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -32,18 +26,10 @@ import {
|
||||
SettingsToolTableRow,
|
||||
} from './SettingsToolTableRow';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTableHeaderRowContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
@@ -171,18 +157,13 @@ export const SettingsToolsTable = () => {
|
||||
title={t`Custom`}
|
||||
description={t`Custom tools created in your workspace`}
|
||||
/>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="custom-tool-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a custom tool...`}
|
||||
value={customSearchTerm}
|
||||
onChange={setCustomSearchTerm}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
</StyledSearchAndFilterContainer>
|
||||
<StyledSearchContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search a custom tool...`}
|
||||
value={customSearchTerm}
|
||||
onChange={setCustomSearchTerm}
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
<Table>
|
||||
<StyledTableHeaderRowContainer>
|
||||
<TableRow
|
||||
@@ -229,18 +210,13 @@ export const SettingsToolsTable = () => {
|
||||
title={t`Built-in`}
|
||||
description={t`Standard tools available to AI agents`}
|
||||
/>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="builtin-tool-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a built-in tool...`}
|
||||
value={builtInSearchTerm}
|
||||
onChange={setBuiltInSearchTerm}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
</StyledSearchAndFilterContainer>
|
||||
<StyledSearchContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search a built-in tool...`}
|
||||
value={builtInSearchTerm}
|
||||
onChange={setBuiltInSearchTerm}
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
<Table>
|
||||
<StyledTableHeaderRowContainer>
|
||||
<TableRow
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
SettingsObjectFieldItemTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectFieldItemTableRow';
|
||||
import { settingsObjectFieldsFamilyState } from '@/settings/data-model/object-details/states/settingsObjectFieldsFamilyState';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||
import { DropdownContent } from '@/ui/layout/dropdown/components/DropdownContent';
|
||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||
@@ -24,28 +23,16 @@ import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAto
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import {
|
||||
IconArchive,
|
||||
IconFilter,
|
||||
IconSearch,
|
||||
IconSettings,
|
||||
} from 'twenty-ui/display';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { IconArchive, IconSettings } from 'twenty-ui/display';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useMapFieldMetadataItemToSettingsObjectDetailTableItem } from '~/pages/settings/data-model/hooks/useMapFieldMetadataItemToSettingsObjectDetailTableItem';
|
||||
import { type SettingsObjectDetailTableItem } from '~/pages/settings/data-model/types/SettingsObjectDetailTableItem';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const SETTINGS_OBJECT_FIELD_TABLE_METADATA: TableMetadata<SettingsObjectDetailTableItem> =
|
||||
@@ -163,55 +150,45 @@ export const SettingsObjectFieldTable = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="object-field-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a field...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<Dropdown
|
||||
dropdownId="settings-fields-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={
|
||||
<Button
|
||||
Icon={IconFilter}
|
||||
size="medium"
|
||||
variant="secondary"
|
||||
accent="default"
|
||||
ariaLabel={t`Filter`}
|
||||
<StyledSearchContainer>
|
||||
<SearchInput
|
||||
placeholder={t`Search a field...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
filterDropdown={(filterButton) => (
|
||||
<Dropdown
|
||||
dropdownId="settings-fields-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
dropdownOffset={{ x: 0, y: 8 }}
|
||||
clickableComponent={filterButton}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() => setShowInactive(!showInactive)}
|
||||
toggled={showInactive}
|
||||
text={t`Inactive`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
{isAdvancedModeEnabled && (
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconSettings}
|
||||
onToggleChange={() =>
|
||||
setShowSystemFields(!showSystemFields)
|
||||
}
|
||||
toggled={showSystemFields}
|
||||
text={t`System fields`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconArchive}
|
||||
onToggleChange={() => setShowInactive(!showInactive)}
|
||||
toggled={showInactive}
|
||||
text={t`Inactive`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
{isAdvancedModeEnabled && (
|
||||
<MenuItemToggle
|
||||
LeftIcon={IconSettings}
|
||||
onToggleChange={() =>
|
||||
setShowSystemFields(!showSystemFields)
|
||||
}
|
||||
toggled={showSystemFields}
|
||||
text={t`System fields`}
|
||||
toggleSize="small"
|
||||
/>
|
||||
)}
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</StyledSearchAndFilterContainer>
|
||||
</StyledSearchContainer>
|
||||
<Table>
|
||||
<TableRow
|
||||
gridTemplateColumns={OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type EnrichedObjectMetadataItem } from '@/object-metadata/types/EnrichedObjectMetadataItem';
|
||||
import { settingsObjectIndexesFamilyState } from '@/settings/data-model/object-details/states/settingsObjectIndexesFamilyState';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { SortableTableHeader } from '@/ui/layout/table/components/SortableTableHeader';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
@@ -15,7 +14,8 @@ import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { IconSearch, IconSquareKey } from 'twenty-ui/display';
|
||||
import { IconSquareKey } from 'twenty-ui/display';
|
||||
import { SearchInput } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type SettingsObjectIndexesTableItem } from '~/pages/settings/data-model/types/SettingsObjectIndexesTableItem';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
@@ -119,9 +119,7 @@ export const SettingsObjectIndexTable = ({
|
||||
return (
|
||||
<>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="object-index-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
<SearchInput
|
||||
placeholder={t`Search an index...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
|
||||
@@ -12,7 +12,6 @@ import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
|
||||
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
@@ -33,12 +32,11 @@ import {
|
||||
IconChevronRight,
|
||||
IconMail,
|
||||
IconReload,
|
||||
IconSearch,
|
||||
IconTrash,
|
||||
Status,
|
||||
TooltipDelay,
|
||||
} from 'twenty-ui/display';
|
||||
import { IconButton } from 'twenty-ui/input';
|
||||
import { IconButton, SearchInput } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { useQuery } from '@apollo/client/react';
|
||||
import { GetWorkspaceInvitationsDocument } from '~/generated-metadata/graphql';
|
||||
@@ -92,13 +90,6 @@ const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
input {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableRows = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
@@ -385,17 +376,11 @@ export const SettingsWorkspaceMembers = () => {
|
||||
description={t`Manage the members of your workspace here`}
|
||||
/>
|
||||
<StyledSearchContainer>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="workspace-members-search"
|
||||
value={searchFilter}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={t`Search a team member...`}
|
||||
fullWidth
|
||||
LeftIcon={IconSearch}
|
||||
sizeVariant="lg"
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<SearchInput
|
||||
value={searchFilter}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={t`Search a team member...`}
|
||||
/>
|
||||
</StyledSearchContainer>
|
||||
<StyledTableContainer hasMoreRows={hasNextPage}>
|
||||
<Table>
|
||||
|
||||
Reference in New Issue
Block a user