Files
twenty/packages/twenty-front/src/pages/settings/ai/components/SettingsSkillInactiveMenuDropDown.tsx
T
Félix Malfait 21ff42074d feat: implement skills system for AI agents (#16865)
## Summary
This PR introduces a Skills system for AI agents, inspired by the [Agent
Skills specification](https://agentskills.io/specification).

## Changes

### Backend
- **SkillEntity**: New database entity with migration for storing skills
- **V2 Sync Mechanism**: Implemented FlatSkill, builders, validators,
and action handlers following the v2 flat entity pattern
- **Standard Skills**: Pre-defined skills (workflow-building,
data-manipulation, dashboard-building, metadata-building, research,
code-interpreter, xlsx, pdf, docx, pptx)
- **GraphQL API**: CRUD operations for skills with proper guards and
permissions
- **Workspace Cache**: Integrated skills into the workspace cache system

### Frontend  
- **Skills Table**: Searchable table in AI settings showing all skills
- **Skill Form**: Create/edit page with Label (primary), Description,
and Content (markdown editor)
- **API Name**: Following existing patterns, name is derived from label
with advanced settings toggle for custom API names
- **Standard vs Custom**: Standard skills are read-only, custom skills
can be edited/deleted

## Key Design Decisions
- Skills are stored in the database (Salesforce-like approach) rather
than files
- Name is derived from Label by default (isLabelSyncedWithName pattern)
- Skills reference functions/files via @ mentions in markdown content
rather than explicit relations
- Standard skills are synced from code, custom skills are created via UI

## Screenshots
Skills table and form UI follow existing settings patterns.

## Testing
- [x] Lint passes
- [x] Typecheck passes
- [ ] CI tests
2026-01-02 14:22:01 +00:00

70 lines
2.1 KiB
TypeScript

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 { GenericDropdownContentWidth } from '@/ui/layout/dropdown/constants/GenericDropdownContentWidth';
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
import { t } from '@lingui/core/macro';
import { IconArchiveOff, IconDotsVertical, IconTrash } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input';
import { MenuItem } from 'twenty-ui/navigation';
type SettingsSkillInactiveMenuDropDownProps = {
isCustomSkill: boolean;
onActivate: () => void;
onDelete: () => void;
skillId: string;
};
export const SettingsSkillInactiveMenuDropDown = ({
onActivate,
skillId,
onDelete,
isCustomSkill,
}: SettingsSkillInactiveMenuDropDownProps) => {
const dropdownId = `${skillId}-settings-skill-inactive-menu-dropdown`;
const { closeDropdown } = useCloseDropdown();
const handleActivate = () => {
onActivate();
closeDropdown(dropdownId);
};
const handleDelete = () => {
onDelete();
closeDropdown(dropdownId);
};
return (
<Dropdown
dropdownId={dropdownId}
clickableComponent={
<LightIconButton
aria-label={t`Inactive Skill Options`}
Icon={IconDotsVertical}
accent="tertiary"
/>
}
dropdownComponents={
<DropdownContent widthInPixels={GenericDropdownContentWidth.Narrow}>
<DropdownMenuItemsContainer>
<MenuItem
text={t`Activate`}
LeftIcon={IconArchiveOff}
onClick={handleActivate}
/>
{isCustomSkill && (
<MenuItem
text={t`Delete`}
LeftIcon={IconTrash}
accent="danger"
onClick={handleDelete}
/>
)}
</DropdownMenuItemsContainer>
</DropdownContent>
}
/>
);
};