@lark-apaas/coding-steering 0.1.32-dev.b2f659e → 0.1.32-dev.cb02bd3
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-html/skills/charts/SKILL.md +11 -4
- package/steering/design-html/skills/data-viz/SKILL.md +1 -1
- package/steering/design-html/skills/hi-fi-design/SKILL.md +1 -1
- package/steering/design-html/skills/interactive-prototype/SKILL.md +1 -17
- package/steering/design-html/skills/pptx-style-extract/SKILL.md +1 -1
- package/steering/design-html/skills/pptx-style-extract/scripts/draft.py +21 -1
- package/steering/design-html/skills/pptx-style-extract/scripts/test_layout_css.py +120 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/test_logo_scope.py +121 -0
- package/steering/design-html/skills/pptx-style-extract/scripts/verify_layout_assets.py +24 -3
- package/steering/design-html/skills/slide-deck/SKILL.md +5 -3
- package/steering/design-html/skills/social-card/SKILL.md +263 -0
- package/steering/nestjs-react-fullstack/skills/app-init-feasibility-guide/SKILL.md +3 -0
- package/steering/nestjs-react-fullstack/skills/client-builtins-file-storage-service/SKILL.md +2 -1
- package/steering/nestjs-react-fullstack/skills/client-builtins-user-service/SKILL.md +2 -0
- package/steering/nestjs-react-fullstack/skills/code-fix/SKILL.md +6 -0
- package/steering/nestjs-react-fullstack/skills/coding-guide/SKILL.md +56 -14
- package/steering/nestjs-react-fullstack/skills/contacts-service/SKILL.md +67 -4
- package/steering/nestjs-react-fullstack/skills/nestjs-cache/SKILL.md +0 -1
- package/steering/nestjs-react-fullstack/skills/openapi-guide/SKILL.md +75 -0
- package/steering/nestjs-react-fullstack/skills/plugin-guide/SKILL.md +10 -4
- package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md +57 -14
- package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md +1 -1
- package/steering/nestjs-react-fullstack/skills_common/user-identity/SKILL.md +3 -1
- package/steering/nestjs-react-fullstack/skills_local/client-builtins-user-service/SKILL.md +2 -0
- package/steering/nestjs-react-fullstack/skills_local/code-fix/SKILL.md +6 -0
- package/steering/nestjs-react-fullstack/skills_local/coding-guide/SKILL.md +49 -22
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/SKILL.md +7 -3
- package/steering/nestjs-react-fullstack/skills_local/plugin-guide/references/plugin-coding-guide.md +57 -14
- package/steering/vite-react/skills/plugin-guide/SKILL.md +1 -0
- package/steering/vite-react/skills/react-three-fiber/SKILL.md +1 -0
- package/steering/vite-react/tech.md +3 -0
- package/steering/design-html/skills/preflight/SKILL.md +0 -51
- package/steering/design-html/skills/preflight/scripts/probe.sh +0 -108
|
@@ -17,6 +17,7 @@ file-match-pattern:
|
|
|
17
17
|
|------|----------------------|--------------------------|
|
|
18
18
|
| 鉴权 | 写操作加 `@NeedLogin()` | **不加** `@NeedLogin()`,鉴权在网关层通过 API Key 完成 |
|
|
19
19
|
| 用户身份 | `req.userContext.userId` 区分用户 | 统一走系统身份,不依赖 `userId` 做业务区分 |
|
|
20
|
+
| 通讯录 | 前端搜人 / 选择器随便用 | **必须**用 `AuthNPaasService.listUsersByIds`(见下文「用户信息」强制规则),搜索类不可用 |
|
|
20
21
|
| Controller 文件 | `xxx.controller.ts` | `xxx.openapi.controller.ts`,放在同一 module 下 |
|
|
21
22
|
| OpenAPI 文档 | 不需要 | **必须**同步维护 `docs/openapi.json`(见下文) |
|
|
22
23
|
|
|
@@ -59,6 +60,80 @@ findAll(@Req() req: Request) {
|
|
|
59
60
|
}
|
|
60
61
|
```
|
|
61
62
|
|
|
63
|
+
## 用户信息(通讯录)—— 强制规则
|
|
64
|
+
|
|
65
|
+
**`/openapi` 路由下查询用户信息,唯一允许的实现是 `AuthNPaasService.listUsersByIds`。没有例外。**
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// ✅ 唯一允许的写法。注意 import 自 fullstack-nestjs-core(项目统一入口)
|
|
69
|
+
import { AuthNPaasService } from '@lark-apaas/fullstack-nestjs-core';
|
|
70
|
+
|
|
71
|
+
@Controller('openapi/xxx')
|
|
72
|
+
export class XxxOpenApiController {
|
|
73
|
+
constructor(private readonly authn: AuthNPaasService) {}
|
|
74
|
+
|
|
75
|
+
@Get(':id')
|
|
76
|
+
async get(@Param('id') id: string) {
|
|
77
|
+
// 不要 try/catch 吞掉——平台故障必须暴露给调用方,别降级成「查无此人」
|
|
78
|
+
const [user] = await this.authn.listUsersByIds([id]);
|
|
79
|
+
if (!user) throw new NotFoundException(`用户 ${id} 不存在`);
|
|
80
|
+
return {
|
|
81
|
+
miaodaUserId: id,
|
|
82
|
+
name: user.name?.zh_cn ?? user.name?.en_us ?? '', // name 是 I18nText,不是字符串
|
|
83
|
+
avatar: user.avatar?.image?.large ?? '', // 头像 URL 在 image.large 上
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
**不要做这两件多余的事**(`PlatformModule.forRoot()` 已经注册了 global 的 `AuthNPaasModule`):
|
|
90
|
+
|
|
91
|
+
- ❌ 在业务 module 里写 `providers: [AuthNPaasService]` —— 会另建一个实例
|
|
92
|
+
- ❌ 往 `package.json` 加 `@lark-apaas/nestjs-authnpaas` 直接依赖 —— 走 `fullstack-nestjs-core` 即可
|
|
93
|
+
|
|
94
|
+
### 禁止(以下任一出现即为错误实现)
|
|
95
|
+
|
|
96
|
+
| ❌ 禁止 | 为什么 |
|
|
97
|
+
|---|---|
|
|
98
|
+
| 复用应用里已有的用户 / 用户资料 service | 那些多半是给浏览器写的,依赖登录态;`/openapi` 下没有登录用户,会静默返回空 |
|
|
99
|
+
| 自己写 HTTP 请求裸调平台通讯录接口 | 鉴权 / 重试 / 日志 / trace 都在 SDK 里,裸调必然漏 |
|
|
100
|
+
| 用 `req.userContext.userId` 做用户查询 | `/openapi` 下**恒为空** |
|
|
101
|
+
| 用 `AuthNPaasService.getCurrentUserLarkUserId()` | 它读 `userId`,取不到只打日志返回 `null`、**不报错**,故障静默 |
|
|
102
|
+
| 从数据库表里 join 出人名 / 头像 | 通讯录不是应用数据,会过期且不 follow 权限 |
|
|
103
|
+
| 按关键词搜人 / 搜部门 / 搜群 | 结果取决于「谁在搜」,`/openapi` 下没有调用者视角,**服务端不提供** |
|
|
104
|
+
| `try/catch` 吞掉 `listUsersByIds` 抛的错、返回空值 | 「平台挂了」和「查无此人」是两回事,吞掉就没人知道故障 |
|
|
105
|
+
| 用 `getBatchLarkUserIds` 判断用户是否存在 | 它是 ID 转换,**外部用户本来就没有 employeeId**,会把外部用户误判成不存在 |
|
|
106
|
+
| 服务端拿不到的字段用 `''` 占位塞进响应 | 调用方会以为字段存在只是没值。拿不到就**从响应类型里删掉**,并告诉用户改走飞书开放平台通讯录 API |
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
// ❌ 错误:包了一层应用现有的用户服务
|
|
110
|
+
const profile = await this.userProfileService.findById(id);
|
|
111
|
+
|
|
112
|
+
// ❌ 错误:OpenAPI 态搜不了人
|
|
113
|
+
const users = await someSearchApi({ query: keyword });
|
|
114
|
+
|
|
115
|
+
// ❌ 错误:读 userId,OpenAPI 态恒为空且失败不报错
|
|
116
|
+
const larkId = await this.authn.getCurrentUserLarkUserId();
|
|
117
|
+
|
|
118
|
+
// ❌ 错误:吞掉平台故障 + 拿不到的字段用空串占位
|
|
119
|
+
let name = '';
|
|
120
|
+
try {
|
|
121
|
+
const [u] = await this.authn.listUsersByIds([id]);
|
|
122
|
+
name = u?.name?.zh_cn ?? '';
|
|
123
|
+
} catch { /* 平台挂了也当查无此人 */ }
|
|
124
|
+
return { userId: id, name, department: '', email: '', jobTitle: '' };
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `AuthNPaasService` import 不到时
|
|
128
|
+
|
|
129
|
+
说明当前 `@lark-apaas/fullstack-nestjs-core` 版本还没带上这个能力。**报告阻塞、请人升级 core 版本**——不要加 `@lark-apaas/nestjs-authnpaas` 直接依赖绕过去,也不要退回上面任何一种禁止写法或自己实现替代品。
|
|
130
|
+
|
|
131
|
+
### 服务端拿不到的字段
|
|
132
|
+
|
|
133
|
+
手机号 / 职位 / 工号 / 直属上级 / 在职状态、以及按 ID 查群 —— 服务端**没有**这些能力。需要就走飞书开放平台通讯录 API,或把该能力放回前端做,**不要**在 `/openapi` 里自己拼一个。
|
|
134
|
+
|
|
135
|
+
完整能力边界与字段口径见 [`contacts-service` › 第六节](../contacts-service/SKILL.md)。
|
|
136
|
+
|
|
62
137
|
## 模块组织
|
|
63
138
|
|
|
64
139
|
`/openapi` Controller 和 `/api` Controller 放在**同一个 module** 下,共享 Service 层。用文件名 `xxx.openapi.controller.ts` 区分。
|
|
@@ -20,8 +20,10 @@ gate-tools:
|
|
|
20
20
|
| 获取运行时投影 | 调用 `get_plugin_ai_json(pluginInstanceId)` |
|
|
21
21
|
| Client 侧调用 | `capabilityClient.load(id).call(actionKey, input)`(流式用 `callStream`) |
|
|
22
22
|
| Server 侧调用(仅兜底) | `capabilityService.load(id).call(actionKey, input)` |
|
|
23
|
+
| 长耗时 AI 结果 | 大体量/多字段/多份/多语言/文件或多模态串联等结构信号命中时,优先前端 `callStream` 渐进展示;需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时使用后端任务记录 + 状态查询 + 结果读取;禁止单个 HTTP 请求等待完整结果后才返回 |
|
|
23
24
|
| capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
|
|
24
25
|
| CapabilityService 导入 | `import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';` |
|
|
26
|
+
| CapabilityService 注入 | `@Inject(CapabilityService) private readonly capabilityService: CapabilityService`(**禁止空参 `@Inject()`**,会导致启动崩溃 502;业务 Module 无需 imports) |
|
|
25
27
|
|
|
26
28
|
## 必读 references
|
|
27
29
|
|
|
@@ -70,6 +72,7 @@ gate-tools:
|
|
|
70
72
|
- 必须先调 `get_plugin_ai_json(pluginInstanceId)`,再产出 **Schema 摘录卡**(格式见 `references/plugin-coding-guide.md`);摘录卡字段缺失禁止编码,`output.fields` 必须完整列出且每个输出字段在代码中被消费(持久化或展示)
|
|
71
73
|
- 按 `actions[].key` 选 actionKey;严格按 `inputSchema` 构造入参(`type: array` 字段必须传数组)、按 `outputSchema` 解析出参——流式 chunk 是**对象**(按字段解构如 `chunk.content`,禁止当字符串拼接),非流式同理按字段名读取;**务必阅读并遵循 `readme`**
|
|
72
74
|
- 调用侧:优先 Client(`unary` → `call()`,`stream` → `callStream()`);触发器/定时任务、敏感凭证、强事务、结果需落库 → Server 侧
|
|
75
|
+
- 高耗时 AI capability 闸门:若运行时投影或功能设计显示输出规模大、输出字段多、需要多份结果、多语言/长文本、文件或多模态输入后继续生成、多个 capability 串联、或结果需后续查看/落库,禁止把完整生成放进一个同步 HTTP 请求等待;能由前端承接时优先 `callStream` 渐进展示,需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时后端创建任务记录后快速返回、由前端短轮询状态/结果,或拆成多个独立小调用
|
|
73
76
|
4. **代码放置**:Client(默认,用户交互触发)→ `client/` 组件/hooks;Server(兜底)→ `server/` Service。
|
|
74
77
|
5. **真实调用冒烟(完成前必须)**:至少成功调用一次 `call()` 或 `callStream()`(按 outputSchema 读 chunk);失败日志含最小字段(字段清单见 `references/plugin-coding-guide.md`)。无冒烟结果不得宣告完成。
|
|
75
78
|
|
|
@@ -136,6 +139,9 @@ const structured = await capabilityClient
|
|
|
136
139
|
├── 输出包含多个独立字段(标题+正文+评分等)
|
|
137
140
|
│ → 拆成多个独立插件并行调用(⭐ 优先)或用 ai-text-to-json
|
|
138
141
|
│ → ⛔ 禁止用 ai-text-generate + 正则/split 解析多字段
|
|
142
|
+
├── 输出规模大(多份结果、多语言、长正文、多章节或批量对象)
|
|
143
|
+
│ → 优先拆分为多个独立生成单元;需要汇总持久化时使用任务状态模型承接
|
|
144
|
+
│ → ⛔ 禁止一个后端 HTTP 请求同步等待所有生成结果后才响应
|
|
139
145
|
└── 输出为单一文本(仅展示,不需解析)→ ai-text-generate
|
|
140
146
|
```
|
|
141
147
|
|
|
@@ -203,9 +209,9 @@ const structured = await capabilityClient
|
|
|
203
209
|
|
|
204
210
|
1. **禁止静默吞异常**:每个 `catch` 至少满足其一——向用户展示错误(toast/页面状态),或触发补偿机制(重试/降级/记录待处理列表)
|
|
205
211
|
2. **异步操作必须有终态**:不阻塞主流程的插件调用须在 DB 维护状态(pending → success/failed),前端必须展示 failed,不能永远 loading
|
|
206
|
-
3.
|
|
207
|
-
4.
|
|
208
|
-
5.
|
|
212
|
+
3. **长耗时 AI 生成不得同步等完**:大体量、多字段、多份、多语言、文件/多模态串联或需要持久化的 AI capability 调用,触发接口必须快速返回任务状态或采用前端流式展示;若 `api_request`、浏览器操作或页面请求出现超时/连接断开,不能只凭服务端最终日志或数据库最终写入宣告成功,必须证明用户侧存在可读取的终态结果
|
|
213
|
+
4. **通知类插件失败必须有补偿**:如 `send-feishu-message` 失败,至少记录"待发送"列表或 UI 提示"通知发送失败,请手动联系"
|
|
214
|
+
5. **配置完整性(load 前必查)**:`load(id)` 前确认实例已创建且 id 与代码完全匹配,否则抛 `CapabilityNotFoundError`(开发态 Top 错误);load/call 失败时停止后续请求避免放大错误;**缓存 load 结果**,同一 id 不重复 load
|
|
209
215
|
|
|
210
216
|
## 缓存与幂等性
|
|
211
217
|
|
|
@@ -240,7 +246,7 @@ const structured = await capabilityClient
|
|
|
240
246
|
|
|
241
247
|
### 通知接收人动态解析
|
|
242
248
|
|
|
243
|
-
接收人(`receiverUserList`/`receiverGroupList` 等)按角色/条件变化时,必须实时查询角色成员经 `input` 传入(角色/成员的运行时查询写法见 `authz-guide` 技能),禁止硬编码或凭经验拼装 ID。如引入缓存,必须提供显式失效手段(如角色变更时清缓存)并明示 TTL,禁止无失效手段的常驻缓存导致接收人信息过期。通知发送失败的补偿要求见上文「插件调用错误处理」第
|
|
249
|
+
接收人(`receiverUserList`/`receiverGroupList` 等)按角色/条件变化时,必须实时查询角色成员经 `input` 传入(角色/成员的运行时查询写法见 `authz-guide` 技能),禁止硬编码或凭经验拼装 ID。如引入缓存,必须提供显式失效手段(如角色变更时清缓存)并明示 TTL,禁止无失效手段的常驻缓存导致接收人信息过期。通知发送失败的补偿要求见上文「插件调用错误处理」第 4 条铁律,不重复展开。
|
|
244
250
|
|
|
245
251
|
## 飞书深链 URL 规范
|
|
246
252
|
|
package/steering/nestjs-react-fullstack/skills/plugin-guide/references/plugin-coding-guide.md
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
### 核心原则:根据场景选择调用侧
|
|
4
4
|
|
|
5
|
-
**默认优先在 Client 侧调用 capabilityClient
|
|
5
|
+
**默认优先在 Client 侧调用 capabilityClient;流式或高耗时 AI 生成优先 `callStream` 渐进展示。结果需要持久化时,优先在流式结束后通过已有后端接口保存;只有 Client 侧无法满足时才切到 Server 侧。**
|
|
6
|
+
|
|
7
|
+
**高耗时 AI capability 先判调用形态**:只要输出规模大、输出字段多、需要多份结果、多语言/长文本、文件或多模态输入后继续生成、多个 capability 串联、或结果需后续查看/落库,就不得把完整 AI 结果塞进一个同步 HTTP 请求等待。能由前端承接时,优先用前端 `callStream` 渐进展示,并在流式结束后按需复用已有 CRUD 接口保存结果;只有 Client 侧无法满足(触发器/敏感凭证/强事务/必须由后端保证落库一致性等)时,才采用后端任务记录 + 状态/结果查询,避免把后台任务作为默认方案。
|
|
8
|
+
|
|
6
9
|
**严禁** import { capabilityClient } from '@lark-apaas/client-capability'。
|
|
7
10
|
**唯一指定**的导入方式是 import { capabilityClient } from '@lark-apaas/client-toolkit';
|
|
8
11
|
|
|
9
12
|
| 优先级 | 场景 | 调用方式 |
|
|
10
13
|
|-------|------|---------|
|
|
11
|
-
| **首选** |
|
|
12
|
-
| **首选** |
|
|
13
|
-
|
|
|
14
|
+
| **首选** | 绝大多数即时展示场景 | `capabilityClient.load(id).call()` |
|
|
15
|
+
| **首选** | 流式输出,或高耗时但可由前端承接的 AI 生成 | `capabilityClient.load(id).callStream()`(流式结束后按需持久化) |
|
|
16
|
+
| **必要时** | 高耗时且 Client 侧无法满足:触发器、敏感凭证、强事务、必须由后端保证落库一致性 | 后端任务记录 + 后台调用 `CapabilityService.load(id).call()` + 前端短轮询状态/结果 |
|
|
17
|
+
| **兜底** | Client 侧无法满足且单次调用可在交互边界内完成 | `CapabilityService.load(id).call()` |
|
|
14
18
|
|
|
15
19
|
#### 什么情况下应使用 Server 侧?
|
|
16
20
|
|
|
@@ -19,7 +23,7 @@
|
|
|
19
23
|
1. **涉及敏感凭证**:调用需要服务端私密 token/secret,不适合暴露给前端
|
|
20
24
|
2. **必须后端编排**:多个插件调用之间有强事务依赖,需要后端统一编排
|
|
21
25
|
3. **触发器/定时任务场景**:没有前端上下文,只能由后端发起
|
|
22
|
-
4.
|
|
26
|
+
4. **插件结果需要持久化**:调用结果需要保存到数据库;能由前端承接时,优先前端 `callStream` 渐进展示并在结束后复用已有 CRUD 接口保存;只有 Client 侧无法满足或必须由后端保证落库一致性时,才在 Server 侧调用并落库。若此时输出规模大、字段多、结果多份或需多步能力串联,必须改为任务记录 + 后台执行 + 状态/结果查询,避免一个 HTTP 请求等待完整生成
|
|
23
27
|
|
|
24
28
|
> **提示**:如果插件结果不需要存储、仅用于即时展示(如流式生成文本、发送消息),优先在前端调用。但当结果需要保存到数据库时,不要回避使用 Server 侧。
|
|
25
29
|
|
|
@@ -42,9 +46,13 @@
|
|
|
42
46
|
|
|
43
47
|
```
|
|
44
48
|
插件结果是否需要持久化到数据库?
|
|
45
|
-
├──
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
├── 否(一次性即时展示)
|
|
50
|
+
│ ├── `outputMode=stream` 或内容生成较慢 → Client 侧 `callStream()` 渐进展示
|
|
51
|
+
│ └── 单次短输出 → Client 侧 `call()`(默认)
|
|
52
|
+
└── 是
|
|
53
|
+
├── `outputMode=stream` 且前端可承接 → 推荐方案 A:Client 侧 `callStream()` 渐进展示,成功后通过已有 CRUD 接口保存结果
|
|
54
|
+
├── 输出较小且可在交互边界内完成、且必须由后端保证一致性 → Server 侧调用并在同一方法中落库
|
|
55
|
+
└── Client 侧无法满足且输出规模大/多字段/多份/多语言/多步骤 → 方案 B:Server 侧创建任务记录,快速返回任务状态;后台调用插件并落库;前端短轮询状态/结果
|
|
48
56
|
```
|
|
49
57
|
|
|
50
58
|
| 应避免的做法 | 推荐做法 |
|
|
@@ -79,12 +87,18 @@
|
|
|
79
87
|
#### call / callStream 函数签名
|
|
80
88
|
|
|
81
89
|
```typescript
|
|
82
|
-
|
|
83
|
-
.
|
|
90
|
+
// 前端 capabilityClient(@lark-apaas/client-toolkit,实际类型来自 @lark-apaas/client-capability)
|
|
91
|
+
.call<T = unknown>(actionKey: string, params?: Record<string, unknown>): Promise<T> // 非流式
|
|
92
|
+
.callStream<T = unknown>(actionKey: string, params?: Record<string, unknown>): AsyncIterable<T> // 流式
|
|
84
93
|
```
|
|
85
94
|
|
|
86
95
|
- **第一个参数 `actionKey`**:必须是字符串,值来自 `get_plugin_ai_json` 返回的 `actions[].key`(如 `'sendFeishuMessage'`、`'textGenerate'`)
|
|
87
|
-
- **第二个参数 `
|
|
96
|
+
- **第二个参数 `params`**:类型是 `Record<string, unknown>`,结构符合 `actions[].inputSchema`
|
|
97
|
+
- 上面是**前端**签名,带泛型。**服务端 `CapabilityService` 的 `call` / `callStream` / `callStreamWithEvents` 都没有泛型**,两者不通用,详见「Server 侧调用方式」
|
|
98
|
+
|
|
99
|
+
> **`Record<string, unknown>` 不接 `interface` 声明的对象**(interface 没有隐式索引签名),直接传会撞
|
|
100
|
+
> `Argument of type 'XxxInput' is not assignable to parameter of type 'Record<string, unknown>'. Index signature for type 'string' is missing in type 'XxxInput'.`
|
|
101
|
+
> 三种正确写法:① 直接传内联对象字面量;② 入参类型用 `type XxxInput = { ... }` 而非 `interface`(type 别名有隐式索引签名);③ 已有 interface 时在调用点显式 `input as unknown as Record<string, unknown>`。**不要为此改用 `as any`。**
|
|
88
102
|
|
|
89
103
|
```typescript
|
|
90
104
|
// ❌ 错误:把参数 JSON.stringify 后当作 actionKey
|
|
@@ -271,10 +285,11 @@ function MultiPluginStreamExample({ recordId }: { recordId: string }) {
|
|
|
271
285
|
2. 一个 `stream` action 的真实调用结果(chunk 按 `outputSchema` 字段读取)
|
|
272
286
|
3. 调用失败时的最小日志字段齐全
|
|
273
287
|
4. 若无法执行真实调用,必须明确写明阻塞原因,禁止直接标记“开发完成”
|
|
288
|
+
5. 若用户触发 AI 生成的请求超时、连接断开或工具返回超时,必须判定为验收失败;只有触发请求快速返回、随后能通过页面或接口读到明确完成或失败终态和结果,才允许标记通过
|
|
274
289
|
|
|
275
290
|
### Server 侧调用方式(仅兜底场景)
|
|
276
291
|
|
|
277
|
-
> 以下场景适合使用 Server
|
|
292
|
+
> 以下场景适合使用 Server 侧调用;若前端 `callStream` + 既有 CRUD 保存即可满足展示和持久化,不要优先引入后端后台任务。
|
|
278
293
|
|
|
279
294
|
#### 1. 何时适合用 Server 侧?
|
|
280
295
|
|
|
@@ -285,6 +300,7 @@ function MultiPluginStreamExample({ recordId }: { recordId: string }) {
|
|
|
285
300
|
| 敏感凭证调用 | 凭证不能暴露给前端 | 调用需要 admin token 的 API |
|
|
286
301
|
| 强事务编排 | 多步骤需要原子性 | 创建记录 → 发通知 → 更新状态必须全成功或全回滚 |
|
|
287
302
|
| 插件结果需持久化 | 调用结果需保存到数据库 | AI 分类/摘要结果需落库、文档解析的结构化数据需入库、图片识别结果需关联业务记录、语音转文字结果需存档等 |
|
|
303
|
+
| 高耗时 AI 生成需后续查看 | 请求不能长期占用用户交互链路 | 批量、多版本、多语言、长文本、多字段结构化生成,或文件/多模态分析后再生成内容 |
|
|
288
304
|
|
|
289
305
|
#### 2. NestJS 注入方式
|
|
290
306
|
|
|
@@ -296,23 +312,49 @@ import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';
|
|
|
296
312
|
export class XxxService {
|
|
297
313
|
private readonly logger = new Logger(XxxService.name);
|
|
298
314
|
|
|
315
|
+
// 注入 token 必须显式写成 @Inject(CapabilityService)
|
|
299
316
|
constructor(
|
|
300
|
-
@Inject() private readonly capabilityService: CapabilityService,
|
|
317
|
+
@Inject(CapabilityService) private readonly capabilityService: CapabilityService,
|
|
301
318
|
) {}
|
|
302
319
|
}
|
|
303
320
|
```
|
|
304
321
|
|
|
322
|
+
**禁止空参 `@Inject()`**。本 stack 的 `nest-cli.json` 用 `builder: swc`,空参 `@Inject()` 会把注入 token 置为 undefined,Nest 回退读 `design:type` 拿到 `Function`,服务端启动即崩溃、所有 `/api` 请求返回 502:
|
|
323
|
+
|
|
324
|
+
```
|
|
325
|
+
UnknownDependenciesException: Nest can't resolve dependencies of the XxxService (?).
|
|
326
|
+
Please make sure that the argument Function at index [0] is available in the XxxModule context.
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
见到这段报错先查构造函数有没有空参 `@Inject()`,不要去改 Module 的 `imports` / `providers`。
|
|
330
|
+
|
|
331
|
+
**业务 Module 不需要注册 CapabilityModule**。`PlatformModule` 是 `@Global()` 且已把 `CapabilityModule` 放进 `exports`,`CapabilityService` 全局可注入。不要在业务 Module 里写 `imports: [CapabilityModule.forRoot(...)]` 或 `imports: [PlatformModule.forRoot()]`。
|
|
332
|
+
|
|
333
|
+
同一规则适用于从 `@lark-apaas/fullstack-nestjs-core` 注入的其他平台服务(`AuthNPaasService`、`FileService` 等)。
|
|
334
|
+
|
|
305
335
|
#### 3. 调用示例
|
|
306
336
|
|
|
337
|
+
服务端 `CapabilityExecutor` 的**三个方法全都没有泛型参数**(`@lark-apaas/nestjs-capability`):
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
call(actionName: string, input: unknown, context?: Partial<PluginActionContext>): Promise<unknown>;
|
|
341
|
+
callStream(actionName: string, input: unknown, context?): AsyncIterable<unknown>;
|
|
342
|
+
callStreamWithEvents(actionName: string, input: unknown, context?): AsyncIterable<StreamEvent<unknown>>;
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
写 `.call<T>(...)` / `.callStream<T>(...)` / `.callStreamWithEvents<T>(...)` 都会报「应有 0 个类型参数,但获得 1 个」。泛型只存在于前端 `capabilityClient`。注意服务端 `input` 是**必传**(没有 `?`),且类型是 `unknown` 而非前端的 `Record<string, unknown>`。服务端接 `unknown` 后在运行时收窄,禁止 `as any`:
|
|
346
|
+
|
|
307
347
|
```typescript
|
|
308
348
|
const inputParams = {
|
|
309
349
|
// 严格按 get_plugin_ai_json.actions[].inputSchema 构造
|
|
310
350
|
};
|
|
311
351
|
|
|
312
352
|
try {
|
|
313
|
-
const output = await this.capabilityService
|
|
353
|
+
const output: unknown = await this.capabilityService
|
|
314
354
|
.load('')
|
|
315
355
|
.call('', inputParams);
|
|
356
|
+
// 运行时收窄后再取字段,例如:
|
|
357
|
+
// if (typeof output === 'object' && output !== null && 'content' in output) { ... }
|
|
316
358
|
return output;
|
|
317
359
|
} catch (error) {
|
|
318
360
|
this.logger.error('pluginInstance call failed', {
|
|
@@ -328,6 +370,7 @@ try {
|
|
|
328
370
|
|
|
329
371
|
- PluginInstance 调用在 Server 侧通常属于 **外部依赖 / side-effect**
|
|
330
372
|
- 除非业务明确要求强一致性,**默认不应阻塞主业务流程**
|
|
373
|
+
- 已选择 Server 侧承接的高耗时 AI capability 必须有可观测状态:创建任务时记录处理进度、完成终态、失败终态、输入摘要、错误信息和结果引用;触发接口只返回任务标识与当前状态,前端通过短轮询读取进度和最终结果
|
|
331
374
|
|
|
332
375
|
推荐写法:异步触发 + catch 兜底:
|
|
333
376
|
|
package/steering/nestjs-react-fullstack/skills/server-builtins-file-storage-service/SKILL.md
CHANGED
|
@@ -11,7 +11,7 @@ match-template-name: nestjs-react-fullstack
|
|
|
11
11
|
在 NestJS 服务端代码中使用 `FileService` 进行文件上传、下载、删除和管理。
|
|
12
12
|
|
|
13
13
|
> **使用注意**: 仅用于服务端文件处理场景,非必要文件上传下载场景请使用前端 `client-builtins-file-storage-service` skill。
|
|
14
|
-
> **入口边界**:本 SDK 是**服务端代码**读写应用存储的入口;Agent 在对话 /
|
|
14
|
+
> **入口边界**:本 SDK 是**服务端代码**读写应用存储的入口;Agent 在对话 / 开发中自己上传或调试文件,先加载应用文件存储操作 skill(按「应用文件存储 / 文件上传 / 文件下载」召回),用其 CLI 命令操作,具体命令一律以该 skill 为准。二者与本 SDK 操作同一个应用存储桶,但**该 CLI 命令只供 Agent 在沙箱终端使用,禁止写进服务端代码**。
|
|
15
15
|
|
|
16
16
|
## 使用场景
|
|
17
17
|
|
|
@@ -93,12 +93,14 @@ match-template-name: nestjs-react-fullstack
|
|
|
93
93
|
|
|
94
94
|
> 完整的解析→守卫流程见 `authn-guide` skill 第二节"认证流程"。
|
|
95
95
|
|
|
96
|
+
类型使用规则:Controller 直接用 `@Req() req: Request` 读取 SDK 增强的 `req.userContext`。测试、mock 或局部业务需要窄类型时,定义独立接口,只声明实际读取字段;不要继承 Express `Request`,也不要重声明平台增强的 `userContext`。未读取的 `tenantId` 省略;必须保留时用 `unknown` 或 SDK 兼容类型。
|
|
97
|
+
|
|
96
98
|
### 字段表
|
|
97
99
|
|
|
98
100
|
| 字段 | 类型 | 说明 |
|
|
99
101
|
|------|------|------|
|
|
100
102
|
| `userId` | `string` | 妙搭用户 ID |
|
|
101
|
-
| `tenantId` | `number` | 租户 ID |
|
|
103
|
+
| `tenantId` | `number \| undefined` | 租户 ID;按 SDK Express `Request.userContext` 增强:可选 number |
|
|
102
104
|
| `appId` | `string` | 应用 ID |
|
|
103
105
|
| `loginUrl` | `string` | 登录跳转 URL |
|
|
104
106
|
| `userType` | `string` | 用户类型(如 `_employee`) |
|
|
@@ -14,6 +14,8 @@ match-template-name: nestjs-react-fullstack
|
|
|
14
14
|
|
|
15
15
|
> **边界说明**:`authClient.session` 仅用于用户登录/登出/获取用户信息等鉴权操作。**插件调用(capability)不属于账户 SDK**,须使用独立的 `capabilityClient`(参见 plugin-guide)。
|
|
16
16
|
>
|
|
17
|
+
> **本地 dev 限制**:本 skill 的能力底层打 `/app/<appId>/__runtime__/api/v1/account/*`。这类端点只有在 `npm run dev:local` 成功 `env pull` 到 `MIAODA_DEV_PLATFORM_BASE`(或 `SANDBOX_PUBLIC_URL`)时才由 dev-proxy 反代到远端沙箱;**env pull 失败或未装 lark-cli 时该反代不挂**(启动脚本只 warn 不中断),表现为 POST 404 / GET 返回 200 + index.html。`useCurrentUserProfile()` 打的是 `account/login/user`;UserSelect / DepartmentSelect / UserDisplay 打 `search_user` / `list_users` / `user_profile` / `search_department`。先确认 `.env.local` 里有 `MIAODA_DEV_PLATFORM_BASE`;确实缺失时禁止改 `business-ui` 组件或换自研控件绕过,改由服务端出数据(`req.userContext` / `AuthNPaasService`)。
|
|
18
|
+
>
|
|
17
19
|
> **运行时边界**:本 skill 所有能力(`authClient`、`useCurrentUserProfile`、UserSelect/UserDisplay 等)仅限前端代码使用,**严禁在 `server/**` 中 import**。服务端获取用户身份用 `req.userContext` / `AuthNPaasService`(见 `user-identity` skill),完整边界规则见 coding-guide。
|
|
18
20
|
|
|
19
21
|
## 怎么选(决策指引)
|
|
@@ -116,6 +116,12 @@ Module '"@client/src/api/gen"' has no exported member named 'DiscrepancyResponse
|
|
|
116
116
|
- [ ] 已根据实际定义修正代码
|
|
117
117
|
- [ ] 已验证修改后类型使用正确
|
|
118
118
|
|
|
119
|
+
## 业务日期错位
|
|
120
|
+
|
|
121
|
+
**问题描述**:本地凌晨、周一或月初附近,日期落到前一天、上周或上月。
|
|
122
|
+
|
|
123
|
+
**解决步骤**:按 `coding-guide` 的「业务日期」一节改——自然日统一走业务时区 helper;精确时刻不动。
|
|
124
|
+
|
|
119
125
|
## JSX 和语法错误
|
|
120
126
|
|
|
121
127
|
### 特殊字符转义处理
|
|
@@ -129,7 +129,7 @@ shared/ # 前后端共享的目录
|
|
|
129
129
|
|
|
130
130
|
1. 优先使用项目已有依赖,仅在无法实现时安装新依赖
|
|
131
131
|
2. 使用前先查看 `package.json` 确保依赖已存在
|
|
132
|
-
3. **子包完整性检查**:部分库有多个子包(如 `@
|
|
132
|
+
3. **子包完整性检查**:部分库有多个子包(如 `@radix-ui/react-*` 系列每个组件都是独立子包),添加 import 后必须确认 package.json 中包含所有需要的子包
|
|
133
133
|
4. 用法不清时查看 readme,可进一步搜索或网页访问获取信息
|
|
134
134
|
|
|
135
135
|
## 文件命名约定
|
|
@@ -147,6 +147,39 @@ shared/ # 前后端共享的目录
|
|
|
147
147
|
- `/api` 和 `/openapi` 代理已配置,前端 TS 严格模式已禁用,后端已启用
|
|
148
148
|
- Rspack 用于构建,已配置好,**禁止修改**
|
|
149
149
|
|
|
150
|
+
### 平台 runtime 接口(`__runtime__`)在本地要靠 env pull 才通
|
|
151
|
+
|
|
152
|
+
dev server 自己只代理 `/api`、`/openapi`、`/__innerapi__`。`/app/<appId>/__runtime__/*` 这一整类平台 runtime 接口走的是另一条路:`npm run dev:local` 会先跑 `lark-cli apps +env-pull` 把沙箱身份/凭证拉进 `.env.local`,其中的 `MIAODA_DEV_PLATFORM_BASE`(或 legacy `SANDBOX_PUBLIC_URL`)才让 dev-proxy 把 `__runtime__` 反代到远端沙箱。
|
|
153
|
+
|
|
154
|
+
**env pull 失败、未装 lark-cli、或 `.spark/meta.json` 缺 app_id 时,启动脚本只 warn 不中断**,反代就不会挂上。此时这些端点的症状是:**POST 返回 404;GET 落到 Vite 的 SPA fallback,返回 200 + `index.html`**(业务侧表现为「返回一坨 HTML,JSON 解析失败」)。
|
|
155
|
+
|
|
156
|
+
常踩到的端点:
|
|
157
|
+
|
|
158
|
+
| 端点 | 方法 | 谁会打到它 |
|
|
159
|
+
|------|------|-----------|
|
|
160
|
+
| `api/v1/permissions/roles` | GET | `AppContainer` 每次加载应用自动打 |
|
|
161
|
+
| `api/v1/observability/{logs,traces,metrics}/collect`、`current_server_timestamp` | POST/GET | 平台埋点上报,自动打 |
|
|
162
|
+
| `api/v1/account/login/user` | POST | `useCurrentUserProfile()` → `authClient.session.getUserInfo()` |
|
|
163
|
+
| `api/v1/account/search_user`、`list_users`、`user_profile`、`search_department`、`convert_lark_user` | POST / `user_profile` 是 GET | `business-ui` 的 UserSelect / DepartmentSelect / UserDisplay |
|
|
164
|
+
| `api/v1/account/search`、`api/v1/account/chat/list_chats` | POST | ChatSelect 选群组件 |
|
|
165
|
+
| `api/v1/studio/user/profile` | GET | `getUserProfile()` |
|
|
166
|
+
| `api/v1/storage/object/<bucket>/pre_upload` | POST | `dataloom.storage.uploadFile()` 前端上传 |
|
|
167
|
+
| `api/v1/studio/plugins/tmp_files/acquire_upload_url`、`acquire_download_url` | POST | `capabilityClient` 的文件类入参 |
|
|
168
|
+
|
|
169
|
+
> `useCurrentUserProfile()` 打的是 `account/login/user`,**不是** `user_profile`/`search_user` 那一排;它另外还打一个 `GET /api/authnpaas/lark-user-id`,那条走 `/api`、被代理、正常。
|
|
170
|
+
|
|
171
|
+
排查顺序:先看 `.env.local` 里有没有 `MIAODA_DEV_PLATFORM_BASE`,没有就重跑 `env pull`。确认拉不到时:
|
|
172
|
+
|
|
173
|
+
- **禁止**改 `client/src/components/business-ui/**`(平台保护文件)、**禁止**给组件加兜底请求或改用自研选人控件绕过——发布后这些端点是正常的,绕过写法反而是错的
|
|
174
|
+
- 需要验证「按当前用户过滤」「展示用户姓名」这类逻辑:改由服务端出数据。服务端的 `req.userContext` 和 `AuthNPaasService` 由网关注入,不受此限制(见 `user-identity` / `contacts-service` skill)。`useCurrentUserProfile()` 长期停在加载态就是这个原因
|
|
175
|
+
- 需要验证上传链路:改用接口测试直接打后端接口,或复用库里已有的文件 URL
|
|
176
|
+
|
|
177
|
+
另外,`UserSelect` 弹层是**搜索驱动**的:不输关键词时列表为空属预期行为,不是接口挂了。
|
|
178
|
+
|
|
179
|
+
### 服务端启动即崩、`/api` 全部 502
|
|
180
|
+
|
|
181
|
+
报错形如 `UnknownDependenciesException: Nest can't resolve dependencies of the XxxService (?)... argument Function at index [0]` 时,先查构造函数有没有空参 `@Inject()`(见 plugin-guide),不要先去改 Module 的 `imports` / `providers`。
|
|
182
|
+
|
|
150
183
|
## 质量保障流程
|
|
151
184
|
|
|
152
185
|
修改代码后:
|
|
@@ -286,6 +319,12 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
286
319
|
|
|
287
320
|
- **`customTimestamptz` 跨网络后是 string**:service 内查询返回 `Date`,但 API 响应经 JSON 序列化后前端拿到 ISO string。`shared/api.interface.ts` 中 timestamptz 字段声明为 `string`,前端需要 Date 时手动 `new Date(value)`。
|
|
288
321
|
|
|
322
|
+
### 业务日期
|
|
323
|
+
|
|
324
|
+
需求里的日期是**自然日**(哪一天 / 哪一周 / 哪一月)而不是精确时刻时,按业务时区计算,未指定时默认 `Asia/Shanghai`。
|
|
325
|
+
|
|
326
|
+
项目里放一个 helper 统一产出 `day` / `month` / `weekStart` / `shiftDay`;服务端写入、统计聚合、前端展示都比较它的返回值。
|
|
327
|
+
|
|
289
328
|
### 数据库使用强约束
|
|
290
329
|
|
|
291
330
|
- 仅通过 schema.ts 暴露的客户端和类型读写,禁止手写 SQL/临时类型
|
|
@@ -329,10 +368,13 @@ await db.select().from(users).where(eq(users.adminUser, userId));
|
|
|
329
368
|
|
|
330
369
|
- **必须从 `req.userContext` 获取**,禁止从前端传递,禁止硬编码
|
|
331
370
|
- 依赖用户信息的接口不要用 API 测试工具测试,用 `think` 工具思考
|
|
371
|
+
- 后端按 `user-identity` 和 `@lark-apaas/fullstack-nestjs-core` 的 Express `Request` 增强读取 `req.userContext`;`tenantId` 为可选 number,不能套用前端 `window.tenantId` / `viewContext` 字符串语义。
|
|
372
|
+
- Controller 直接用 `@Req() req: Request`;测试、mock 或业务窄类型用独立接口,只声明实际读取字段。不要继承 Express `Request` 或重声明 `userContext`;未读 `tenantId` 就省略,必须保留时用 `unknown` 或 SDK 兼容类型。
|
|
332
373
|
|
|
333
374
|
```typescript
|
|
334
|
-
// req.userContext
|
|
335
|
-
// - userId/
|
|
375
|
+
// req.userContext 字段(由身份认证中间件挂载,完整合同见 user-identity):
|
|
376
|
+
// - userId/appId: string
|
|
377
|
+
// - tenantId: SDK Request.userContext 的可选 number
|
|
336
378
|
// - env: 'preview' | 'runtime' (preview=预览态, runtime=发布运行态)
|
|
337
379
|
// - userName/userNameEn: string
|
|
338
380
|
// - userNameI18n: string (多语名字, 如 {zh_cn: '用户', en_us: 'user'})
|
|
@@ -443,14 +485,13 @@ NestJS 自己不读 env。直连 NestJS 端口 → header 缺失 → `req.userCo
|
|
|
443
485
|
|
|
444
486
|
- **框架**: React 19 + TypeScript
|
|
445
487
|
- **路由**: React Router DOM v6
|
|
446
|
-
- **样式**:
|
|
488
|
+
- **样式**: tailwindcss(语义化 token)
|
|
447
489
|
- **UI 组件库**: shadcn/ui — Use components for functionality, heavily style them
|
|
448
490
|
- **图表**: ReactECharts,**开发前必须调用 `/charts-skill`**
|
|
449
491
|
- **图标**: Lucide React(唯一图标库,禁止 Emoji 和其他图标库)
|
|
450
492
|
- **表格/表单/图表**: 见下方"组件 Skill 召回规则",开发前必须先调用对应 Skill
|
|
451
493
|
- **用户**: 用户信息展示/选择必须用 `business-ui` 组件(阅读 README.md),禁止直接展示 userId
|
|
452
|
-
-
|
|
453
|
-
- **Markdown 渲染**: `components/ui/streamdown`(内置 prose 排版)
|
|
494
|
+
- **Markdown 渲染**: `components/ui/markdown`(react-markdown + remark-gfm,内置 prose 排版)
|
|
454
495
|
|
|
455
496
|
## API 请求
|
|
456
497
|
|
|
@@ -483,8 +524,7 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
483
524
|
| --------------------------------------------------- | --------------------------------------- | --------------------------------- |
|
|
484
525
|
| Table | `@lark-apaas/client-toolkit/antd-table` | 数据表格,**先调 `/table-skill`** |
|
|
485
526
|
| UserSelect/UserDisplay/UserProfile/DepartmentSelect | `business-ui/*` | 用户/部门选择展示 |
|
|
486
|
-
|
|
|
487
|
-
| Streamdown | `components/ui/streamdown` | Markdown/流式渲染 |
|
|
527
|
+
| Markdown | `components/ui/markdown` | Markdown 渲染(react-markdown + remark-gfm) |
|
|
488
528
|
|
|
489
529
|
### 组件 Skill 召回规则(强制执行)
|
|
490
530
|
|
|
@@ -535,7 +575,6 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
535
575
|
```
|
|
536
576
|
需要写样式?
|
|
537
577
|
├─ 基础布局/间距/颜色 → Tailwind ✅
|
|
538
|
-
├─ 复杂动画/伪元素/高级CSS → styled-jsx ✅
|
|
539
578
|
└─ JS动态计算值 → 行内 style ✅
|
|
540
579
|
```
|
|
541
580
|
|
|
@@ -548,11 +587,6 @@ import { axiosForBackend } from '@lark-apaas/client-toolkit/utils/getAxiosForBac
|
|
|
548
587
|
- **arbitrary values 中空格用下划线**:`from-[hsl(215_60%_18%)]` 非 `from-[hsl(215 60% 18%)]`
|
|
549
588
|
- `tailwind-theme.css` 自定义属性用 `hsl(H, S%, L%)` 格式(非 `23 10% 23%`)
|
|
550
589
|
|
|
551
|
-
### styled-jsx 规范
|
|
552
|
-
|
|
553
|
-
- **技术栈一致性**:仅在已配置 styled-jsx 插件的项目中使用。`package.json` 无 `styled-jsx` 依赖则**禁用**,否则运行时 SyntaxError
|
|
554
|
-
- **禁止动态插值**:`<style jsx>` 内禁止 `${...}` 等表达式(会卡死)。动态值放 CSS 变量,用 `var(--xxx)` 引用
|
|
555
|
-
|
|
556
590
|
### 布局/排版
|
|
557
591
|
|
|
558
592
|
- Spacing 保持一致(small/medium/large 三级),Panel 风格统一
|
|
@@ -635,15 +669,8 @@ return <h1>{data?.title || '未知标题'}</h1>;
|
|
|
635
669
|
| 验证 | zod |
|
|
636
670
|
| 工具函数 | lodash |
|
|
637
671
|
| 样式 | clsx |
|
|
638
|
-
| Excel | xlsx(**仅前端实现,禁止服务端实现**。解析后将结构化数据传到服务端保存) |
|
|
639
|
-
| PDF 导出 | jspdf + html2canvas(**仅前端实现,禁止服务端实现**) |
|
|
640
|
-
| 文件上传 | react-dropzone |
|
|
641
|
-
| 二维码 | qrcode.react |
|
|
642
672
|
| 用户反馈 | sonner |
|
|
643
|
-
|
|
644
|
-
| 数字动画 | react-countup |
|
|
645
|
-
| Base64 | js-base64 — `import { encode, decode } from 'js-base64'` |
|
|
646
|
-
| 3D 场景 | cobe |
|
|
673
|
+
|
|
647
674
|
|
|
648
675
|
## 滚动分页最佳实践
|
|
649
676
|
|
|
@@ -35,9 +35,10 @@ lark-cli apps --help 2>&1 | grep -q '+plugin-install' && echo "READY" || echo "M
|
|
|
35
35
|
| Client 侧非流式调用 | `capabilityClient.load(id).call(actionKey, input)` |
|
|
36
36
|
| Client 侧流式调用 | `capabilityClient.load(id).callStream(actionKey, input)` |
|
|
37
37
|
| Server 侧调用(仅兜底) | `capabilityService.load(id).call(actionKey, input)` |
|
|
38
|
+
| 长耗时 AI 结果 | 大体量/多字段/多份/多语言/文件或多模态串联等结构信号命中时,优先前端 `callStream` 渐进展示;需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时使用后端任务记录 + 状态查询 + 结果读取;禁止单个 HTTP 请求等待完整结果后才返回 |
|
|
38
39
|
| capabilityClient 导入 | `import { capabilityClient } from '@lark-apaas/client-toolkit'` |
|
|
39
40
|
| CapabilityService 导入 | `import { CapabilityService } from '@lark-apaas/fullstack-nestjs-core';` |
|
|
40
|
-
| CapabilityService 注入 | `@Inject() private readonly capabilityService: CapabilityService
|
|
41
|
+
| CapabilityService 注入 | `@Inject(CapabilityService) private readonly capabilityService: CapabilityService`(**禁止空参 `@Inject()`**,会导致启动崩溃 502;业务 Module 无需 imports) |
|
|
41
42
|
| 插件实例配置位置 | `server/capabilities/<instance_id>.json`(默认全栈应用)。见「配置目录」段 |
|
|
42
43
|
|
|
43
44
|
> **capabilityClient 导入警告**:`capabilityClient` 是从 `@lark-apaas/client-toolkit` 直接导入的独立对象,**不是**从 `getDataloom()` 上获取的。正确且唯一的方式:`import { capabilityClient } from '@lark-apaas/client-toolkit'`。
|
|
@@ -516,7 +517,9 @@ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
|
|
|
516
517
|
2. 结果供后续功能消费
|
|
517
518
|
3. 用户再次访问时需要看到结果
|
|
518
519
|
|
|
519
|
-
|
|
520
|
+
**推荐**:能由前端承接时,Client 侧 `callStream` 渐进展示,流式结束后调已有 CRUD 接口保存。**必要时**:敏感凭证、触发器、强事务或必须由后端保证落库一致性时,Server 侧 Service 调用插件并落库。
|
|
521
|
+
|
|
522
|
+
**高耗时 AI capability 闸门**:若插件输出定义或功能设计显示输出规模大、输出字段多、需要多份结果、多语言/长文本、文件或多模态输入后继续生成、多个 capability 串联、或结果需后续查看/落库,禁止把完整生成放进一个同步 HTTP 请求等待;能由前端承接时优先 `callStream` 渐进展示,需保存则流式结束后复用 CRUD 落库;仅在 Client 侧无法满足时后端创建任务记录后快速返回、由前端短轮询状态/结果,或拆成多个独立小调用。
|
|
520
523
|
|
|
521
524
|
### 生成代码
|
|
522
525
|
|
|
@@ -535,7 +538,8 @@ npx @lark-apaas/miaoda-cli plugin list --id <instance_id>
|
|
|
535
538
|
|
|
536
539
|
1. **禁止静默吞异常**:每个 `catch` 块必须向用户展示错误或触发补偿
|
|
537
540
|
2. **异步操作必须有终态**:DB 中维护状态(pending → success / failed)
|
|
538
|
-
3.
|
|
541
|
+
3. **长耗时 AI 生成不得同步等完**:大体量、多字段、多份、多语言、文件/多模态串联或需要持久化的 AI capability 调用,触发接口必须快速返回任务状态或采用前端流式展示;若接口请求、浏览器操作或页面请求出现超时/连接断开,不能只凭服务端最终日志或数据库最终写入宣告成功,必须证明用户侧存在可读取的终态结果
|
|
542
|
+
4. **插件失败必须有补偿**:至少记录到待处理列表或提示用户重试
|
|
539
543
|
|
|
540
544
|
## 缓存与幂等性
|
|
541
545
|
|