@lark-apaas/coding-steering 0.1.6-alpha.8 → 0.1.6-alpha.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/steering/design-stack/skills/.gitkeep +0 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/SKILL.md +174 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/dynamic-permission-guide.md +621 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/management-page-spec.md +505 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/runtime-role-controller-spec.md +203 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-examples.md +90 -0
- package/steering/nestjs-react-fullstack/skills/authz-guide/references/sdk-types.md +216 -0
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +405 -0
- package/steering/nestjs-react-fullstack/skills/devops-guide/SKILL.md +119 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +582 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +357 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/table.md +513 -0
- package/steering/nestjs-react-fullstack/skills/react-hook-best-practices/SKILL.md +118 -0
- package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +177 -0
- package/steering/nestjs-react-fullstack/skills/user-management-best-practices/SKILL.md +142 -0
- package/steering/design-stack/skills/client-add-aily-web-chat/SKILL.md +0 -139
- package/steering/design-stack/skills/client-builtins-user-service/SKILL.md +0 -628
- package/steering/design-stack/skills/code-fix/SKILL.md +0 -246
- package/steering/design-stack/skills/feishu/SKILL.md +0 -270
- package/steering/design-stack/skills/feishu/references/approval.md +0 -214
- package/steering/design-stack/skills/feishu/references/attendance.md +0 -163
- package/steering/design-stack/skills/feishu/references/bitable.md +0 -309
- package/steering/design-stack/skills/feishu/references/calendar.md +0 -190
- package/steering/design-stack/skills/feishu/references/contacts.md +0 -160
- package/steering/design-stack/skills/feishu/references/doc.md +0 -256
- package/steering/design-stack/skills/feishu/references/drive.md +0 -103
- package/steering/design-stack/skills/feishu/references/events.md +0 -198
- package/steering/design-stack/skills/feishu/references/id-convert.md +0 -128
- package/steering/design-stack/skills/feishu/references/messaging.md +0 -207
- package/steering/design-stack/skills/feishu/references/oauth.md +0 -164
- package/steering/design-stack/skills/feishu/references/perm.md +0 -90
- package/steering/design-stack/skills/feishu/references/wiki.md +0 -164
- package/steering/design-stack/skills/user-identity/SKILL.md +0 -300
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: server-builtins-file-storage-service
|
|
3
|
+
description: "Use when server-side code needs to upload or download files programmatically, such as file format conversion, automated report generation, or background tasks that produce files. NOT for frontend file operations. 触发词:服务端文件上传, 服务端文件下载, FileService, nestjs file, 后端文件处理, 文件格式转换, 生成文件上传"
|
|
4
|
+
steering: true
|
|
5
|
+
steering-topic: server_builtins_file_storage_service
|
|
6
|
+
match-template-name: nestjs-react-fullstack
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# NestJS File Service SDK
|
|
10
|
+
|
|
11
|
+
在 NestJS 服务端代码中使用 `FileService` 进行文件上传、下载、删除和管理。
|
|
12
|
+
|
|
13
|
+
> **使用注意**: 仅用于服务端文件处理场景,非必要文件上传下载场景请使用前端 `client-builtins-file-storage-service` skill。
|
|
14
|
+
> CLI 调试场景请参考 `file-storage` skill。
|
|
15
|
+
|
|
16
|
+
## 使用场景
|
|
17
|
+
|
|
18
|
+
| 场景 | 说明 |
|
|
19
|
+
|------|------|
|
|
20
|
+
| 服务端文件处理 | 下载文件到本地处理后重新上传,如图片格式转换、docx 格式转换等 |
|
|
21
|
+
| 自动化任务生成文件 | 定时任务或事件触发后,根据数据库/插件内容生成文件并上传,如定时生成 PDF 报告、图片海报等 |
|
|
22
|
+
|
|
23
|
+
## Quick Reference
|
|
24
|
+
|
|
25
|
+
| 方法 | 说明 | 返回值 |
|
|
26
|
+
|------|------|--------|
|
|
27
|
+
| `upload(file, options?)` | 上传文件 | `FileMeta` |
|
|
28
|
+
| `download(path)` | 下载文件(≤50MB) | `FileDownloadBuilder` |
|
|
29
|
+
| `remove(filePaths)` | 删除文件 | `RemoveResponse` |
|
|
30
|
+
| `getFileMetadata(filePath)` | 获取文件元信息 | `FileMeta` |
|
|
31
|
+
|
|
32
|
+
## 注入 FileService
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { FileService } from '@lark-apaas/fullstack-nestjs-core';
|
|
36
|
+
|
|
37
|
+
@Injectable()
|
|
38
|
+
export class MyService {
|
|
39
|
+
constructor(private readonly fileService: FileService) {}
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## 公共类型
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
interface FileMeta {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
filePath: string;
|
|
50
|
+
metadata: { contentLength: string; mimeType: string };
|
|
51
|
+
downloadURL: string;
|
|
52
|
+
createdAt: string;
|
|
53
|
+
updatedAt: string;
|
|
54
|
+
bucketID: string;
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 核心操作
|
|
59
|
+
|
|
60
|
+
### 1. 上传文件
|
|
61
|
+
|
|
62
|
+
**入参:**
|
|
63
|
+
|
|
64
|
+
| 参数 | 类型 | 必需 | 说明 |
|
|
65
|
+
|------|------|------|------|
|
|
66
|
+
| `file` | `FileBody` | 是 | 文件内容(支持 `Buffer`、`ReadableStream`、`ArrayBuffer`、`Blob`、`string` 等) |
|
|
67
|
+
| `options` | `UploadOptions` | 否 | 上传选项 |
|
|
68
|
+
|
|
69
|
+
**UploadOptions:**
|
|
70
|
+
|
|
71
|
+
| 字段 | 类型 | 必需 | 说明 |
|
|
72
|
+
|------|------|------|------|
|
|
73
|
+
| `fileName` | `string` | 否 | 文件名称 |
|
|
74
|
+
| `contentType` | `string` | 否 | MIME 类型 |
|
|
75
|
+
| `cacheControl` | `string \| number` | 否 | 缓存控制 |
|
|
76
|
+
| `upsert` | `boolean` | 否 | 是否覆盖已有文件 |
|
|
77
|
+
|
|
78
|
+
**返回值:** `FileMeta`
|
|
79
|
+
|
|
80
|
+
**示例:**
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// 基本上传
|
|
84
|
+
const result = await this.fileService.upload(buffer);
|
|
85
|
+
|
|
86
|
+
// 带选项上传
|
|
87
|
+
const result = await this.fileService.upload(fileBody, {
|
|
88
|
+
fileName: 'report.pdf',
|
|
89
|
+
contentType: 'application/pdf',
|
|
90
|
+
upsert: false,
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 2. 下载文件
|
|
95
|
+
|
|
96
|
+
**入参:**
|
|
97
|
+
|
|
98
|
+
| 参数 | 类型 | 必需 | 说明 |
|
|
99
|
+
|------|------|------|------|
|
|
100
|
+
| `path` | `string` | 是 | downloadURL 或文件存储路径 |
|
|
101
|
+
|
|
102
|
+
**返回值 `DownloadResult`:**
|
|
103
|
+
|
|
104
|
+
| 字段 | 类型 | 说明 |
|
|
105
|
+
|------|------|------|
|
|
106
|
+
| `content` | `Blob`(默认)/ `ReadableStream`(流式) | 文件内容 |
|
|
107
|
+
| `metadata` | `FileMeta` | 文件元信息 |
|
|
108
|
+
|
|
109
|
+
> **推荐始终使用 `.asStream()`**,避免大文件导致内存溢出。直接 `await download()` 有 50MB 限制。
|
|
110
|
+
|
|
111
|
+
**示例:**
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// 推荐:使用 downloadURL + 流式下载
|
|
115
|
+
const downloadURL = fileMeta.downloadURL; // 从上传返回值或数据库获取
|
|
116
|
+
const { content, metadata } = await this.fileService
|
|
117
|
+
.download(downloadURL)
|
|
118
|
+
.asStream();
|
|
119
|
+
|
|
120
|
+
// 也可以使用文件存储路径
|
|
121
|
+
const { content, metadata } = await this.fileService
|
|
122
|
+
.download('file.pdf')
|
|
123
|
+
.asStream();
|
|
124
|
+
|
|
125
|
+
// 不推荐:Blob 下载(限制 50MB,会将整个文件加载到内存)
|
|
126
|
+
const { content, metadata } = await this.fileService.download(downloadURL);
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 3. 删除文件
|
|
130
|
+
|
|
131
|
+
**入参:**
|
|
132
|
+
|
|
133
|
+
| 参数 | 类型 | 必需 | 说明 |
|
|
134
|
+
|------|------|------|------|
|
|
135
|
+
| `filePaths` | `string[]` | 是 | downloadURL 或文件存储路径数组 |
|
|
136
|
+
|
|
137
|
+
**返回值:** `FileMeta[]` - 删除成功的文件元信息数组
|
|
138
|
+
|
|
139
|
+
**示例:**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// 推荐:使用 downloadURL
|
|
143
|
+
const result = await this.fileService.remove([fileMeta.downloadURL]);
|
|
144
|
+
|
|
145
|
+
// 也可以使用文件存储路径
|
|
146
|
+
const result = await this.fileService.remove(['file1.pdf', 'file2.png']);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### 4. 获取文件元信息
|
|
150
|
+
|
|
151
|
+
**入参:**
|
|
152
|
+
|
|
153
|
+
| 参数 | 类型 | 必需 | 说明 |
|
|
154
|
+
|------|------|------|------|
|
|
155
|
+
| `filePath` | `string` | 是 | downloadURL 或文件存储路径 |
|
|
156
|
+
|
|
157
|
+
**返回值:** `FileMeta | null` - 文件元信息或 `null` 表示文件不存在或删除失败
|
|
158
|
+
|
|
159
|
+
**示例:**
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
// 推荐:使用 downloadURL
|
|
163
|
+
const meta = await this.fileService.getFileMetadata(fileMeta.downloadURL);
|
|
164
|
+
|
|
165
|
+
// 也可以使用文件存储路径
|
|
166
|
+
const meta = await this.fileService.getFileMetadata('file.pdf');
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Common Mistakes
|
|
170
|
+
|
|
171
|
+
| 错误 | 正确做法 |
|
|
172
|
+
|------|----------|
|
|
173
|
+
| 直接 `await download()` 导致内存溢出 | 始终使用 `.asStream()` 流式下载 |
|
|
174
|
+
| 忘记设置 `contentType` 导致浏览器无法预览 | 上传时明确指定 `contentType` |
|
|
175
|
+
| 直接 `new FileService()` 手动实例化 | 通过 NestJS DI 注入 `FileService` |
|
|
176
|
+
| 在异步回调中调用 `download()` 导致上下文丢失 | 在请求处理函数中立即调用,SDK 内部已处理上下文捕获 |
|
|
177
|
+
| 删除时传单个字符串 | `remove()` 参数为 `string[]` 数组 |
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: user-management-best-practices
|
|
3
|
+
description: "Use when 决定是否创建用户表、选择用户唯一标识(user_id vs email/手机号)、设计 user_profile 字段与 UNIQUE 约束、处理防重复提交/upsert 去重,或涉及问卷、抽奖、报名、投票等用户提交场景的表结构设计。触发词:用户管理, 用户表设计, user_id, email, user_profile, 去重, 防重复提交, 批量导入用户, upsert, UNIQUE约束, UserSelect, UserDisplay, user management, deduplication"
|
|
4
|
+
steering: true
|
|
5
|
+
steering-topic: user_management_best_practices
|
|
6
|
+
match-template-name: nestjs-react-fullstack
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# 用户管理最佳实践指南
|
|
10
|
+
|
|
11
|
+
## 概述
|
|
12
|
+
|
|
13
|
+
本指南适用于生成涉及用户管理、问卷、抽奖、报名、投票等用户提交场景的代码。
|
|
14
|
+
|
|
15
|
+
**核心问题:**
|
|
16
|
+
|
|
17
|
+
- 妙搭 Agent 无法获取企业的所有用户数据,需要构建应用维度的用户表
|
|
18
|
+
- 企业无法显式感知飞书的 user_id(不透明的数字字符串,如 "1847292357012580"),无法批量导入飞书用户到应用的用户表
|
|
19
|
+
- 推荐使用邮箱或者手机号作为业务层唯一标识(不是 user_id)
|
|
20
|
+
- UserSelect 返回的 IUserProfile 对象已包含 email 和 phone_number 字段,可直接使用
|
|
21
|
+
|
|
22
|
+
**本指南解决:**
|
|
23
|
+
|
|
24
|
+
- 何时使用 UserSelect 组件 vs 何时创建用户表
|
|
25
|
+
- 如何设计用户业务表(正确的唯一约束)
|
|
26
|
+
- 防止重复提交的正确实现方式
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## 核心原则:禁止事项
|
|
31
|
+
|
|
32
|
+
| 禁止操作 | 说明 | 正确做法 |
|
|
33
|
+
| ------------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------- |
|
|
34
|
+
| 不推荐实现批量导入用户的功能 | Agent 无法获取企业全量用户,批量 INSERT users 表行不通 | 推荐使用 UserSelect 组件动态选择用户 |
|
|
35
|
+
| 不推荐用 user_id 作为去重逻辑 | 企业不感知飞书用户 id,无法感知是谁 | 使用 email 或者手机号做唯一约束(UNIQUE(email)),不要使用姓名,可能会重复 |
|
|
36
|
+
| 禁止编造 user_id | Mock 数据中不能随意编造 user_id 值 | 必须使用 SQL skill 规定的测试用户列表 |
|
|
37
|
+
| 禁止全量查询用户列表 | 全量拉取数据会导致接口超时或 OOM | 使用后端分页查询(skip/take) |
|
|
38
|
+
| 禁止前端分页用户列表 | 前端分页需要全量拉取数据,导致性能问题 | 使用后端分页,前端仅控制页码 |
|
|
39
|
+
| 禁止只存 user_id 不存 email or 手机号 | 业务层无法用 user_id 做防重和查询 | 存储 user_profile 字段(已包含 email/phone),并在表中单独添加 email 或 phone 列用于唯一约束 |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 决策树示例:何时创建用户表 vs 何时使用 user_profile
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
需要记录用户相关业务数据?
|
|
47
|
+
│
|
|
48
|
+
├─ 仅需要识别"谁"执行操作(发帖人、评论者)
|
|
49
|
+
│ └─ ✅ 直接使用 user_profile 类型字段
|
|
50
|
+
│ 示例:posts.author(user_profile)
|
|
51
|
+
│
|
|
52
|
+
└─ 需要存储业务特定的用户数据
|
|
53
|
+
│
|
|
54
|
+
├─ 数据与具体业务资源强绑定(问卷提交、抽奖记录)
|
|
55
|
+
│ └─ ✅ 创建业务表 + user_profile 字段 + email 唯一约束
|
|
56
|
+
│ 示例:questionnaire_responses (id, respondent, email, UNIQUE(questionnaire_id, email))
|
|
57
|
+
│
|
|
58
|
+
└─ 数据代表"用户在系统中的身份"(员工、学生、会员)
|
|
59
|
+
└─ ✅ 创建人员实体表,user_profile 作为主键
|
|
60
|
+
示例:employees (employee_profile PRIMARY KEY, department, position)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 用户列表查询与性能注意事项
|
|
66
|
+
|
|
67
|
+
### UserSelect vs 分页列表的选择
|
|
68
|
+
|
|
69
|
+
根据场景选择正确的用户选择方式:
|
|
70
|
+
|
|
71
|
+
| 场景 | 使用组件 | 数据加载方式 | 典型数量 |
|
|
72
|
+
| ------------------ | ---------------- | ------------ | -------- |
|
|
73
|
+
| 表单选择用户 | UserSelect | 搜索驱动 | 1-10 |
|
|
74
|
+
| 用户管理后台 | Table + 后端分页 | 分页加载 | 100+ |
|
|
75
|
+
| 问卷提交记录列表 | Table + 后端分页 | 分页加载 | 100+ |
|
|
76
|
+
| 抽奖参与者名单展示 | Table + 后端分页 | 分页加载 | 100+ |
|
|
77
|
+
|
|
78
|
+
- **UserSelect 组件**:搜索驱动,返回完整 IUserProfile 对象(含 email、phone_number)。详细用法参考 **client-builtins-user-service**
|
|
79
|
+
- **Table + 后端分页**:详细实现参考 **table-skill**
|
|
80
|
+
|
|
81
|
+
### 分页实现
|
|
82
|
+
|
|
83
|
+
用户列表必须使用后端分页,禁止前端全量拉取。具体实现参考 **table-skill**。
|
|
84
|
+
|
|
85
|
+
### 动态创建用户记录
|
|
86
|
+
|
|
87
|
+
**何时动态创建:**
|
|
88
|
+
|
|
89
|
+
1. **首次提交场景**:用户首次提交问卷/参与抽奖时,使用 `upsert` 方法
|
|
90
|
+
2. **首次登录场景**:用户首次登录系统时,创建用户记录
|
|
91
|
+
|
|
92
|
+
## 常见错误速查表
|
|
93
|
+
|
|
94
|
+
| 错误模式 | 问题 | 正确做法 | 影响 |
|
|
95
|
+
| ----------------------- | ---------------------- | ------------------------------------------------------------ | ----------- |
|
|
96
|
+
| 批量 INSERT users | Agent 无法获取全量用户 | 使用 UserSelect 组件 | 🔴 CRITICAL |
|
|
97
|
+
| `UNIQUE(user_id)` | 企业不认识 user_id | `UNIQUE(email)` | 🔴 CRITICAL |
|
|
98
|
+
| 编造 user_id | Mock 数据无效 | 使用测试用户列表 | 🔴 CRITICAL |
|
|
99
|
+
| 全量查询用户列表 | 接口超时或 OOM | 使用后端分页(skip/take) | 🔴 CRITICAL |
|
|
100
|
+
| 前端分页(全量拉取) | 首次加载超时 | 使用后端分页 | 🔴 CRITICAL |
|
|
101
|
+
| 只存 user_id 不存 email | 无法做业务层防重 | 存储 user_profile(已含 email),并添加 email 列用于唯一约束 | 🟠 BUG |
|
|
102
|
+
| 直接显示 user_id | 用户体验差 | 使用 UserDisplay 组件 | 🟡 UX |
|
|
103
|
+
| 创建 users 基础表 | 无法同步飞书数据 | 使用 user_profile 类型 | 🟠 DESIGN |
|
|
104
|
+
|
|
105
|
+
## 相关技能参考
|
|
106
|
+
|
|
107
|
+
- **client-builtins-user-service**: UserSelect 详细用法、IUserProfile 类型定义、UserDisplay 组件、useCurrentUserProfile hook
|
|
108
|
+
- **sql**: user_profile 类型约束、JSONB 验证规则、测试用户列表
|
|
109
|
+
- **table-skill**: Table 组件使用、后端分页实现、前端分页最佳实践
|
|
110
|
+
- **authz-guide**: 结合基于角色的访问控制(如:只允许用户查看/编辑自己的提交)
|
|
111
|
+
|
|
112
|
+
## 自查清单
|
|
113
|
+
|
|
114
|
+
### 表设计
|
|
115
|
+
|
|
116
|
+
- [ ] 判断是否需要创建用户表(参考决策树)
|
|
117
|
+
- [ ] 人员实体表使用 `user_profile PRIMARY KEY`
|
|
118
|
+
- [ ] 业务表使用 `id UUID PRIMARY KEY` + `user_profile` 字段
|
|
119
|
+
- [ ] 业务表包含 `email VARCHAR(255)` or `phone VARCHAR(255)` 字段
|
|
120
|
+
- [ ] 设置了正确的唯一约束(使用 email or 手机号,如 `UNIQUE(resource_id, email)`)
|
|
121
|
+
|
|
122
|
+
### 前端实现
|
|
123
|
+
|
|
124
|
+
- [ ] 使用 UserSelect 组件而非手动输入
|
|
125
|
+
- [ ] 使用 UserDisplay 展示用户(不直接显示 user_id)
|
|
126
|
+
- [ ] 用户列表使用后端分页(不全量拉取)
|
|
127
|
+
- [ ] Table 组件的 onChange 触发后端请求
|
|
128
|
+
|
|
129
|
+
### 后端实现
|
|
130
|
+
|
|
131
|
+
- [ ] Entity 定义了正确的唯一约束(`@Unique(['resourceId', 'email'])` or `@Unique(['resourceId', 'phone'])`)
|
|
132
|
+
- [ ] 错误处理包含唯一约束冲突(409 Conflict)
|
|
133
|
+
- [ ] 存储 user_profile 字段(已包含 email/phone)
|
|
134
|
+
- [ ] 在表中单独添加 email 或 phone 列,用于唯一约束和索引查询
|
|
135
|
+
- [ ] 列表接口使用 skip/take 分页查询
|
|
136
|
+
- [ ] 返回 pagination 对象(page, pageSize, total)
|
|
137
|
+
|
|
138
|
+
### Mock 数据
|
|
139
|
+
|
|
140
|
+
- [ ] user_id 使用测试用户列表(不编造)
|
|
141
|
+
- [ ] user_profile 中的 email/phone 与 user_id 对应正确
|
|
142
|
+
- [ ] 测试了重复提交场景(唯一约束生效)
|
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: client-add-aily-web-chat
|
|
3
|
-
description: Use when integrating Aily AI chat panel into a web app using @lark-apaas/client-toolkit/aily-chat. 触发词:Aily 聊天, Aily 对话面板, Aily chat, aily-chat SDK, AI 对话组件集成
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 添加 Aily Web 对话面板
|
|
7
|
-
|
|
8
|
-
使用 `@lark-apaas/client-toolkit/aily-chat` 将 Aily 对话面板集成到 Web 应用。SDK 与框架无关(纯 DOM 操作)。
|
|
9
|
-
|
|
10
|
-
## Quick Reference
|
|
11
|
-
|
|
12
|
-
| 操作 | 方法 |
|
|
13
|
-
|------|------|
|
|
14
|
-
| 初始化 | `await initAilyChat(container, config)` |
|
|
15
|
-
| 发送消息 | `chatPanel.sendMessage({ content, skillID? })` |
|
|
16
|
-
| 清空记录 | `chatPanel.clear()` |
|
|
17
|
-
| 清空并停止 | `chatPanel.clearAndStop()` |
|
|
18
|
-
| 更新配置 | `chatPanel.updateConfig(partialConfig)` |
|
|
19
|
-
| 销毁 | `chatPanel.destroy()` |
|
|
20
|
-
|
|
21
|
-
## 核心流程
|
|
22
|
-
|
|
23
|
-
### 1. 向用户获取 appKey
|
|
24
|
-
|
|
25
|
-
实现前必须先询问用户:
|
|
26
|
-
|
|
27
|
-
> "请提供你的 Aily `appKey` 以配置对话组件。你可以在 [Aily 平台](https://apaas.feishu.cn/ai/projects) 中找到对应应用的 appKey。"
|
|
28
|
-
|
|
29
|
-
### 2. 集成代码
|
|
30
|
-
|
|
31
|
-
React 组件示例(vanilla JS 同理,对 DOM 元素调用 `initAilyChat` 即可):
|
|
32
|
-
|
|
33
|
-
```tsx
|
|
34
|
-
import { useEffect, useRef } from "react";
|
|
35
|
-
import { initAilyChat, type ChatPanel } from "@lark-apaas/client-toolkit/aily-chat";
|
|
36
|
-
|
|
37
|
-
interface AilyChatProps {
|
|
38
|
-
appKey: string;
|
|
39
|
-
anonymous?: boolean;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const containerStyle = { width: "100%", height: "500px" };
|
|
43
|
-
|
|
44
|
-
const AilyChat = ({ appKey, anonymous }: AilyChatProps) => {
|
|
45
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
46
|
-
const chatPanelRef = useRef<ChatPanel | null>(null);
|
|
47
|
-
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (!containerRef.current) return;
|
|
50
|
-
let destroyed = false;
|
|
51
|
-
|
|
52
|
-
initAilyChat(containerRef.current, {
|
|
53
|
-
appKey,
|
|
54
|
-
anonymous,
|
|
55
|
-
conversion: { needAvatar: true },
|
|
56
|
-
events: {
|
|
57
|
-
onReady: () => console.log("对话就绪"),
|
|
58
|
-
onError: (err) => console.error("对话错误", err),
|
|
59
|
-
},
|
|
60
|
-
}).then((panel) => {
|
|
61
|
-
if (destroyed) panel.destroy();
|
|
62
|
-
else chatPanelRef.current = panel;
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
return () => {
|
|
66
|
-
destroyed = true;
|
|
67
|
-
chatPanelRef.current?.destroy();
|
|
68
|
-
chatPanelRef.current = null;
|
|
69
|
-
};
|
|
70
|
-
}, [appKey, anonymous]);
|
|
71
|
-
|
|
72
|
-
return <div ref={containerRef} style={containerStyle} />;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
export default AilyChat;
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
### 3. 告知用户 UI 定制选项
|
|
79
|
-
|
|
80
|
-
代码集成完成后,**必须**向用户展示以下可定制选项:
|
|
81
|
-
|
|
82
|
-
- 修改组件标题/顶部标题
|
|
83
|
-
- 关闭/开启组件标题栏
|
|
84
|
-
- 显示/隐藏关闭按钮
|
|
85
|
-
- 隐藏/展示用户头像、隐藏/展示系统头像
|
|
86
|
-
- 修改用户头像或系统头像,并发送新头像图片
|
|
87
|
-
- 隐藏/展示组件欢迎语
|
|
88
|
-
|
|
89
|
-
## 配置参考
|
|
90
|
-
|
|
91
|
-
### WebSDKConfig(顶层)
|
|
92
|
-
|
|
93
|
-
| 字段 | 类型 | 必填 | 说明 |
|
|
94
|
-
|------|------|------|------|
|
|
95
|
-
| `appKey` | string | 是 | 应用标识 |
|
|
96
|
-
| `uuid` | string | 否 | 用户标识,启用 `needCache` 时必填 |
|
|
97
|
-
| `anonymous` | boolean | 否 | `true` 开启匿名模式,跳过登录。**须在 Aily 应用「渠道管理」中将 SDK 模式开启匿名访问,否则会报错「匿名SDK没有开启支持使用应用身份调用API和SDK」** |
|
|
98
|
-
| `conversion` | object | 否 | 对话配置(头像、缓存、欢迎语) |
|
|
99
|
-
| `editor` | object | 否 | 顶部栏配置(标题、关闭按钮) |
|
|
100
|
-
| `events` | object | 否 | 事件回调 |
|
|
101
|
-
|
|
102
|
-
### conversion
|
|
103
|
-
|
|
104
|
-
| 字段 | 类型 | 默认值 | 说明 |
|
|
105
|
-
|------|------|--------|------|
|
|
106
|
-
| `needCache` | boolean | `false` | 启用消息缓存(需同时提供 `uuid`) |
|
|
107
|
-
| `storageType` | string | `"memory"` | `memory` / `sessionStorage` / `localStorage` / `indexDB` / `server` |
|
|
108
|
-
|
|
109
|
-
> 头像和欢迎语相关字段见上方「UI 定制选项」表。
|
|
110
|
-
|
|
111
|
-
### editor
|
|
112
|
-
|
|
113
|
-
| 字段 | 类型 | 默认值 | 说明 |
|
|
114
|
-
|------|------|--------|------|
|
|
115
|
-
| `display` | boolean | `true` | 是否显示编辑器 |
|
|
116
|
-
| `needHeader` | boolean | `true` | 是否显示顶部 header(含标题和新建对话按钮),仅 V2 生效 |
|
|
117
|
-
| `headerTitle` | string | 应用名称 | 对话标题,显示在 header 中 |
|
|
118
|
-
| `needCloseButton` | boolean | `false` | 是否显示关闭按钮。配合 `events.onClose` 监听点击 |
|
|
119
|
-
|
|
120
|
-
### events
|
|
121
|
-
|
|
122
|
-
| 事件 | 签名 | 说明 |
|
|
123
|
-
|------|------|------|
|
|
124
|
-
| `onReady` | `() => void` | 对话就绪 |
|
|
125
|
-
| `onError` | `(error: Error) => void` | 发生错误 |
|
|
126
|
-
| `onMessage` | `(message: unknown) => void` | 收到消息 |
|
|
127
|
-
| `onInited` | `(data: { sessionId, conversationId, handler }) => void` | 会话已创建 |
|
|
128
|
-
| `onInitedWithWelcome` | `() => void` | 欢迎语已创建 |
|
|
129
|
-
| `onClose` | `() => void` | 用户点击关闭按钮时触发(需配合 `editor.needCloseButton: true`) |
|
|
130
|
-
|
|
131
|
-
## Common Mistakes
|
|
132
|
-
|
|
133
|
-
| 错误 | 正确做法 |
|
|
134
|
-
|------|----------|
|
|
135
|
-
| 忘记在 useEffect 清理时调用 `destroy()` | 必须在 cleanup 中销毁 ChatPanel,否则内存泄漏 |
|
|
136
|
-
| 启用 `needCache` 但未传 `uuid` | 缓存依赖 `uuid` 识别用户,缺失则无法恢复历史消息 |
|
|
137
|
-
| 容器元素无固定宽高 | 容器必须有明确尺寸,否则面板不可见 |
|
|
138
|
-
| 在 React 严格模式下未处理重复初始化 | 用 `destroyed` 标志防止 StrictMode 双重 mount 导致重复面板 |
|
|
139
|
-
| 设置 `anonymous: true` 但未在 Aily 平台开启匿名访问 | 须前往 Aily 应用「渠道管理」将 SDK 模式开启匿名访问,否则报错「匿名SDK没有开启支持使用应用身份调用API和SDK」 |
|