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
This commit is contained in:
+21
-3
@@ -17,9 +17,12 @@ import { Text } from '@tiptap/extension-text';
|
||||
import { Underline } from '@tiptap/extension-underline';
|
||||
import { Dropcursor, Placeholder, UndoRedo } from '@tiptap/extensions';
|
||||
import { type Editor, useEditor } from '@tiptap/react';
|
||||
import { marked } from 'marked';
|
||||
import { type DependencyList, useMemo } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export type AdvancedTextEditorContentType = 'json' | 'markdown';
|
||||
|
||||
type UseAdvancedTextEditorProps = {
|
||||
placeholder: string | undefined;
|
||||
readonly: boolean | undefined;
|
||||
@@ -30,6 +33,7 @@ type UseAdvancedTextEditorProps = {
|
||||
onImageUpload?: (file: File) => Promise<string>;
|
||||
onImageUploadError?: (error: Error, file: File) => void;
|
||||
enableSlashCommand?: boolean;
|
||||
contentType?: AdvancedTextEditorContentType;
|
||||
};
|
||||
|
||||
export const useAdvancedTextEditor = (
|
||||
@@ -43,9 +47,12 @@ export const useAdvancedTextEditor = (
|
||||
onImageUpload,
|
||||
onImageUploadError,
|
||||
enableSlashCommand,
|
||||
contentType = 'json',
|
||||
}: UseAdvancedTextEditorProps,
|
||||
dependencies?: DependencyList,
|
||||
) => {
|
||||
const isMarkdownMode = contentType === 'markdown';
|
||||
|
||||
const extensions = useMemo(
|
||||
() => [
|
||||
Document,
|
||||
@@ -87,12 +94,23 @@ export const useAdvancedTextEditor = (
|
||||
],
|
||||
);
|
||||
|
||||
const getEditorContent = () => {
|
||||
if (!isDefined(defaultValue)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (isMarkdownMode) {
|
||||
// Convert markdown to HTML, then TipTap will parse the HTML
|
||||
return marked.parse(defaultValue, { async: false }) as string;
|
||||
}
|
||||
|
||||
return getInitialAdvancedTextEditorContent(defaultValue);
|
||||
};
|
||||
|
||||
const editor = useEditor(
|
||||
{
|
||||
extensions,
|
||||
content: isDefined(defaultValue)
|
||||
? getInitialAdvancedTextEditorContent(defaultValue)
|
||||
: undefined,
|
||||
content: getEditorContent(),
|
||||
editable: !readonly,
|
||||
onUpdate: ({ editor }) => {
|
||||
onUpdate(editor);
|
||||
|
||||
Reference in New Issue
Block a user