diff --git a/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx index 409ab29c15..258650e049 100644 --- a/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/ar/developers/extend/capabilities/apps.mdx @@ -837,6 +837,255 @@ export default defineFrontComponent({ * **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة مكوّن أمامي جديد. * **يدوي**: أنشئ ملفًا جديدًا `.tsx` واستخدم `defineFrontComponent()` مع اتباع النمط نفسه. +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| الحقل | النوع | الوصف | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `التسمية` | `string` (required) | Display label shown in the command menu | +| `أيقونة` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | الوصف | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| دالة | Signature | الوصف | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `التنقل` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### المهارات تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج: diff --git a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index a713b70424..08ff346423 100644 --- a/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/de/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -77,7 +77,7 @@ To avoid unnecessary [re-renders](/l/de/developers/contribute/capabilities/front ### Zustandsverwaltung -[Jotai](https://jotai.org/) handles state management. +[Jotai](https://jotai.org/) übernimmt die Zustandsverwaltung. Siehe [Best Practices](/l/de/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) für mehr Informationen zur Zustandsverwaltung. diff --git a/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx index 9fa92f19f7..69dae71045 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/getting-started.mdx @@ -1,20 +1,20 @@ --- title: Erste Schritte -description: Create your first Twenty app in minutes. +description: Erstellen Sie in wenigen Minuten Ihre erste Twenty-App. --- Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfähig, entwickelt sich jedoch noch weiter. -Apps let you extend Twenty with custom objects, fields, logic functions, AI skills, and UI components — all managed as code. +Apps ermöglichen es Ihnen, Twenty mit benutzerdefinierten Objekten, Feldern, Logikfunktionen, KI-Fähigkeiten und UI-Komponenten zu erweitern — alles als Code verwaltet. **Was Sie heute tun können:** * Benutzerdefinierte Objekte und Felder als Code definieren (verwaltetes Datenmodell) -* Build logic functions with custom triggers (HTTP routes, cron, database events) +* Erstellen Sie Logikfunktionen mit benutzerdefinierten Triggern (HTTP-Routen, cron, Datenbankereignisse) * Fähigkeiten für KI-Agenten definieren -* Build front components that render inside Twenty's UI +* Erstellen Sie Frontend-Komponenten, die in der Twenty-UI gerendert werden * Dieselbe App in mehreren Workspaces bereitstellen ## Voraussetzungen diff --git a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx index d9f0634aff..221bb8b0fe 100644 --- a/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx +++ b/packages/twenty-docs/l/de/developers/extend/apps/publishing.mdx @@ -1,6 +1,6 @@ --- -title: Publishing -description: Distribute your Twenty app to the marketplace or deploy it internally. +title: Veröffentlichen +description: Veröffentlichen Sie Ihre Twenty-App auf dem Twenty-Marktplatz oder stellen Sie sie intern bereit. --- @@ -9,41 +9,41 @@ Apps befinden sich derzeit in der Alpha-Testphase. Die Funktion ist funktionsfä ## Übersicht -Once your app is [built and tested locally](/l/de/developers/extend/apps/building), you have two paths for distributing it: +Sobald Ihre App [lokal gebaut und getestet](/l/de/developers/extend/apps/building) wurde, haben Sie zwei Möglichkeiten, sie zu verteilen: -* **Publish to npm** — list your app in the Twenty marketplace for any workspace to discover and install. -* **Push a tarball** — deploy your app to a specific Twenty server for internal use without making it publicly available. +* **Auf npm veröffentlichen** — führen Sie Ihre App im Twenty-Marktplatz auf, damit jeder Arbeitsbereich sie entdecken und installieren kann. +* **Einen Tarball pushen** — stellen Sie Ihre App auf einem bestimmten Twenty-Server für die interne Nutzung bereit, ohne sie öffentlich verfügbar zu machen. -## Publishing to npm +## Auf npm veröffentlichen -Publishing to npm makes your app discoverable in the Twenty marketplace. Any Twenty workspace can browse, install, and upgrade marketplace apps directly from the UI. +Die Veröffentlichung auf npm macht Ihre App im Twenty-Marktplatz auffindbar. Jeder Twenty-Arbeitsbereich kann Marktplatz-Apps direkt über die Benutzeroberfläche durchsuchen, installieren und aktualisieren. -### Requirements +### Anforderungen -* An [npm](https://www.npmjs.com) account -* Your package name **must** use the `twenty-app-` prefix (e.g., `twenty-app-postcard-sender`) +* Ein [npm](https://www.npmjs.com)-Konto +* Ihr Paketname **muss** das Präfix `twenty-app-` verwenden (z. B. `twenty-app-postcard-sender`) ### Schritte -1. **Build your app** — the CLI compiles your TypeScript sources and generates the application manifest: +1. **App erstellen** — die CLI kompiliert Ihre TypeScript-Quellen und erzeugt das Anwendungsmanifest: ```bash filename="Terminal" yarn twenty app:build ``` -2. **Publish to npm** — push the built package to the npm registry: +2. **Auf npm veröffentlichen** — pushen Sie das gebaute Paket in die npm-Registry: ```bash filename="Terminal" npx twenty app:publish ``` -### Auto-discovery +### Automatische Erkennung -Packages with the `twenty-app-` prefix are automatically discovered by the Twenty marketplace catalog. Once published, your app appears in the marketplace within a few minutes — no manual registration or approval required. +Pakete mit dem Präfix `twenty-app-` werden vom Twenty-Marktplatzkatalog automatisch erkannt. Nach der Veröffentlichung erscheint Ihre App innerhalb weniger Minuten im Marktplatz — keine manuelle Registrierung oder Genehmigung erforderlich. -### CI publishing +### CI-Veröffentlichung -The scaffolded project includes a GitHub Actions workflow that publishes on every release. It runs `app:build`, then `npm publish --provenance` from the build output: +Das vorgefertigte Projekt enthält einen GitHub-Actions-Workflow, der bei jedem Release eine Veröffentlichung durchführt. Er führt `app:build` aus und danach `npm publish --provenance` aus dem Build-Output: ```yaml filename=".github/workflows/publish.yml" name: Publish @@ -72,48 +72,48 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} ``` -For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `npx twenty app:build`, then `npm publish` from `.twenty/output`. +Für andere CI-Systeme (GitLab CI, CircleCI usw.) gelten die gleichen drei Befehle: `yarn install`, `npx twenty app:build` und anschließend `npm publish` aus `.twenty/output`. -**npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions. +**npm-Provenance** ist optional, wird jedoch empfohlen. Das Veröffentlichen mit `--provenance` fügt Ihrem npm-Eintrag ein Vertrauensabzeichen hinzu, sodass Nutzer überprüfen können, dass das Paket aus einem bestimmten Commit in einer öffentlichen CI-Pipeline gebaut wurde. Siehe die [npm-Provenance-Dokumentation](https://docs.npmjs.com/generating-provenance-statements) für Einrichtungshinweise. -## Internal distribution +## Interne Verteilung -For apps you don't want publicly available — proprietary tools, enterprise-only integrations, or experimental builds — you can push a tarball directly to a Twenty server. +Für Apps, die Sie nicht öffentlich verfügbar machen möchten — proprietäre Tools, nur für Unternehmen bestimmte Integrationen oder experimentelle Builds — können Sie einen Tarball direkt auf einen Twenty-Server pushen. -### Push a tarball +### Einen Tarball pushen -Build your app and deploy it to a specific server in one step: +Erstellen Sie Ihre App und stellen Sie sie in einem Schritt auf einem bestimmten Server bereit: ```bash filename="Terminal" npx twenty app:publish --server ``` -Any workspace on that server can then install and upgrade the app from the **Applications** settings page. +Jeder Arbeitsbereich auf diesem Server kann die App anschließend über die Seite **Applications** in den Einstellungen installieren und aktualisieren. -### Version management +### Versionsverwaltung -To release an update: +So veröffentlichen Sie ein Update: -1. Bump the `version` field in your `package.json` -2. Push a new tarball with `npx twenty app:publish --server ` -3. Workspaces on that server will see the upgrade available in their settings +1. Erhöhen Sie das Feld `version` in Ihrer `package.json` +2. Pushen Sie einen neuen Tarball mit `npx twenty app:publish --server ` +3. Arbeitsbereiche auf diesem Server sehen in ihren Einstellungen, dass ein Upgrade verfügbar ist. -Internal apps are scoped to the server they're pushed to. They won't appear in the public marketplace and can't be installed by workspaces on other servers. +Interne Apps sind auf den Server beschränkt, auf den sie gepusht werden. Sie erscheinen nicht im öffentlichen Marktplatz und können von Arbeitsbereichen auf anderen Servern nicht installiert werden. -## App categories +## App-Kategorien -Twenty organizes apps into three categories based on how they're distributed: +Twenty organisiert Apps in drei Kategorien, basierend auf ihrer Vertriebsart: -| Kategorie | Wie es funktioniert | Visible in marketplace? | -| --------------- | ------------------------------------------------------------------------------------------------------------ | ----------------------- | -| **Entwicklung** | Local dev mode apps running via `yarn twenty app:dev`. Used for building and testing. | Nein | -| **Published** | Apps published to npm with the `twenty-app-` prefix. Listed in the marketplace for any workspace to install. | Ja | -| **Internal** | Apps deployed via tarball to a specific server. Available only to workspaces on that server. | Nein | +| Kategorie | Wie es funktioniert | Im Marktplatz sichtbar? | +| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | +| **Entwicklung** | Lokale Apps im Entwicklungsmodus, die über `yarn twenty app:dev` ausgeführt werden. Zum Erstellen und Testen verwendet. | Nein | +| **Veröffentlicht** | Auf npm veröffentlichte Apps mit dem Präfix `twenty-app-`. Im Marktplatz gelistet, damit jeder Arbeitsbereich sie installieren kann. | Ja | +| **Intern** | Apps, die per Tarball auf einen bestimmten Server bereitgestellt werden. Nur für Arbeitsbereiche auf diesem Server verfügbar. | Nein | -Start in **Development** mode while building your app. When it's ready, choose **Published** (npm) for broad distribution or **Internal** (tarball) for private deployment. +Beginnen Sie im **Entwicklungsmodus**, während Sie Ihre App erstellen. Wenn sie bereit ist, wählen Sie **Veröffentlicht** (npm) für die breite Verteilung oder **Intern** (Tarball) für die private Bereitstellung. diff --git a/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx index f2470441a7..6645bae140 100644 --- a/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/de/developers/extend/capabilities/apps.mdx @@ -321,11 +321,11 @@ Sie können Standardfelder überschreiben, indem Sie in Ihrem `fields`-Array ein dies wird jedoch nicht empfohlen. -### Defining fields on existing objects +### Felder für bestehende Objekte definieren -Use `defineField()` to add custom fields to existing objects — both standard objects (like `company`, `person`, `opportunity`) and custom objects defined by other apps. Each field lives in its own file and references the target object by its `universalIdentifier`. +Verwenden Sie `defineField()`, um benutzerdefinierte Felder zu bestehenden Objekten hinzuzufügen — sowohl zu Standardobjekten (wie `company`, `person`, `opportunity`) als auch zu benutzerdefinierten Objekten, die von anderen Apps definiert werden. Jedes Feld befindet sich in einer eigenen Datei und verweist auf das Zielobjekt über dessen `universalIdentifier`. -To reference standard objects, import `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` from `twenty-sdk`. This constant provides stable identifiers for all built-in objects and their fields: +Um auf Standardobjekte zu verweisen, importieren Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` aus `twenty-sdk`. Diese Konstante stellt stabile Bezeichner für alle integrierten Objekte und deren Felder bereit: ```typescript // src/fields/apollo-total-funding.field.ts @@ -349,22 +349,22 @@ export default defineField({ Hauptpunkte: -* `objectUniversalIdentifier` tells Twenty which object to attach the field to. Use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` for standard objects. -* Each field requires its own stable `universalIdentifier`, a `name`, `type`, `label`, and the target `objectUniversalIdentifier`. -* You can scaffold new fields using `yarn twenty entity:add` and choosing the field option. -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` is also exported as `STANDARD_OBJECT` for convenience — both refer to the same constant. +* `objectUniversalIdentifier` teilt Twenty mit, an welches Objekt das Feld angehängt werden soll. Verwenden Sie `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` für Standardobjekte. +* Jedes Feld benötigt einen eigenen stabilen `universalIdentifier`, `name`, `type`, `label` und den Ziel-`objectUniversalIdentifier`. +* Sie können mit `yarn twenty entity:add` neue Felder anlegen, indem Sie die Feldoption wählen. +* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` wird der Einfachheit halber auch als `STANDARD_OBJECT` exportiert — beide verweisen auf dieselbe Konstante. -Available standard objects include: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion`, and `workspaceMember`. +Verfügbare Standardobjekte sind unter anderem: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion` und `workspaceMember`. -Each standard object also exposes its field identifiers. For example, to reference a specific field on a standard object in role permissions: +Jedes Standardobjekt stellt außerdem seine Feldbezeichner bereit. Beispielsweise, um in Rollenberechtigungen auf ein bestimmtes Feld eines Standardobjekts zu verweisen: ```typescript STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier ``` -#### Relation fields on existing objects +#### Beziehungsfelder bei bestehenden Objekten -You can also define relation fields that link existing objects to your custom objects: +Sie können auch Beziehungsfelder definieren, die bestehende Objekte mit Ihren benutzerdefinierten Objekten verknüpfen: ```typescript // src/fields/people-on-call-recording.field.ts @@ -837,6 +837,255 @@ Sie können neue Frontend-Komponenten auf zwei Arten erstellen: * **Generiert**: Führen Sie `yarn twenty entity:add` aus und wählen Sie die Option zum Hinzufügen einer neuen Frontend-Komponente. * **Manuell**: Erstellen Sie eine neue `.tsx`-Datei und verwenden Sie `defineFrontComponent()` nach demselben Muster. +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| Feld | Typ | Beschreibung | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `beschriftung` | `string` (required) | Display label shown in the command menu | +| `symbol` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | Beschreibung | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| Funktion | Signature | Beschreibung | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `navigieren` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### Fähigkeiten Skills definieren wiederverwendbare Anweisungen und Fähigkeiten, die KI-Agenten in Ihrem Arbeitsbereich verwenden können. Verwenden Sie `defineSkill()`, um Skills mit eingebauter Validierung zu definieren: @@ -974,12 +1223,12 @@ uploadFile( ): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> ``` -| Parameter | Typ | Beschreibung | -| ---------------------------------- | -------- | ------------------------------------------------------------------------------- | -| `fileBuffer` | `Buffer` | Der Rohinhalt der Datei | -| `filename` | `string` | Der Name der Datei (wird für Speicherung und Anzeige verwendet) | -| `contentType` | `string` | MIME-Typ der Datei (standardmäßig `application/octet-stream`, wenn weggelassen) | -| `fieldMetadataUniversalIdentifier` | `string` | Der `universalIdentifier` des Dateityp-Felds in Ihrem Objekt | +| Parameter | Typ | Beschreibung | +| ---------------------------------- | -------------- | ------------------------------------------------------------------------------- | +| `fileBuffer` | `Buffer` | Der Rohinhalt der Datei | +| `filename` | `string` | Der Name der Datei (wird für Speicherung und Anzeige verwendet) | +| `contentType` | `string` | MIME-Typ der Datei (standardmäßig `application/octet-stream`, wenn weggelassen) | +| `fieldMetadataUniversalIdentifier` | `Zeichenkette` | Der `universalIdentifier` des Dateityp-Felds in Ihrem Objekt | Hauptpunkte: diff --git a/packages/twenty-docs/l/de/developers/extend/extend.mdx b/packages/twenty-docs/l/de/developers/extend/extend.mdx index f38eb0aabe..d12db12273 100644 --- a/packages/twenty-docs/l/de/developers/extend/extend.mdx +++ b/packages/twenty-docs/l/de/developers/extend/extend.mdx @@ -15,7 +15,7 @@ Twenty ist darauf ausgelegt, erweiterbar zu sein. Verwenden Sie unsere APIs, Web * **APIs**: Abfragen und ändern Sie Ihre CRM-Daten programmatisch mit REST oder GraphQL * **Webhooks**: Erhalten Sie Benachrichtigungen in Echtzeit, wenn Ereignisse in Twenty auftreten -* **Apps**: Build custom applications that extend Twenty's capabilities +* **Apps**: Erstellen Sie benutzerdefinierte Anwendungen, die die Funktionalität von Twenty erweitern ## Erste Schritte @@ -27,6 +27,6 @@ Twenty ist darauf ausgelegt, erweiterbar zu sein. Verwenden Sie unsere APIs, Web Erhalten Sie Benachrichtigungen über Ereignisse in Echtzeit - Build customizations as code + Erstellen Sie Anpassungen als Code diff --git a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index 30fb17f7be..77ea6146b3 100644 --- a/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/it/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -77,7 +77,7 @@ Per evitare [re-render](/l/it/developers/contribute/capabilities/frontend-develo ### Gestione dello stato -[Jotai](https://jotai.org/) handles state management. +[Jotai](https://jotai.org/) gestisce lo stato. Vedi [best practices](/l/it/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) per ulteriori informazioni sulla gestione dello stato. diff --git a/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx index dcb7e5b82f..63c4f35caf 100644 --- a/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/it/developers/extend/capabilities/apps.mdx @@ -837,6 +837,255 @@ Puoi creare nuovi componenti front-end in due modi: * **Generata dallo scaffolder**: Esegui `yarn twenty entity:add` e scegli l'opzione per aggiungere un nuovo componente front-end. * **Manuale**: Crea un nuovo file `.tsx` e usa `defineFrontComponent()`, seguendo lo stesso schema. +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| Campo | Tipo | Descrizione | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `etichetta` | `string` (required) | Display label shown in the command menu | +| `icona` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | Descrizione | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| Funzione | Signature | Descrizione | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `naviga` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### Abilità Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation: diff --git a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index 9743a94c47..31c274ae2b 100644 --- a/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/pt/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -77,7 +77,7 @@ To avoid unnecessary [re-renders](/l/pt/developers/contribute/capabilities/front ### Gerenciamento de Estado -[Jotai](https://jotai.org/) handles state management. +[Jotai](https://jotai.org/) gerencia o estado. Veja [melhores práticas](/l/pt/developers/contribute/capabilities/frontend-development/best-practices-front#state-management) para mais informações sobre gerenciamento de estado. diff --git a/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx index 03244d1f76..ca1cec8a00 100644 --- a/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/pt/developers/extend/capabilities/apps.mdx @@ -838,6 +838,255 @@ Você pode criar novos componentes de front-end de duas formas: * **Gerado automaticamente**: Execute `yarn twenty entity:add` e escolha a opção para adicionar um novo componente de front-end. * **Manual**: Crie um novo ficheiro `.tsx` e use `defineFrontComponent()`, seguindo o mesmo padrão. +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| Campo | Tipo | Descrição | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `etiqueta` | `string` (required) | Display label shown in the command menu | +| `ícone` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | Descrição | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| Função | Signature | Descrição | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `navegar` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### Habilidades As habilidades definem instruções e capacidades reutilizáveis que os agentes de IA podem usar no seu espaço de trabalho. Use `defineSkill()` para definir habilidades com validação integrada: diff --git a/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx index 1a3bfb583d..4b4c18f577 100644 --- a/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/ro/developers/extend/capabilities/apps.mdx @@ -837,6 +837,255 @@ Puteți crea componente Front noi în două moduri: * **Generat**: Rulați `yarn twenty entity:add` și alegeți opțiunea de a adăuga o componentă frontend nouă. * **Manual**: Creați un fișier nou `.tsx` și folosiți `defineFrontComponent()`, urmând același model. +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| Câmp | Tip | Descriere | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `etichetă` | `string` (required) | Display label shown in the command menu | +| `pictogramă` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | Descriere | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| Funcție | Signature | Descriere | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `navigate` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### Abilități Abilitățile definesc instrucțiuni și capabilități reutilizabile pe care agenții AI le pot folosi în spațiul dvs. de lucru. Folosiți `defineSkill()` pentru a defini abilități cu validare încorporată: diff --git a/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx index c0685379f8..282b91f07d 100644 --- a/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/ru/developers/extend/capabilities/apps.mdx @@ -837,6 +837,255 @@ export default defineFrontComponent({ * **Сгенерировано**: Запустите `yarn twenty entity:add` и выберите опцию добавления нового фронтенд-компонента. * **Вручную**: Создайте новый файл `.tsx` и используйте `defineFrontComponent()`, следуя тому же шаблону. +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| Поле | Тип | Описание | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `метка` | `string` (required) | Display label shown in the command menu | +| `иконка` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | Описание | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `строка` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| Функция | Signature | Описание | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `навигация` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### Навыки Навыки определяют многократно используемые инструкции и возможности, которые агенты ИИ могут использовать в вашем рабочем пространстве. Используйте `defineSkill()` для определения навыков со встроенной валидацией: diff --git a/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx index 096d5d075e..6950093da2 100644 --- a/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/tr/developers/extend/capabilities/apps.mdx @@ -837,6 +837,255 @@ Yeni ön uç bileşenlerini iki şekilde oluşturabilirsiniz: * **Şablondan**: `yarn twenty entity:add` çalıştırın ve yeni bir ön uç bileşeni ekleme seçeneğini seçin. * **Manuel**: Aynı deseni izleyerek yeni bir `.tsx` dosyası oluşturun ve `defineFrontComponent()` kullanın. +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| Alan | Tür | Açıklama | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `etiket` | `string` (required) | Display label shown in the command menu | +| `simge` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | Açıklama | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| Fonksiyon | Signature | Açıklama | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `gezin` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### Beceriler Yetenekler, yapay zekâ ajanlarının çalışma alanınızda kullanabileceği yeniden kullanılabilir yönergeleri ve kabiliyetleri tanımlar. Yerleşik doğrulamayla yetenekleri tanımlamak için `defineSkill()` kullanın: diff --git a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx index dd623139d4..d642d5d880 100644 --- a/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx +++ b/packages/twenty-docs/l/zh/developers/contribute/capabilities/frontend-development/frontend-commands.mdx @@ -77,7 +77,7 @@ To avoid unnecessary [re-renders](/l/zh/developers/contribute/capabilities/front ### 状态管理 -[Jotai](https://jotai.org/) handles state management. +[Jotai](https://jotai.org/) 处理状态管理。 查看[最佳实践](/l/zh/developers/contribute/capabilities/frontend-development/best-practices-front#state-management)以获取有关状态管理的更多信息。 diff --git a/packages/twenty-docs/l/zh/developers/extend/api.mdx b/packages/twenty-docs/l/zh/developers/extend/api.mdx new file mode 100644 index 0000000000..3ab6804392 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/api.mdx @@ -0,0 +1,147 @@ +--- +title: 接口 +description: 使用 REST 或 GraphQL 以编程方式查询和修改您的客户关系管理数据。 +--- + +import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; + +Twenty 的设计对开发者友好,提供适配您自定义数据模型的强大 API。 我们提供四种不同的 API 类型来满足不同的集成需求。 + +## 开发者优先的方法 + +Twenty 会针对您的数据模型生成专用 API: + +* **无需长 ID**:直接在端点中使用对象和字段名称 +* **标准与自定义对象平等对待**:您的自定义对象将享有与内置对象相同的 API 支持 +* **专用端点**:每个对象和字段都有自己的 API 端点 +* **自定义文档**:专门为您的工作区的数据模型生成 + + +创建 API 密钥后,可在 **设置 → API & Webhooks** 中查看您的个性化 API 文档。 由于 Twenty 会生成与您的自定义数据模型相匹配的 API,因此文档对您的工作区是唯一的。 + + +## 两种 API 类型 + +### 核心 API + +访问路径:`/rest/`或`/graphql/`。 + +处理您实际的**记录**(数据): + +* 创建、读取、更新、删除 People、Companies、Opportunities 等。 +* 查询并筛选数据 +* 管理记录关系 + +### 元数据 API + +访问路径:`/rest/metadata/`或`/metadata/`。 + +管理您的**工作区和数据模型**: + +* 创建、修改或删除对象和字段 +* 配置工作区设置 +* 定义对象之间的关系 + +## REST 与 GraphQL + +核心 API 和元数据 API 均提供 REST 和 GraphQL 格式: + +| 格式 | 可用操作 | +| ----------- | ------------------------------- | +| **REST** | CRUD、批量操作、Upsert | +| **GraphQL** | 同上 + **批量 Upsert**,在一次调用中进行关系查询 | + +可根据需要选择 — 两种格式访问的是同一份数据。 + +## API 端点 + +| 环境 | 基础 URL | +| ------- | ------------------------- | +| **云端** | `https://api.twenty.com/` | +| **自托管** | `https://{your-domain}/` | + +## 身份验证 + +每个 API 请求都需要在请求头中包含 API 密钥: + +``` +Authorization: Bearer YOUR_API_KEY +``` + +### 创建 API 密钥 + +1. 前往 **设置 → APIs & Webhooks** +2. 点击 **+ 创建密钥** +3. 配置: + * **名称**:密钥的描述性名称 + * **到期日期**:密钥的到期时间 +4. 单击 **保存** +5. **立即复制** — 密钥仅显示一次 + + + + +您的 API 密钥可访问敏感数据。 不要与不受信任的服务共享它。 如果遭到泄露,请立即将其禁用并生成一个新的。 + + +### 为 API 密钥分配角色 + +为提高安全性,请分配特定角色以限制访问: + +1. 进入 **设置 → 角色** +2. 点击要分配的角色 +3. 打开 **分配** 选项卡 +4. 在 **API Keys** 下,点击 **+ Assign to API key** +5. 选择该 API 密钥 + +该密钥将继承该角色的权限。 详见 [权限](/l/zh/user-guide/permissions-access/capabilities/permissions)。 + +### 管理 API 密钥 + +**Regenerate**: 设置 → APIs & Webhooks → 点击密钥 → **Regenerate** + +**Delete**: 设置 → APIs & Webhooks → 点击密钥 → **Delete** + +## API 操作台 + +使用我们内置的操作台,可直接在浏览器中测试您的 API — 同时支持 **REST** 和 **GraphQL**。 + +### 访问操作台 + +1. 前往 **设置 → APIs & Webhooks** +2. 创建 API 密钥(必需) +3. 点击 **REST API** 或 **GraphQL API** 打开操作台 + +### 您将获得 + +* **交互式文档**:针对您的特定数据模型生成 +* **实时测试**:对您的工作区执行真实的 API 调用 +* **架构浏览器**:浏览可用的对象、字段和关系 +* **请求构建器**:使用自动补全构建查询 + +操作台会反映您的自定义对象和字段,因此文档始终与您的工作区保持一致且准确。 + +## 批量操作 + +REST 和 GraphQL 均支持批量操作: + +* **批量大小**:每个请求最多 60 条记录 +* **操作**:创建、更新、删除多条记录 + +**仅 GraphQL 功能:** + +* **批量 Upsert**:在一次调用中创建或更新 +* 使用复数对象名称(例如,用 `CreateCompanies` 而不是 `CreateCompany`) + +## 速率限制 + +为确保平台稳定性,API 请求将受到限流: + +| 限制 | 值 | +| -------- | ----------- | +| **请求** | 每分钟 100 次调用 | +| **批量大小** | 每次调用 60 条记录 | + + +使用批量操作以最大化吞吐量 — 在一次 API 调用中处理最多 60 条记录,而不是发起单独的请求。 + diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx new file mode 100644 index 0000000000..8f84ee3b3e --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/building.mdx @@ -0,0 +1,689 @@ +--- +title: 构建应用 +description: 使用 Twenty SDK 定义对象、逻辑函数、前端组件等。 +--- + + +应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。 + + +## 使用 SDK 资源(类型与配置) + +twenty-sdk 提供你在应用中使用的类型化构件和辅助函数。 以下是你最常接触的关键部分。 + +### 辅助函数 + +该 SDK 提供辅助函数用于定义你的应用实体。 如 [实体检测](/l/zh/developers/extend/apps/getting-started#entity-detection) 中所述,你必须使用 `export default define({...})` 才能让你的实体被检测到: + +| 函数 | 目的 | +| -------------------------------- | ------------------- | +| `defineApplication` | 配置应用元数据(必需,每个应用一个) | +| `defineObject` | 定义带字段的自定义对象 | +| `defineLogicFunction` | 定义带处理程序的逻辑函数 | +| `definePreInstallLogicFunction` | 定义一个安装前逻辑函数(每个应用一个) | +| `definePostInstallLogicFunction` | 定义一个安装后逻辑函数(每个应用一个) | +| `defineFrontComponent` | 为自定义 UI 定义前端组件 | +| `defineRole` | 配置角色权限和对象访问 | +| `defineField` | 为现有对象扩展额外字段 | +| `defineView` | 为对象定义已保存的视图 | +| `defineNavigationMenuItem` | 定义侧边栏导航链接 | +| `defineSkill` | 定义 AI 智能体技能 | + +这些函数会在构建时校验你的配置,并提供 IDE 自动补全和类型安全。 + +### 定义对象 + +自定义对象同时描述工作空间中记录的架构与行为。 使用 `defineObject()` 以内置校验定义对象: + +```typescript +// src/app/postCard.object.ts +import { defineObject, FieldType } from 'twenty-sdk'; + +enum PostCardStatus { + DRAFT = 'DRAFT', + SENT = 'SENT', + DELIVERED = 'DELIVERED', + RETURNED = 'RETURNED', +} + +export default defineObject({ + universalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', + nameSingular: 'postCard', + namePlural: 'postCards', + labelSingular: 'Post Card', + labelPlural: 'Post Cards', + description: 'A post card object', + icon: 'IconMail', + fields: [ + { + universalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', + name: 'content', + type: FieldType.TEXT, + label: 'Content', + description: "Postcard's content", + icon: 'IconAbc', + }, + { + universalIdentifier: 'c6aa31f3-da76-4ac6-889f-475e226009ac', + name: 'recipientName', + type: FieldType.FULL_NAME, + label: 'Recipient name', + icon: 'IconUser', + }, + { + universalIdentifier: '95045777-a0ad-49ec-98f9-22f9fc0c8266', + name: 'recipientAddress', + type: FieldType.ADDRESS, + label: 'Recipient address', + icon: 'IconHome', + }, + { + universalIdentifier: '87b675b8-dd8c-4448-b4ca-20e5a2234a1e', + name: 'status', + type: FieldType.SELECT, + label: 'Status', + icon: 'IconSend', + defaultValue: `'${PostCardStatus.DRAFT}'`, + options: [ + { value: PostCardStatus.DRAFT, label: 'Draft', position: 0, color: 'gray' }, + { value: PostCardStatus.SENT, label: 'Sent', position: 1, color: 'orange' }, + { value: PostCardStatus.DELIVERED, label: 'Delivered', position: 2, color: 'green' }, + { value: PostCardStatus.RETURNED, label: 'Returned', position: 3, color: 'orange' }, + ], + }, + { + universalIdentifier: 'e06abe72-5b44-4e7f-93be-afc185a3c433', + name: 'deliveredAt', + type: FieldType.DATE_TIME, + label: 'Delivered at', + icon: 'IconCheck', + isNullable: true, + defaultValue: null, + }, + ], +}); +``` + +关键点: + +* 使用 `defineObject()` 以获得内置校验和更好的 IDE 支持。 +* `universalIdentifier` 必须在各次部署间保持唯一且稳定。 +* 每个字段都需要 `name`、`type`、`label` 以及其自身稳定的 `universalIdentifier`。 +* `fields` 数组是可选的——你可以定义没有自定义字段的对象。 +* 你可以使用 `yarn twenty entity:add` 脚手架创建新对象,它会引导你完成命名、字段和关系。 + + +**基础字段会自动创建。** 当你定义自定义对象时,Twenty 会自动添加标准字段 +例如 `id`、`name`、`createdAt`、`updatedAt`、`createdBy`、`updatedBy` 和 `deletedAt`。 +你无需在 `fields` 数组中定义这些字段——只需添加你的自定义字段。 +你可以通过在你的 `fields` 数组中定义一个同名字段来覆盖默认字段, +但不建议这样做。 + + +### 应用配置(application-config.ts) + +每个应用都有一个 `application-config.ts` 文件,用于描述: + +* **应用的身份**:标识符、显示名称和描述。 +* **函数如何运行**:它们用于权限的角色。 +* **(可选)变量**:以环境变量形式提供给函数的键值对。 +* **(可选)安装前函数**:在应用安装之前运行的逻辑函数。 +* **(可选)安装后函数**:在应用安装后运行的逻辑函数。 + +使用 `defineApplication()` 定义你的应用配置: + +```typescript +// src/application-config.ts +import { defineApplication } from 'twenty-sdk'; +import { DEFAULT_ROLE_UNIVERSAL_IDENTIFIER } from 'src/roles/default-role'; + +export default defineApplication({ + universalIdentifier: '4ec0391d-18d5-411c-b2f3-266ddc1c3ef7', + displayName: 'My Twenty App', + description: 'My first Twenty app', + icon: 'IconWorld', + applicationVariables: { + DEFAULT_RECIPIENT_NAME: { + universalIdentifier: '19e94e59-d4fe-4251-8981-b96d0a9f74de', + description: 'Default recipient name for postcards', + value: 'Jane Doe', + isSecret: false, + }, + }, + defaultRoleUniversalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, +}); +``` + +备注: + +* `universalIdentifier` 字段是你拥有的确定性 ID;生成一次并在多次同步中保持稳定。 +* `applicationVariables` 会变成函数可用的环境变量(例如,`DEFAULT_RECIPIENT_NAME` 可作为 `process.env.DEFAULT_RECIPIENT_NAME` 使用)。 +* `defaultRoleUniversalIdentifier` 必须与角色文件一致(见下文)。 +* 清单构建期间会自动检测安装前和安装后函数。 参见 [安装前函数](#pre-install-functions) 和 [安装后函数](#post-install-functions)。 + +#### 角色和权限 + +应用可以定义角色,以封装对工作空间对象与操作的权限。 `application-config.ts` 中的 `defaultRoleUniversalIdentifier` 字段指定你的应用逻辑函数所使用的默认角色。 + +* 作为 `TWENTY_API_KEY` 注入的运行时 API 密钥源自该默认函数角色。 +* 类型化客户端将受限于该角色授予的权限。 +* 遵循最小权限原则:仅授予函数所需权限来创建一个专用角色,然后引用其通用标识符。 + +##### 默认函数角色(*.role.ts) + +当你脚手架生成新应用时,CLI 也会创建一个默认角色文件。 使用 `defineRole()` 定义带内置校验的角色: + +```typescript +// src/roles/default-role.ts +import { defineRole, PermissionFlag } from 'twenty-sdk'; + +export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = + 'b648f87b-1d26-4961-b974-0908fd991061'; + +export default defineRole({ + universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER, + label: 'Default function role', + description: 'Default role for function Twenty client', + canReadAllObjectRecords: false, + canUpdateAllObjectRecords: false, + canSoftDeleteAllObjectRecords: false, + canDestroyAllObjectRecords: false, + canUpdateAllSettings: false, + canBeAssignedToAgents: false, + canBeAssignedToUsers: false, + canBeAssignedToApiKeys: false, + objectPermissions: [ + { + objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', + canReadObjectRecords: true, + canUpdateObjectRecords: true, + canSoftDeleteObjectRecords: false, + canDestroyObjectRecords: false, + }, + ], + fieldPermissions: [ + { + objectUniversalIdentifier: '9f9882af-170c-4879-b013-f9628b77c050', + fieldUniversalIdentifier: 'b2c37dc0-8ae7-470e-96cd-1476b47dfaff', + canReadFieldValue: false, + canUpdateFieldValue: false, + }, + ], + permissionFlags: [PermissionFlag.APPLICATIONS], +}); +``` + +随后,该角色的 `universalIdentifier` 会在 `application-config.ts` 中被引用为 `defaultRoleUniversalIdentifier`。 换句话说: + +* **\*.role.ts** 定义默认函数角色可以执行的操作。 +* **application-config.ts** 指向该角色,使你的函数继承其权限。 + +备注: + +* 从脚手架生成的角色开始,然后按照最小权限原则逐步收紧权限。 +* 将 `objectPermissions` 和 `fieldPermissions` 替换为你的函数所需的对象/字段。 +* `permissionFlags` 控制对平台级能力的访问。 尽量保持最小化;仅添加所需项。 +* 在 Hello World 应用中查看可运行示例:[`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts)。 + +### 逻辑函数的配置与入口点 + +每个函数文件都使用 `defineLogicFunction()` 导出包含处理程序和可选触发器的配置。 + +```typescript +// src/app/createPostCard.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk'; +import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk'; +import { CoreApiClient, type Person } from 'twenty-sdk/generated'; + +const handler = async (params: RoutePayload) => { + const client = new CoreApiClient(); + const name = 'name' in params.queryStringParameters + ? params.queryStringParameters.name ?? process.env.DEFAULT_RECIPIENT_NAME ?? 'Hello world' + : 'Hello world'; + + const result = await client.mutation({ + createPostCard: { + __args: { data: { name } }, + id: true, + name: true, + }, + }); + return result; +}; + +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'create-new-post-card', + timeoutSeconds: 2, + handler, + triggers: [ + // Public HTTP route trigger '/s/post-card/create' + { + universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', + type: 'route', + path: '/post-card/create', + httpMethod: 'GET', + isAuthRequired: false, + }, + // Cron trigger (CRON pattern) + // { + // universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2', + // type: 'cron', + // pattern: '0 0 1 1 *', + // }, + // Database event trigger + // { + // universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156', + // type: 'databaseEvent', + // eventName: 'person.updated', + // updatedFields: ['name'], + // }, + ], +}); +``` + +常见触发器类型: + +* **route**:在\*\*`/s/` 端点\*\*下通过 HTTP 路径与方法公开你的函数: + +> 例如 `path: '/post-card/create',` -> 调用 `/s/post-card/create` + +* **cron**:使用 CRON 表达式按计划运行你的函数。 +* **databaseEvent**:在工作空间对象生命周期事件上运行。 当事件操作为 `updated` 时,可以在 `updatedFields` 数组中指定要监听的特定字段。 如果未定义或为空,任何更新都会触发该函数。 + +> 例如 `person.updated` + +备注: + +* `triggers` 数组是可选的。 没有触发器的函数可作为实用函数,被其他函数调用。 +* 你可以在单个函数中混用多种触发器类型。 + +### 安装前函数 + +安装前函数是在你的应用安装到工作区之前自动运行的逻辑函数。 这对于执行验证任务、先决条件检查,或在主安装开始前准备工作区状态很有用。 + +当你使用 `create-twenty-app` 脚手架创建一个新应用时,会在 `src/logic-functions/pre-install.ts` 为你生成一个安装前函数: + +```typescript +// src/logic-functions/pre-install.ts +import { definePreInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Pre install logic function executed successfully!', payload.previousVersion); +}; + +export default definePreInstallLogicFunction({ + universalIdentifier: '', + name: 'pre-install', + description: 'Runs before installation to prepare the application.', + timeoutSeconds: 300, + handler, +}); +``` + +你也可以随时使用 CLI 手动执行安装前函数: + +```bash filename="Terminal" +yarn twenty function:execute --preInstall +``` + +关键点: + +* 安装前函数使用 `definePreInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 +* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 +* 每个应用仅允许一个安装前函数。 如果检测到多个,清单构建将报错。 +* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `preInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 +* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的准备任务。 +* 安装前函数不需要触发器——它们会在安装前由平台调用,或通过 `function:execute --preInstall` 手动调用。 + +### 安装后函数 + +安装后函数是在你的应用安装到工作区后自动运行的逻辑函数。 这对于一次性设置任务很有用,例如填充默认数据、创建初始记录或配置工作区设置。 + +当你使用 `create-twenty-app` 脚手架创建一个新应用时,会在 `src/logic-functions/post-install.ts` 为你生成一个安装后函数: + +```typescript +// src/logic-functions/post-install.ts +import { definePostInstallLogicFunction, type InstallLogicFunctionPayload } from 'twenty-sdk'; + +const handler = async (payload: InstallLogicFunctionPayload): Promise => { + console.log('Post install logic function executed successfully!', payload.previousVersion); +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: '', + name: 'post-install', + description: 'Runs after installation to set up the application.', + timeoutSeconds: 300, + handler, +}); +``` + +你也可以随时使用 CLI 手动执行安装后函数: + +```bash filename="Terminal" +yarn twenty function:execute --postInstall +``` + +关键点: + +* 安装后函数使用 `definePostInstallLogicFunction()` —— 这是一个省略触发器设置(`cronTriggerSettings`、`databaseEventTriggerSettings`、`httpRouteTriggerSettings`、`isTool`)的专用变体。 +* 处理器会接收一个 `InstallLogicFunctionPayload`,其包含 `{ previousVersion: string }` —— 即之前安装的应用版本(全新安装则为空字符串)。 +* 每个应用仅允许一个安装后函数。 如果检测到多个,清单构建将报错。 +* 在构建期间,函数的 `universalIdentifier` 会自动设置为应用清单上的 `postInstallLogicFunctionUniversalIdentifier` —— 你无需在 `defineApplication()` 中引用它。 +* 默认超时时间设置为 300 秒(5 分钟),以便支持更长的设置任务,如数据填充。 +* 安装后函数不需要触发器——它们会在安装过程中由平台调用,或通过 `function:execute --postInstall` 手动调用。 + +### 路由触发器负载 + + +**破坏性变更(v1.16,2026 年 1 月):** 路由触发器的负载格式已更改。 在 v1.16 之前,查询参数、路径参数和请求体会直接作为负载发送。 从 v1.16 开始,它们被嵌套在结构化的 `RoutePayload` 对象中。 + +**v1.16 之前:** +```typescript +const handler = async (params) => { + const { param1, param2 } = params; // Direct access +}; +``` + +**v1.16 之后:** +```typescript +const handler = async (event: RoutePayload) => { + const { param1, param2 } = event.body; // Access via .body + const { queryParam } = event.queryStringParameters; + const { id } = event.pathParameters; +}; +``` + +**迁移现有函数:** 将处理程序更新为从 `event.body`、`event.queryStringParameters` 或 `event.pathParameters` 解构,而不是直接从参数对象解构。 + + +当路由触发器调用你的逻辑函数时,它会接收一个遵循 AWS HTTP API v2 格式的 `RoutePayload` 对象。 从 `twenty-sdk` 导入该类型: + +```typescript +import { defineLogicFunction, type RoutePayload } from 'twenty-sdk'; + +const handler = async (event: RoutePayload) => { + // Access request data + const { headers, queryStringParameters, pathParameters, body } = event; + + // HTTP method and path are available in requestContext + const { method, path } = event.requestContext.http; + + return { message: 'Success' }; +}; +``` + +`RoutePayload` 类型具有以下结构: + +| 属性 | 类型 | 描述 | +| ---------------------------- | ------------------------------------- | ------------------------------------------------ | +| `headers` | `Record` | HTTP 请求头(仅限 `forwardedRequestHeaders` 中列出的那些) | +| `queryStringParameters` | `Record` | 查询字符串参数(多个值以逗号连接) | +| `pathParameters` | `Record` | 从路由模式中提取的路径参数(例如,`/users/:id` → `{ id: '123' }`) | +| `body` | `object \| null` | 已解析的请求体(JSON) | +| `isBase64Encoded` | `boolean` | 请求体是否为 base64 编码 | +| `requestContext.http.method` | `string` | HTTP 方法(GET、POST、PUT、PATCH、DELETE) | +| `requestContext.http.path` | `string` | 原始请求路径 | + +### 转发 HTTP 请求头 + +出于安全原因,默认**不会**将传入请求的 HTTP 请求头传递给你的逻辑函数。 如需访问特定请求头,请在 `forwardedRequestHeaders` 数组中显式列出: + +```typescript +export default defineLogicFunction({ + universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf', + name: 'webhook-handler', + handler, + triggers: [ + { + universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6', + type: 'route', + path: '/webhook', + httpMethod: 'POST', + isAuthRequired: false, + forwardedRequestHeaders: ['x-webhook-signature', 'content-type'], + }, + ], +}); +``` + +随后你可以在处理程序中访问这些请求头: + +```typescript +const handler = async (event: RoutePayload) => { + const signature = event.headers['x-webhook-signature']; + const contentType = event.headers['content-type']; + + // Validate webhook signature... + return { received: true }; +}; +``` + + + 请求头名称会被规范化为小写。 请使用小写键访问它们(例如,`event.headers['content-type']`)。 + + +你可以通过两种方式创建新函数: + +* **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新逻辑函数的选项。 这将生成一个包含处理程序和配置的入门文件。 +* **手动**:创建一个新的 `*.logic-function.ts` 文件,并使用 `defineLogicFunction()`,遵循相同的模式。 + +### 将逻辑函数标记为工具 + +逻辑函数可以作为供 AI 智能体和工作流使用的**工具**对外提供。 当函数被标记为工具时,Twenty 的 AI 功能即可发现它,并可在工作流自动化中将其选作一个步骤。 + +要将逻辑函数标记为工具,请设置 `isTool: true`,并提供 `toolInputSchema`,使用 [JSON Schema](https://json-schema.org/) 描述预期的输入参数: + +```typescript +// src/logic-functions/enrich-company.logic-function.ts +import { defineLogicFunction } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/generated'; + +const handler = async (params: { companyName: string; domain?: string }) => { + const client = new CoreApiClient(); + + const result = await client.mutation({ + createTask: { + __args: { + data: { + title: `Enrich data for ${params.companyName}`, + body: `Domain: ${params.domain ?? 'unknown'}`, + }, + }, + id: true, + }, + }); + + return { taskId: result.createTask.id }; +}; + +export default defineLogicFunction({ + universalIdentifier: 'f47ac10b-58cc-4372-a567-0e02b2c3d479', + name: 'enrich-company', + description: 'Enrich a company record with external data', + timeoutSeconds: 10, + handler, + isTool: true, + toolInputSchema: { + type: 'object', + properties: { + companyName: { + type: 'string', + description: 'The name of the company to enrich', + }, + domain: { + type: 'string', + description: 'The company website domain (optional)', + }, + }, + required: ['companyName'], + }, +}); +``` + +关键点: + +* **`isTool`** (`boolean`, 默认: `false`): 当设置为 `true` 时,该函数会被注册为工具,并可供 AI 代理和工作流自动化使用。 +* **`toolInputSchema`** (`object`, 可选): 描述函数可接受参数的 JSON Schema 对象。 AI 代理使用此架构来理解该工具期望的输入并验证调用。 如果省略,架构将默认为 `{ type: 'object', properties: {} }`(无参数)。 +* 设置为 `isTool: false`(或未设置)的函数**不会**被暴露为工具。 它们仍可直接执行或被其他函数调用,但不会出现在工具发现中。 +* **工具命名**: 当作为工具对外暴露时,函数名会被自动规范化为 `logic_function_`(转换为小写,非字母数字字符替换为下划线)。 例如,`enrich-company` 将变为 `logic_function_enrich_company`。 +* 你可以将 `isTool` 与触发器结合使用——一个函数既可以作为工具(由 AI 代理调用),也可以同时由事件(cron、数据库事件、路由)触发。 + + +**写一个好的 `description`。** AI 代理会依赖该函数的 `description` 字段来决定何时使用该工具。 明确说明该工具的作用以及应在何时调用。 + + +### 前端组件 + +前端组件使你可以构建在 Twenty 的 UI 中渲染的自定义 React 组件。 使用 `defineFrontComponent()` 以内置校验定义组件: + +```typescript +// src/front-components/my-widget.tsx +import { defineFrontComponent } from 'twenty-sdk'; + +const MyWidget = () => { + return ( +
+

My Custom Widget

+

This is a custom front component for Twenty.

+
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-widget', + description: 'A custom widget component', + component: MyWidget, +}); +``` + +关键点: + +* 前端组件是在 Twenty 中的隔离上下文中渲染的 React 组件。 +* `component` 字段引用你的 React 组件。 +* 组件会在 `yarn twenty app:dev` 期间自动构建并同步。 + +你可以通过两种方式创建新的前端组件: + +* **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新前端组件的选项。 +* **手动**:创建一个新的 `.tsx` 文件,并使用 `defineFrontComponent()`,遵循相同的模式。 + +### 技能 + +技能定义了可复用的指令和能力,AI 智能体可在你的工作区中使用。 使用 `defineSkill()` 定义带内置校验的技能: + +```typescript +// src/skills/example-skill.ts +import { defineSkill } from 'twenty-sdk'; + +export default defineSkill({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'sales-outreach', + label: 'Sales Outreach', + description: 'Guides the AI agent through a structured sales outreach process', + icon: 'IconBrain', + content: `You are a sales outreach assistant. When reaching out to a prospect: +1. Research the company and recent news +2. Identify the prospect's role and likely pain points +3. Draft a personalized message referencing specific details +4. Keep the tone professional but conversational`, +}); +``` + +关键点: + +* `name` 是该技能的唯一标识字符串(推荐使用 kebab-case)。 +* `label` 是在 UI 中显示的人类可读名称。 +* `content` 包含技能指令——这是 AI 智能体使用的文本。 +* `icon`(可选)设置在 UI 中显示的图标。 +* `description`(可选)提供有关技能用途的更多上下文。 + +你可以通过两种方式创建新技能: + +* **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新技能的选项。 +* **手动**:创建一个新文件,并使用 `defineSkill()`,遵循相同的模式。 + +### 生成的类型化客户端 + +两个类型化客户端由 `yarn twenty app:dev` 自动生成(基于你的工作区架构),并存放在 `node_modules/twenty-sdk/generated`: + +* **`CoreApiClient`** — 查询 `/graphql` 端点以获取工作区数据 +* **`MetadataApiClient`** — 查询 `/metadata` 端点以获取工作区配置并处理文件上传 + +```typescript +import { CoreApiClient, MetadataApiClient } from 'twenty-sdk/generated'; + +const client = new CoreApiClient(); +const { me } = await client.query({ me: { id: true, displayName: true } }); + +const metadataClient = new MetadataApiClient(); +const { currentWorkspace } = await metadataClient.query({ currentWorkspace: { id: true } }); +``` + +每当你的对象或字段发生变化时,`yarn twenty app:dev` 都会自动重新生成这两个客户端。 + +#### 逻辑函数中的运行时凭据 + +当你的函数在 Twenty 上运行时,平台会在代码执行前将凭据作为环境变量注入: + +* `TWENTY_API_URL`:你的应用所针对的 Twenty API 的基础 URL。 +* `TWENTY_API_KEY`:作用域限定于你的应用默认函数角色的短期密钥。 + +备注: + +* 你无需向生成的客户端传递 URL 或 API 密钥。 它会在运行时从 process.env 读取 `TWENTY_API_URL` 和 `TWENTY_API_KEY`。 +* API 密钥的权限由 `application-config.ts` 中通过 `defaultRoleUniversalIdentifier` 引用的角色决定。 这是你的应用逻辑函数使用的默认角色。 +* 应用可以定义角色以遵循最小权限原则。 仅授予函数所需的权限,然后将 `defaultRoleUniversalIdentifier` 指向该角色的通用标识符。 + +#### 上传文件 + +生成的 `MetadataApiClient` 包含一个 `uploadFile` 方法,用于将文件附加到你的工作区对象的文件类型字段。 由于标准 GraphQL 客户端不原生支持多部分文件上传,该客户端提供了一个专用方法,在底层实现了 [GraphQL 多部分请求规范](https://github.com/jaydenseric/graphql-multipart-request-spec)。 + +```typescript +import { MetadataApiClient } from 'twenty-sdk/generated'; +import * as fs from 'fs'; + +const metadataClient = new MetadataApiClient(); + +const fileBuffer = fs.readFileSync('./invoice.pdf'); + +const uploadedFile = await metadataClient.uploadFile( + fileBuffer, // file contents as a Buffer + 'invoice.pdf', // filename + 'application/pdf', // MIME type (defaults to 'application/octet-stream') + '58a0a314-d7ea-4865-9850-7fb84e72f30b', // field universal identifier +); + +console.log(uploadedFile); +// { id: '...', path: '...', size: 12345, createdAt: '...', url: 'https://...' } +``` + +方法签名: + +```typescript +uploadFile( + fileBuffer: Buffer, + filename: string, + contentType: string, + fieldMetadataUniversalIdentifier: string, +): Promise<{ id: string; path: string; size: number; createdAt: string; url: string }> +``` + +| 参数 | 类型 | 描述 | +| ---------------------------------- | -------- | ------------------------------------------------ | +| `fileBuffer` | `Buffer` | 原始文件内容 | +| `filename` | `string` | 文件名称(用于存储和显示) | +| `contentType` | `string` | 文件的 MIME 类型(如果省略,默认为 `application/octet-stream`) | +| `fieldMetadataUniversalIdentifier` | `string` | 你的对象上文件类型字段的 `universalIdentifier` | + +关键点: + +* `uploadFile` 方法可在 `MetadataApiClient` 上使用,因为上传 mutation 由 `/metadata` 端点解析。 +* 它使用该字段的 `universalIdentifier`(而不是其工作区特定的 ID),因此你的上传代码可以在安装了你的应用的任何工作区中使用——这与应用在其他地方引用字段的方式保持一致。 +* 返回的 `url` 是一个签名 URL,你可以用它来访问已上传的文件。 + +### Hello World 示例 + +在[此处](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world)查看一个最小的端到端示例,展示对象、逻辑函数、前端组件和多种触发器。 diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx new file mode 100644 index 0000000000..d1815343e4 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/getting-started.mdx @@ -0,0 +1,233 @@ +--- +title: 开始使用 +description: 几分钟内创建你的第一个 Twenty 应用。 +--- + + +应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。 + + +应用可通过自定义对象、字段、逻辑函数、AI 技能和 UI 组件来扩展 Twenty——全部以代码进行管理。 + +**你现在可以做什么:** + +* 以代码定义自定义对象和字段(受管理的数据模型) +* 使用自定义触发器(HTTP 路由、cron、数据库事件)构建逻辑函数 +* 为 AI 智能体定义技能 +* 构建在 Twenty 的 UI 中渲染的前端组件 +* 将同一个应用部署到多个工作空间 + +## 先决条件 + +* Node.js 24+ 和 Yarn 4 +* 一个 Twenty 工作空间和一个 API 密钥(在 https://app.twenty.com/settings/api-webhooks 创建) + +## 开始使用 + +使用官方脚手架创建一个新应用,然后进行身份验证并开始开发: + +```bash filename="Terminal" +# Scaffold a new app (includes all examples by default) +npx create-twenty-app@latest my-twenty-app +cd my-twenty-app + +# Start dev mode: automatically syncs local changes to your workspace +yarn twenty app:dev +``` + +脚手架工具支持两种模式,用于控制包含哪些示例文件: + +```bash filename="Terminal" +# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill) +npx create-twenty-app@latest my-app + +# Minimal: only core files (application-config.ts and default-role.ts) +npx create-twenty-app@latest my-app --minimal +``` + +从这里您可以: + +```bash filename="Terminal" +# Add a new entity to your application (guided) +yarn twenty entity:add + +# Watch your application's function logs +yarn twenty function:logs + +# Execute a function by name +yarn twenty function:execute -n my-function -p '{"name": "test"}' + +# Execute the pre-install function +yarn twenty function:execute --preInstall + +# Execute the post-install function +yarn twenty function:execute --postInstall + +# Uninstall the application from the current workspace +yarn twenty app:uninstall + +# Display commands' help +yarn twenty help +``` + +另请参阅:[create-twenty-app](https://www.npmjs.com/package/create-twenty-app) 和 [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk) 的 CLI 参考页面。 + +## 项目结构(脚手架生成) + +当你运行 `npx create-twenty-app@latest my-twenty-app` 时,脚手架将: + +* 将一个最小的基础应用复制到 `my-twenty-app/` 中 +* 添加本地 `twenty-sdk` 依赖和 Yarn 4 配置 +* 创建与 `twenty` CLI 关联的配置文件和脚本 +* 生成核心文件(应用配置、默认函数角色、安装前/安装后函数),并基于脚手架模式生成示例文件 + +使用默认 `--exhaustive` 模式新搭建的应用如下所示: + +```text filename="my-twenty-app/" +my-twenty-app/ + package.json + yarn.lock + .gitignore + .nvmrc + .yarnrc.yml + .yarn/ + install-state.gz + .oxlintrc.json + tsconfig.json + README.md + public/ # Public assets folder (images, fonts, etc.) + src/ + ├── application-config.ts # Required - main application configuration + ├── roles/ + │ └── default-role.ts # Default role for logic functions + ├── objects/ + │ └── example-object.ts # Example custom object definition + ├── fields/ + │ └── example-field.ts # Example standalone field definition + ├── logic-functions/ + │ ├── hello-world.ts # Example logic function + │ ├── pre-install.ts # Pre-install logic function + │ └── post-install.ts # Post-install logic function + ├── front-components/ + │ └── hello-world.tsx # Example front component + ├── views/ + │ └── example-view.ts # Example saved view definition + ├── navigation-menu-items/ + │ └── example-navigation-menu-item.ts # Example sidebar navigation link + └── skills/ + └── example-skill.ts # Example AI agent skill definition +``` + +使用 `--minimal` 时,只会创建核心文件(`application-config.ts`、`roles/default-role.ts`、`logic-functions/pre-install.ts` 和 `logic-functions/post-install.ts`)。 + +总体来说: + +* **package.json**:声明应用名称、版本、引擎(Node 24+、Yarn 4),并添加 `twenty-sdk` 以及一个 `twenty` 脚本,该脚本会委托给本地的 `twenty` CLI。 运行 `yarn twenty help` 以列出所有可用命令。 +* **.gitignore**:忽略常见产物,如 `node_modules`、`.yarn`、`generated/`(类型化客户端)、`dist/`、`build/`、覆盖率文件夹、日志文件以及 `.env*` 文件。 +* **yarn.lock**、**.yarnrc.yml**、**.yarn/**:锁定并配置项目使用的 Yarn 4 工具链。 +* **.nvmrc**:固定项目期望的 Node.js 版本。 +* **.oxlintrc.json** 和 **tsconfig.json**:为应用的 TypeScript 源码提供 Lint 与 TypeScript 配置。 +* **README.md**:应用根目录中的简短 README,包含基本说明。 +* **public/**: 一个用于存储公共资源(图像、字体、静态文件)的文件夹,这些资源将随你的应用程序一起提供。 放置在此处的文件会在同步期间上传,并可在运行时访问。 +* **src/**:你以代码形式定义应用的主要位置 + +### 实体检测 + +该 SDK 通过在你的 TypeScript 文件中解析 **`export default define({...})`** 调用来检测实体。 每种实体类型都有一个从 `twenty-sdk` 导出的对应辅助函数: + +| 辅助函数 | 实体类型 | +| -------------------------------- | ---------------- | +| `defineObject` | 自定义对象定义 | +| `defineLogicFunction` | 逻辑函数定义 | +| `definePreInstallLogicFunction` | 安装前逻辑函数(在安装之前运行) | +| `definePostInstallLogicFunction` | 安装后逻辑函数(在安装之后运行) | +| `defineFrontComponent` | 前端组件定义 | +| `defineRole` | 角色定义 | +| `defineField` | 现有对象的字段扩展 | +| `defineView` | 已保存的视图定义 | +| `defineNavigationMenuItem` | 导航菜单项定义 | +| `defineSkill` | AI 代理技能定义 | + + +**文件命名是灵活的。** 实体检测基于 AST — SDK 会扫描你的源文件以查找 `export default define({...})` 模式。 你可以按照自己的喜好组织文件和文件夹。 按实体类型分组(例如 `logic-functions/`、`roles/`)只是代码组织的一种约定,并非必需。 + + +已检测实体的示例: + +```typescript +// This file can be named anything and placed anywhere in src/ +import { defineObject, FieldType } from 'twenty-sdk'; + +export default defineObject({ + universalIdentifier: '...', + nameSingular: 'postCard', + // ... rest of config +}); +``` + +后续命令将添加更多文件和文件夹: + +* `yarn twenty app:dev` 会在 `node_modules/twenty-sdk/generated` 中自动生成两个类型化 API 客户端:`CoreApiClient`(通过 `/graphql` 获取工作区数据)和 `MetadataApiClient`(通过 `/metadata` 处理工作区配置和文件上传)。 +* `yarn twenty entity:add` 会在 `src/` 下为你的自定义对象、函数、前端组件、角色、技能等添加实体定义文件。 + +## 身份验证 + +首次运行 `yarn twenty auth:login` 时,你将被提示输入: + +* API URL(默认为 http://localhost:3000 或你当前的工作空间配置) +* API 密钥 + +你的凭据按用户存储在 `~/.twenty/config.json` 中。 你可以维护多个配置文件并在它们之间切换。 + +### 管理工作空间 + +```bash filename="Terminal" +# Login interactively (recommended) +yarn twenty auth:login + +# Login to a specific workspace profile +yarn twenty auth:login --workspace my-custom-workspace + +# List all configured workspaces +yarn twenty auth:list + +# Switch the default workspace (interactive) +yarn twenty auth:switch + +# Switch to a specific workspace +yarn twenty auth:switch production + +# Check current authentication status +yarn twenty auth:status +``` + +使用 `yarn twenty auth:switch` 切换工作空间后,后续所有命令将默认使用该工作空间。 你仍可通过 `--workspace ` 临时覆盖。 + +## 手动设置(不使用脚手架) + +虽然我们建议使用 `create-twenty-app` 以获得最佳的上手体验,但你也可以手动设置项目。 不要全局安装 CLI。 相反,请将 `twenty-sdk` 添加为本地依赖,并在你的 package.json 中配置一个脚本: + +```bash filename="Terminal" +yarn add -D twenty-sdk +``` + +然后添加一个 `twenty` 脚本: + +```json filename="package.json" +{ + "scripts": { + "twenty": "twenty" + } +} +``` + +现在你可以通过 `yarn twenty ` 运行所有命令,例如 `yarn twenty app:dev`、`yarn twenty help` 等。 + +## 故障排除 + +* 身份验证错误:运行 `yarn twenty auth:login`,并确保你的 API 密钥具有所需权限。 +* 无法连接到服务器:请验证 API URL,并确保 Twenty 服务器可达。 +* 类型或客户端缺失/过期:重启 `yarn twenty app:dev` — 它会自动生成类型化客户端。 +* 开发模式未同步:确保 `yarn twenty app:dev` 正在运行,并且你的环境不会忽略变更。 + +Discord 帮助频道:https://discord.com/channels/1130383047699738754/1130386664812982322 diff --git a/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx new file mode 100644 index 0000000000..34c27b92ea --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/apps/publishing.mdx @@ -0,0 +1,119 @@ +--- +title: 发布 +description: 将你的 Twenty 应用分发到应用市场,或进行内部部署。 +--- + + +应用目前处于 Alpha 测试阶段。 该功能可用,但仍在演进中。 + + +## 概览 + +一旦你的应用已[在本地构建并完成测试](/l/zh/developers/extend/apps/building),你可以通过两种方式进行分发: + +* **发布到 npm** — 将你的应用在 Twenty 应用市场上架,供任何工作区发现并安装。 +* **推送 tar 包** — 将你的应用部署到特定的 Twenty 服务器供内部使用,而无需公开发布。 + +## 发布到 npm + +发布到 npm 可让你的应用在 Twenty 应用市场中被发现。 任何 Twenty 工作区都可以直接通过 UI 浏览、安装和升级应用市场中的应用。 + +### 要求 + +* 一个 [npm](https://www.npmjs.com) 账户 +* 你的包名**必须**使用 `twenty-app-` 前缀(例如,`twenty-app-postcard-sender`) + +### 步骤 + +1. **构建你的应用** — CLI 会编译你的 TypeScript 源码并生成应用清单: + +```bash filename="Terminal" +yarn twenty app:build +``` + +2. **发布到 npm** — 将构建好的包推送到 npm 注册表: + +```bash filename="Terminal" +npx twenty app:publish +``` + +### 自动发现 + +Twenty 应用市场目录会自动发现带有 `twenty-app-` 前缀的包。 发布后,你的应用会在几分钟内出现在应用市场中 — 无需手动注册或审批。 + +### CI 发布 + +脚手架项目包含一个 GitHub Actions 工作流,会在每次发版时自动发布。 它会先运行 `app:build`,然后在构建输出目录中执行 `npm publish --provenance`: + +```yaml filename=".github/workflows/publish.yml" +name: Publish +on: + release: + types: [published] + +permissions: + contents: read + id-token: write + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "24" + registry-url: https://registry.npmjs.org + - run: yarn install --immutable + - run: npx twenty app:build + - run: npm publish --provenance --access public + working-directory: .twenty/output + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} +``` + +对于其他 CI 系统(GitLab CI、CircleCI 等),同样适用以下三条命令:`yarn install`、`npx twenty app:build`,然后在 `.twenty/output` 目录下执行 `npm publish`。 + + +**npm provenance** 可选,但建议启用。 使用 `--provenance` 发布会在你的 npm 列表中添加可信徽章,使用户可以验证该包是由公共 CI 流水线中的特定提交构建的。 有关设置说明,请参见 [npm provenance 文档](https://docs.npmjs.com/generating-provenance-statements)。 + + +## 内部分发 + +对于你不希望公开的应用 — 例如专有工具、仅供企业使用的集成或实验性构建 — 你可以将 tar 包直接推送到某台 Twenty 服务器。 + +### 推送 tar 包 + +在一步中构建你的应用并将其部署到特定服务器: + +```bash filename="Terminal" +npx twenty app:publish --server +``` + +该服务器上的任何工作区随后都可以在**应用程序**设置页面安装和升级该应用。 + +### 版本管理 + +要发布更新: + +1. 更新 `package.json` 中的 `version` 字段 +2. 使用 `npx twenty app:publish --server ` 推送新的 tar 包 +3. 该服务器上的工作区会在其设置中看到可用的升级 + + +内部应用的作用范围仅限于它们被推送到的服务器。 它们不会出现在公共应用市场中,其他服务器上的工作区也无法安装。 + + +## 应用类别 + +Twenty 会根据分发方式将应用归为三类: + +| 类别 | 工作原理 | 在应用市场中可见? | +| ------- | ------------------------------------------------- | --------- | +| **开发** | 通过 `yarn twenty app:dev` 运行的本地开发模式应用。 用于构建和测试。 | 否 | +| **已发布** | 使用 `twenty-app-` 前缀发布到 npm 的应用。 在应用市场上架,供任何工作区安装。 | 是 | +| **内部** | 通过 tar 包部署到特定服务器的应用。 仅对该服务器上的工作区可用。 | 否 | + + +在构建你的应用时,从**开发**模式开始。 准备就绪后,选择用于广泛分发的**已发布**(npm),或用于私有部署的**内部**(tar 包)。 + diff --git a/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx b/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx index 129aa9e706..42f112e49e 100644 --- a/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/capabilities/apps.mdx @@ -321,11 +321,11 @@ export default defineObject({ 但不建议这样做。 -### Defining fields on existing objects +### 在现有对象上定义字段 -Use `defineField()` to add custom fields to existing objects — both standard objects (like `company`, `person`, `opportunity`) and custom objects defined by other apps. Each field lives in its own file and references the target object by its `universalIdentifier`. +使用 `defineField()` 向现有对象添加自定义字段——包括标准对象(如 `company`、`person`、`opportunity`)以及由其他应用定义的自定义对象。 每个字段位于其各自的文件中,并通过其 `universalIdentifier` 引用目标对象。 -To reference standard objects, import `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` from `twenty-sdk`. This constant provides stable identifiers for all built-in objects and their fields: +要引用标准对象,请从 `twenty-sdk` 导入 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS`。 该常量为所有内置对象及其字段提供稳定的标识符: ```typescript // src/fields/apollo-total-funding.field.ts @@ -349,22 +349,22 @@ export default defineField({ 关键点: -* `objectUniversalIdentifier` tells Twenty which object to attach the field to. Use `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` for standard objects. -* Each field requires its own stable `universalIdentifier`, a `name`, `type`, `label`, and the target `objectUniversalIdentifier`. -* You can scaffold new fields using `yarn twenty entity:add` and choosing the field option. -* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` is also exported as `STANDARD_OBJECT` for convenience — both refer to the same constant. +* `objectUniversalIdentifier` 告诉 Twenty 将该字段附加到哪个对象。 使用 `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS..universalIdentifier` 用于标准对象。 +* 每个字段都需要其自身稳定的 `universalIdentifier`、`name`、`type`、`label`,以及目标 `objectUniversalIdentifier`。 +* 你可以使用 `yarn twenty entity:add` 脚手架创建新字段,并选择字段选项。 +* `STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS` 也以 `STANDARD_OBJECT` 的名称导出以方便使用——二者指向同一常量。 -Available standard objects include: `attachment`, `blocklist`, `calendarChannel`, `calendarEvent`, `calendarEventParticipant`, `company`, `connectedAccount`, `dashboard`, `favorite`, `favoriteFolder`, `message`, `messageChannel`, `messageParticipant`, `messageThread`, `note`, `noteTarget`, `opportunity`, `person`, `task`, `taskTarget`, `timelineActivity`, `workflow`, `workflowAutomatedTrigger`, `workflowRun`, `workflowVersion`, and `workspaceMember`. +可用的标准对象包括:`attachment`、`blocklist`、`calendarChannel`、`calendarEvent`、`calendarEventParticipant`、`company`、`connectedAccount`、`dashboard`、`favorite`、`favoriteFolder`、`message`、`messageChannel`、`messageParticipant`、`messageThread`、`note`、`noteTarget`、`opportunity`、`person`、`task`、`taskTarget`、`timelineActivity`、`workflow`、`workflowAutomatedTrigger`、`workflowRun`、`workflowVersion` 和 `workspaceMember`。 -Each standard object also exposes its field identifiers. For example, to reference a specific field on a standard object in role permissions: +每个标准对象还会暴露其字段标识符。 例如,要在角色权限中引用标准对象上的特定字段: ```typescript STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.fields.name.universalIdentifier ``` -#### Relation fields on existing objects +#### 现有对象上的关联字段 -You can also define relation fields that link existing objects to your custom objects: +你还可以定义关联字段,将现有对象链接到你的自定义对象: ```typescript // src/fields/people-on-call-recording.field.ts @@ -837,6 +837,255 @@ export default defineFrontComponent({ * **脚手架生成**:运行 `yarn twenty entity:add` 并选择添加新前端组件的选项。 * **手动**:创建一个新的 `.tsx` 文件,并使用 `defineFrontComponent()`,遵循相同的模式。 +#### Where front components can be used + +Front components can render in two locations within Twenty: + +* **Side panel** — Non-headless front components open in the right-hand side panel. This is the default behavior when a front component is triggered from the command menu. +* **Widgets (dashboards and record pages)** — Front components can be embedded as widgets inside page layouts. When configuring a dashboard or a record page layout, users can add a front component widget. + +#### Headless vs non-headless + +Front components come in two rendering modes controlled by the `isHeadless` option: + +**Non-headless (default)** — The component renders a visible UI. When triggered from the command menu it opens in the side panel. This is the default behavior when `isHeadless` is `false` or omitted. + +**Headless** — The component mounts invisibly in the background. It does not open the side panel. Headless components are designed for actions that execute logic and then unmount themselves — for example, running an async task, navigating to a page, or showing a confirmation modal. They pair naturally with the SDK Command components described below. + +```typescript +export default defineFrontComponent({ + universalIdentifier: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + name: 'my-action', + description: 'Runs an action without opening the side panel', + component: MyAction, + isHeadless: true, + command: { + universalIdentifier: 'b2c3d4e5-f6a7-8901-bcde-f12345678901', + label: 'Run my action', + }, +}); +``` + +#### Adding command menu items + +To make a front component appear as an item in Twenty's command menu, add the `command` property to `defineFrontComponent()`. When users open the command menu (Cmd+K / Ctrl+K), the item shows up and triggers the front component on click. + +The `command` object accepts the following fields: + +| 字段 | 类型 | 描述 | +| --------------------------------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `universalIdentifier` | `string` (required) | Unique ID for the command menu item | +| `标签` | `string` (required) | Display label shown in the command menu | +| `图标` | `string` (optional) | Icon name (e.g., `'IconSparkles'`) | +| `isPinned` | `boolean` (optional) | Whether the command is pinned at the top of the menu | +| `availabilityType` | `'GLOBAL' \| 'RECORD_SELECTION'` (optional) | `GLOBAL` shows the command everywhere; `RECORD_SELECTION` shows it only in record contexts | +| `availabilityObjectUniversalIdentifier` | `string` (optional) | Restrict the command to a specific object type (e.g., Person) | + +Here is an example from the call-recording app that adds a command scoped to Person records: + +```typescript +import { defineFrontComponent } from 'twenty-sdk'; + +export default defineFrontComponent({ + universalIdentifier: 'c3d4e5f6-a7b8-9012-cdef-123456789012', + name: 'Summarize Person Call Recordings', + description: 'Generates a summary of call recordings for a person', + component: SummarizePersonRecordings, + command: { + universalIdentifier: 'd4e5f6a7-b8c9-0123-defa-234567890123', + label: 'Summarize call recordings', + icon: 'IconSparkles', + isPinned: false, + availabilityType: 'RECORD_SELECTION', + availabilityObjectUniversalIdentifier: + '20202020-e674-48e5-a542-72570eee7213', + }, +}); +``` + +When the command is synced, it appears in the command menu. If the front component is non-headless the side panel opens with the component rendered inside. If it is headless the component mounts in the background and executes its logic. + +#### SDK Command components + +The `twenty-sdk` package provides four Command helper components designed for headless front components. Each component executes an action on mount, handles errors by showing a snackbar notification, and automatically unmounts the front component when done. + +Import them from `twenty-sdk/command`: + +* **`Command`** — Runs an async callback via the `execute` prop. +* **`CommandLink`** — Navigates to an app path. Props: `to`, `params`, `queryParams`, `options`. +* **`CommandModal`** — Opens a confirmation modal. If the user confirms, executes the `execute` callback. Props: `title`, `subtitle`, `execute`, `confirmButtonText`, `confirmButtonAccent`. +* **`CommandOpenSidePanelPage`** — Opens a specific side panel page. Props: `page`, `pageTitle`, `pageIcon`. + +Here is a full example of a headless front component using `Command` to run an action from the command menu: + +```typescript +// src/front-components/run-action.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { Command } from 'twenty-sdk/command'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const RunAction = () => { + const execute = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + createTask: { + __args: { data: { title: 'Created by my app' } }, + id: true, + }, + }); + }; + + return ; +}; + +export default defineFrontComponent({ + universalIdentifier: 'e5f6a7b8-c9d0-1234-efab-345678901234', + name: 'run-action', + description: 'Creates a task from the command menu', + component: RunAction, + isHeadless: true, + command: { + universalIdentifier: 'f6a7b8c9-d0e1-2345-fabc-456789012345', + label: 'Run my action', + icon: 'IconPlayerPlay', + }, +}); +``` + +And an example using `CommandModal` to ask for confirmation before executing: + +```typescript +// src/front-components/delete-draft.tsx +import { defineFrontComponent } from 'twenty-sdk'; +import { CommandModal } from 'twenty-sdk/command'; + +const DeleteDraft = () => { + const execute = async () => { + // perform the deletion + }; + + return ( + + ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'a7b8c9d0-e1f2-3456-abcd-567890123456', + name: 'delete-draft', + description: 'Deletes a draft with confirmation', + component: DeleteDraft, + isHeadless: true, + command: { + universalIdentifier: 'b8c9d0e1-f2a3-4567-bcde-678901234567', + label: 'Delete draft', + icon: 'IconTrash', + }, +}); +``` + +#### Execution context + +Every front component receives an execution context that provides information about where and how it is running. Access context values using hooks from `twenty-sdk`: + +| Hook | Return type | 描述 | +| ----------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `useFrontComponentId()` | `string` | The unique ID of the current front component instance | +| `useRecordId()` | `string \| null` | The ID of the current record, when the component runs in a record context (e.g., a record page widget or a command scoped to a record). Returns `null` otherwise. | +| `useUserId()` | `string \| null` | The ID of the current user | + +```typescript +import { useRecordId, useUserId } from 'twenty-sdk'; + +const MyWidget = () => { + const recordId = useRecordId(); + const userId = useUserId(); + + return ( +
+

Record: {recordId ?? 'none'}

+

User: {userId ?? 'anonymous'}

+
+ ); +}; +``` + +The context is reactive — if the surrounding record changes, hooks automatically return the updated values. + +#### Host API functions + +Front components run in an isolated sandbox but can interact with Twenty's UI through a set of functions provided by the host. Import them directly from `twenty-sdk`: + +```typescript +import { + navigate, + closeSidePanel, + enqueueSnackbar, + unmountFrontComponent, + openSidePanelPage, + openCommandConfirmationModal, +} from 'twenty-sdk'; +``` + +| 函数 | Signature | 描述 | +| ------------------------------ | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `导航` | `(to, params?, queryParams?, options?) => Promise` | Navigate to a typed app path within Twenty | +| `closeSidePanel` | `() => Promise` | Close the side panel | +| `enqueueSnackbar` | `(params) => Promise` | Show a snackbar notification. Params: `message`, `variant` (`'error'`, `'success'`, `'info'`, `'warning'`), optional `duration`, `detailedMessage`, `dedupeKey` | +| `unmountFrontComponent` | `() => Promise` | Unmount the current front component (used by headless components to clean up after execution) | +| `openSidePanelPage` | `(params) => Promise` | Open a page in the side panel. Params: `page`, `pageTitle`, `pageIcon`, `shouldResetSearchState` | +| `openCommandConfirmationModal` | `(params) => Promise<'confirm' \| 'cancel'>` | Show a confirmation modal and wait for the user's response. Params: `title`, `subtitle`, `confirmButtonText`, `confirmButtonAccent` (`'default'`, `'blue'`, `'danger'`) | + +Here is an example that uses the host API to show a snackbar and close the side panel after an action completes: + +```typescript +import { defineFrontComponent, useRecordId } from 'twenty-sdk'; +import { enqueueSnackbar, closeSidePanel } from 'twenty-sdk'; +import { CoreApiClient } from 'twenty-sdk/clients'; + +const ArchiveRecord = () => { + const recordId = useRecordId(); + + const handleArchive = async () => { + const client = new CoreApiClient(); + + await client.mutation({ + updateTask: { + __args: { id: recordId, data: { status: 'ARCHIVED' } }, + id: true, + }, + }); + + await enqueueSnackbar({ + message: 'Record archived', + variant: 'success', + }); + + await closeSidePanel(); + }; + + return ( +
+

Archive this record?

+ +
+ ); +}; + +export default defineFrontComponent({ + universalIdentifier: 'c9d0e1f2-a3b4-5678-cdef-789012345678', + name: 'archive-record', + description: 'Archives the current record', + component: ArchiveRecord, +}); +``` + ### 技能 Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation: diff --git a/packages/twenty-docs/l/zh/developers/extend/extend.mdx b/packages/twenty-docs/l/zh/developers/extend/extend.mdx index d945901ae6..02c4319a29 100644 --- a/packages/twenty-docs/l/zh/developers/extend/extend.mdx +++ b/packages/twenty-docs/l/zh/developers/extend/extend.mdx @@ -1,7 +1,6 @@ --- title: 扩展 description: 使用 API、网络钩子和自定义应用扩展 Twenty 的功能。 -redirect: /developers/introduction --- @@ -16,18 +15,18 @@ Twenty 的设计旨在实现可扩展性。 使用我们的 API、网络钩子 * **API**:使用 REST 或 GraphQL 以编程方式查询和修改您的客户关系管理数据 * **网络钩子**:当 Twenty 中发生事件时接收实时通知 -* **应用**:构建扩展 Twenty 功能的自定义应用程序 - 即将推出! +* **应用**:构建扩展 Twenty 功能的自定义应用程序 ## 开始使用 - + 以编程方式连接到 Twenty - + 实时接收事件通知 - - 以代码方式构建自定义项 (Alpha) + + 以代码方式构建自定义项 diff --git a/packages/twenty-docs/l/zh/developers/extend/webhooks.mdx b/packages/twenty-docs/l/zh/developers/extend/webhooks.mdx new file mode 100644 index 0000000000..a6546b02b3 --- /dev/null +++ b/packages/twenty-docs/l/zh/developers/extend/webhooks.mdx @@ -0,0 +1,116 @@ +--- +title: Webhooks +description: 当您的 CRM 中发生事件时接收实时通知。 +--- + +import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; + +当 Twenty 中发生事件时,Webhook 会实时将数据推送到您的系统 — 无需轮询。 使用它们来保持外部系统同步、触发自动化或发送警报。 + +## 创建 Webhook + +1. 前往 **设置 → APIs & Webhooks → Webhooks** +2. 单击 **+ 创建 Webhook** +3. 输入您的 Webhook URL(必须可公开访问) +4. 单击 **保存** + +Webhook 会立即激活并开始发送通知。 + + + +### 管理 Webhooks + +**编辑**:点击该 Webhook → 更新 URL → **保存** + +**删除**:点击该 Webhook → **删除** → 确认 + +## 事件 + +Twenty 会针对以下事件类型发送 Webhook: + +| 事件 | 示例 | +| --------- | ---------------------------------------------------------- | +| **记录已创建** | `person.created`, `company.created`, `note.created` | +| **记录已更新** | `person.updated`, `company.updated`, `opportunity.updated` | +| **记录已删除** | `person.deleted`, `company.deleted` | + +所有事件类型都会发送到您的 Webhook URL。 事件过滤可能会在未来的版本中添加。 + +## 负载格式 + +每个 Webhook 都会发送一个带有 JSON 正文的 HTTP POST: + +```json +{ + "event": "person.created", + "data": { + "id": "abc12345", + "firstName": "Alice", + "lastName": "Doe", + "email": "alice@example.com", + "createdAt": "2025-02-10T15:30:45Z", + "createdBy": "user_123" + }, + "timestamp": "2025-02-10T15:30:50Z" +} +``` + +| 字段 | 描述 | +| ----------- | -------------------------- | +| `event` | 发生了什么(例如,`person.created`) | +| `data` | 已创建/更新/删除的完整记录 | +| `timestamp` | 事件发生的时间(UTC) | + + +请返回 **2xx HTTP 状态**(200-299)以确认已接收。 非 2xx 的响应将被记录为投递失败。 + + +## Webhook 验证 + +为了安全起见,Twenty 会对每个 Webhook 请求进行签名。 请验证签名以确保请求真实有效。 + +### 请求头 + +| 请求头 | 描述 | +| ---------------------------- | -------------- | +| `X-Twenty-Webhook-Signature` | HMAC SHA256 签名 | +| `X-Twenty-Webhook-Timestamp` | 请求时间戳 | + +### 验证步骤 + +1. 从 `X-Twenty-Webhook-Timestamp` 获取时间戳 +2. 构造字符串:`{timestamp}:{JSON payload}` +3. 使用您的 Webhook 密钥计算 HMAC SHA256 +4. 与 `X-Twenty-Webhook-Signature` 进行比较 + +### 示例(Node.js) + +```javascript +const crypto = require("crypto"); + +const timestamp = req.headers["x-twenty-webhook-timestamp"]; +const payload = JSON.stringify(req.body); +const secret = "your-webhook-secret"; + +const stringToSign = `${timestamp}:${payload}`; +const expectedSignature = crypto + .createHmac("sha256", secret) + .update(stringToSign) + .digest("hex"); + +const receivedSignature = req.headers["x-twenty-webhook-signature"]; +const isValid = crypto.timingSafeEqual( + Buffer.from(expectedSignature, "hex"), + Buffer.from(receivedSignature, "hex") +); +``` + +## Webhooks 与工作流 + +| 方法 | 方向 | 用例 | +| ------------------- | --- | -------------------- | +| **Webhooks** | OUT | 自动通知外部系统任何记录变更 | +| **工作流 + HTTP 请求** | OUT | 使用自定义逻辑(过滤、转换)向外发送数据 | +| **工作流 Webhook 触发器** | IN | 从外部系统接收数据到 Twenty | + +如需接收外部数据,请参见 [设置 Webhook 触发器](/l/zh/user-guide/workflows/how-tos/connect-to-other-tools/set-up-a-webhook-trigger)。 diff --git a/packages/twenty-docs/l/zh/developers/introduction.mdx b/packages/twenty-docs/l/zh/developers/introduction.mdx index 616c99dfec..6906d4334b 100644 --- a/packages/twenty-docs/l/zh/developers/introduction.mdx +++ b/packages/twenty-docs/l/zh/developers/introduction.mdx @@ -5,28 +5,18 @@ description: 欢迎来到 Twenty 开发者文档,这是您用于扩展、自 import { CardTitle } from "/snippets/card-title.mdx" - - - API - 使用 REST 或 GraphQL 查询和修改您的 CRM 数据。 + + + 扩展 + 使用 API、网络钩子和自定义应用构建集成。 - - Webhooks - 当事件发生时接收实时通知。 - - - - Apps - 构建扩展 Twenty 功能的定制应用程序。 - - - + 自托管 在您自己的基础设施上部署并管理 Twenty。 - + 贡献 加入我们的开源社区并为 Twenty 做出贡献。 diff --git a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/export-your-data.mdx b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/export-your-data.mdx index f128dcbbdc..4a6d66bf04 100644 --- a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/export-your-data.mdx +++ b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/export-your-data.mdx @@ -24,7 +24,7 @@ import { VimeoEmbed } from '/snippets/vimeo-embed.mdx'; * 仅导出**可见列** * 仅导出**筛选后的记录**(基于您当前视图) -对于更大的导出(20,000+ 条记录),请使用筛选器分批导出,或使用 [API](/l/zh/developers/api)。 +对于更大的导出(20,000+ 条记录),请使用筛选器分批导出,或使用 [API](/l/zh/developers/extend/api)。 ### 权限 @@ -148,7 +148,7 @@ API 没有记录数量限制: 2. 使用 GraphQL API 查询记录 3. 在您的应用程序中处理结果 -参见:[API 文档](/l/zh/developers/api) +参见:[API 文档](/l/zh/developers/extend/api) ## 技巧与最佳实践 @@ -206,4 +206,4 @@ API 没有记录数量限制: * [如何更新现有记录](/l/zh/user-guide/data-migration/how-tos/update-existing-records-via-import) — 编辑并重新导入您的导出文件 * [如何通过 API 导入数据](/l/zh/user-guide/data-migration/how-tos/import-data-via-api) — 适用于大型数据集 -* [API 文档](/l/zh/developers/api) — 构建自定义导出工作流 +* [API 文档](/l/zh/developers/extend/api) — 构建自定义导出工作流 diff --git a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/import-data-via-api.mdx b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/import-data-via-api.mdx index 0dc5b07b60..7984332760 100644 --- a/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/import-data-via-api.mdx +++ b/packages/twenty-docs/l/zh/user-guide/data-migration/how-tos/import-data-via-api.mdx @@ -57,10 +57,10 @@ Twenty 实施速率限制以确保系统稳定性: Twenty 支持两种 API 类型: -| 接口 | 最适合 | 文档 | -| ----------- | ----------------------- | ---------------------------------------------- | -| **GraphQL** | 灵活查询、获取关联数据、复杂操作 | [API 文档](/l/zh/developers/api) | -| **REST** | 简单的 CRUD 操作、熟悉的 REST 模式 | [API 文档](/l/zh/developers/api) | +| 接口 | 最适合 | 文档 | +| ----------- | ----------------------- | -------------------------------- | +| **GraphQL** | 灵活查询、获取关联数据、复杂操作 | [API 文档](/l/zh/developers/extend/api) | +| **REST** | 简单的 CRUD 操作、熟悉的 REST 模式 | [API 文档](/l/zh/developers/extend/api) | 两种 API 都支持: @@ -173,4 +173,4 @@ GraphQL API 支持**批量合并插入** — 若记录已存在则更新,不 有关完整的实现细节、代码示例和架构参考: -* [API 文档](/l/zh/developers/api) +* [API 文档](/l/zh/developers/extend/api) diff --git a/packages/twenty-docs/l/zh/user-guide/getting-started/capabilities/what-is-twenty.mdx b/packages/twenty-docs/l/zh/user-guide/getting-started/capabilities/what-is-twenty.mdx index 38e59f38b9..1d01a91d23 100644 --- a/packages/twenty-docs/l/zh/user-guide/getting-started/capabilities/what-is-twenty.mdx +++ b/packages/twenty-docs/l/zh/user-guide/getting-started/capabilities/what-is-twenty.mdx @@ -35,7 +35,7 @@ description: Twenty 是一款开源 CRM,为您提供构建模块,助您精 * **仪表板:** 通过自定义报表和可视化跟踪绩效。 [查看仪表板](/l/zh/user-guide/dashboards/overview)。 * **权限与访问:** 通过基于角色的权限控制谁可以查看、编辑和管理您的数据。 [配置访问](/l/zh/user-guide/permissions-access/overview)。 * **笔记与任务:** 创建与您的记录关联的笔记和任务,以便更好地协作。 -* **API 与 Webhook:** 连接到其他应用,并构建自定义集成。 [开始集成](/l/zh/developers/api)。 +* **API 与 Webhook:** 连接到其他应用,并构建自定义集成。 [开始集成](/l/zh/developers/extend/api)。 ## 立即加入 diff --git a/packages/twenty-docs/l/zh/user-guide/workflows/how-tos/crm-automations/display-related-record-data.mdx b/packages/twenty-docs/l/zh/user-guide/workflows/how-tos/crm-automations/display-related-record-data.mdx index d4a0f02e91..3a43bdb1bc 100644 --- a/packages/twenty-docs/l/zh/user-guide/workflows/how-tos/crm-automations/display-related-record-data.mdx +++ b/packages/twenty-docs/l/zh/user-guide/workflows/how-tos/crm-automations/display-related-record-data.mdx @@ -95,7 +95,7 @@ description: 使用工作流显示关联记录中的数据(例如,在机会 * 公司规模:`{{searchRecords[0].employees}}` -**任务和备注限制**:任务和备注上的关系被硬编码为多对多,目前尚不可用于工作流触发器或操作。 要访问这些关系,请改用 [API](/l/zh/developers/api)。 +**任务和备注限制**:任务和备注上的关系被硬编码为多对多,目前尚不可用于工作流触发器或操作。 要访问这些关系,请改用 [API](/l/zh/developers/extend/api)。 ## 双向同步