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)查看一个最小的端到端示例,展示对象、逻辑函数、前端组件和多种触发器。