21ff42074d
## 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
70 lines
2.1 KiB
TypeScript
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>
|
|
}
|
|
/>
|
|
);
|
|
};
|