wtfai 1.5.6 → 1.5.8

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/README.md CHANGED
@@ -34,7 +34,7 @@ const client = new Wtfai({
34
34
  const session = client.createSession('workflow-id')
35
35
 
36
36
  // 3. 发送消息
37
- await session.send({ content: '你好' })
37
+ await session.send({ parts: [{ type: 'text', text: '你好' }] })
38
38
  ```
39
39
 
40
40
  ## API 文档
@@ -442,6 +442,128 @@ const url = await IframeBridge.uploadFile(file, {
442
442
 
443
443
  ---
444
444
 
445
+ ### Session Data CRUD API
446
+
447
+ 用于会话级数据的持久化存储,与 `saveData/loadData` 不同,这是一套完整的 CRUD 接口,支持集合(Collection)概念,适合存储结构化数据。
448
+
449
+ > **与 saveData/loadData 的区别**:
450
+ > - `saveData/loadData` 存储在工作流 state 中(适合简单 key-value)
451
+ > - Session Data API 存储在独立数据表中(适合结构化数据、列表数据)
452
+
453
+ #### `IframeBridge.createRecord(collection, data)`
454
+
455
+ 在指定集合中创建一条记录。
456
+
457
+ ```javascript
458
+ const id = await IframeBridge.createRecord('notes', {
459
+ title: 'My Note',
460
+ content: 'Hello World',
461
+ createdAt: Date.now()
462
+ });
463
+ console.log('创建成功, ID:', id);
464
+ ```
465
+
466
+ #### `IframeBridge.getRecord(collection, id)`
467
+
468
+ 获取单条记录。
469
+
470
+ ```javascript
471
+ const data = await IframeBridge.getRecord('notes', id);
472
+ if (data) {
473
+ console.log('记录内容:', data.title, data.content);
474
+ } else {
475
+ console.log('记录不存在');
476
+ }
477
+ ```
478
+
479
+ #### `IframeBridge.updateRecord(collection, id, data)`
480
+
481
+ 更新记录(完整替换)。
482
+
483
+ ```javascript
484
+ await IframeBridge.updateRecord('notes', id, {
485
+ title: 'Updated Title',
486
+ content: 'New content',
487
+ updatedAt: Date.now()
488
+ });
489
+ ```
490
+
491
+ #### `IframeBridge.deleteRecord(collection, id)`
492
+
493
+ 删除单条记录。
494
+
495
+ ```javascript
496
+ await IframeBridge.deleteRecord('notes', id);
497
+ ```
498
+
499
+ #### `IframeBridge.listRecords(collection, options?)`
500
+
501
+ 分页查询集合中的记录。
502
+
503
+ ```javascript
504
+ const result = await IframeBridge.listRecords('notes', {
505
+ page: 1,
506
+ pageSize: 10
507
+ });
508
+ console.log(`共 ${result.total} 条记录`);
509
+ result.records.forEach(r => {
510
+ console.log(r.id, r.data.title);
511
+ });
512
+ ```
513
+
514
+ #### `IframeBridge.clearCollection(collection)`
515
+
516
+ 清空集合中的所有记录。
517
+
518
+ ```javascript
519
+ const count = await IframeBridge.clearCollection('notes');
520
+ console.log(`已删除 ${count} 条记录`);
521
+ ```
522
+
523
+ **使用场景**:
524
+ - 待办事项列表
525
+ - 用户收藏/历史记录
526
+ - 表单草稿
527
+ - 游戏存档
528
+
529
+ ---
530
+
531
+ ## HTTP API
532
+
533
+ 以下 API 可直接通过 HTTP 调用,无需使用 SDK。
534
+
535
+ ### 中断工作流执行
536
+
537
+ 中断正在运行的工作流。工作流会返回 `error` 事件携带中断原因后关闭连接。
538
+
539
+ **请求**
540
+
541
+ ```http
542
+ POST /workflows/:workflowId/interrupt
543
+ Content-Type: application/json
544
+
545
+ {
546
+ "threadId": "会话ID",
547
+ "reason": "中断原因"
548
+ }
549
+ ```
550
+
551
+ **响应**
552
+
553
+ ```json
554
+ {
555
+ "success": true,
556
+ "local": true // true=本地实例中断, false=通过集群广播
557
+ }
558
+ ```
559
+
560
+ **说明**:
561
+ - 支持集群模式,会通过 Redis Pub/Sub 广播中断信号到所有实例
562
+ - 工作流被中断后会触发 `error` 事件,`message` 为传入的 `reason`
563
+ - 如果找不到对应的执行会话,返回 404
564
+
565
+ ---
566
+
445
567
  ### UploadService
446
568
 
447
569
  文件上传服务(通过 `client.upload` 访问)。
@@ -52,9 +52,28 @@ class IframeBridgeHost {
52
52
  const [title] = params;
53
53
  await this.session.updateSessionTitle(title);
54
54
  result = true;
55
- } else if ('uploadFile' === method) {
55
+ } else if ('getSessionId' === method) result = this.session.getState().threadId;
56
+ else if ('uploadFile' === method) {
56
57
  const [options] = params;
57
58
  result = await this.session.uploadFileFromIframe(options);
59
+ } else if ('createRecord' === method) {
60
+ const [collection, data] = params;
61
+ result = await this.session.createRecord(collection, data);
62
+ } else if ('getRecord' === method) {
63
+ const [collection, id] = params;
64
+ result = await this.session.getRecord(collection, id);
65
+ } else if ('updateRecord' === method) {
66
+ const [collection, id, data] = params;
67
+ result = await this.session.updateRecord(collection, id, data);
68
+ } else if ('deleteRecord' === method) {
69
+ const [collection, id] = params;
70
+ result = await this.session.deleteRecord(collection, id);
71
+ } else if ('listRecords' === method) {
72
+ const [collection, options] = params;
73
+ result = await this.session.listRecords(collection, options);
74
+ } else if ('clearCollection' === method) {
75
+ const [collection] = params;
76
+ result = await this.session.clearCollection(collection);
58
77
  } else {
59
78
  const userMethods = this.session.getIframeMethods();
60
79
  const fn = userMethods[method];
@@ -78,7 +97,14 @@ class IframeBridgeHost {
78
97
  'hasMethod',
79
98
  'executeWorkflow',
80
99
  'uploadFile',
81
- 'updateSessionTitle'
100
+ 'updateSessionTitle',
101
+ 'getSessionId',
102
+ 'createRecord',
103
+ 'getRecord',
104
+ 'updateRecord',
105
+ 'deleteRecord',
106
+ 'listRecords',
107
+ 'clearCollection'
82
108
  ];
83
109
  if (builtIns.includes(methodName)) return true;
84
110
  const userMethods = this.session.getIframeMethods();
package/dist/session.d.ts CHANGED
@@ -71,6 +71,54 @@ export declare class WorkflowSession {
71
71
  * @param title 新的标题
72
72
  */
73
73
  updateSessionTitle(title: string): Promise<void>;
74
+ /**
75
+ * 创建数据记录
76
+ * @param collection 集合名称
77
+ * @param data 数据内容
78
+ * @returns 新创建记录的 ID
79
+ */
80
+ createRecord(collection: string, data: Record<string, unknown>): Promise<string>;
81
+ /**
82
+ * 获取数据记录
83
+ * @param collection 集合名称
84
+ * @param id 记录 ID
85
+ * @returns 记录数据,不存在时返回 null
86
+ */
87
+ getRecord(collection: string, id: string): Promise<Record<string, unknown> | null>;
88
+ /**
89
+ * 更新数据记录
90
+ * @param collection 集合名称
91
+ * @param id 记录 ID
92
+ * @param data 新的数据内容
93
+ */
94
+ updateRecord(collection: string, id: string, data: Record<string, unknown>): Promise<void>;
95
+ /**
96
+ * 删除数据记录
97
+ * @param collection 集合名称
98
+ * @param id 记录 ID
99
+ */
100
+ deleteRecord(collection: string, id: string): Promise<void>;
101
+ /**
102
+ * 列表查询数据记录
103
+ * @param collection 集合名称
104
+ * @param options 分页选项
105
+ */
106
+ listRecords(collection: string, options?: {
107
+ page?: number;
108
+ pageSize?: number;
109
+ }): Promise<{
110
+ records: Array<{
111
+ id: string;
112
+ data: Record<string, unknown>;
113
+ }>;
114
+ total: number;
115
+ }>;
116
+ /**
117
+ * 清空集合中的所有数据
118
+ * @param collection 集合名称
119
+ * @returns 被删除的记录数量
120
+ */
121
+ clearCollection(collection: string): Promise<number>;
74
122
  /**
75
123
  * 执行指定的工作流(临时/嵌套执行),工作流需要使用消息生成组件来生成消息供收集
76
124
  * 不会影响当前会话的状态(messages 等)
package/dist/session.js CHANGED
@@ -158,6 +158,90 @@ class WorkflowSession {
158
158
  }, this.headers).catch(reject);
159
159
  });
160
160
  }
161
+ async createRecord(collection, data) {
162
+ this.assertNotDisposed();
163
+ if (!this.state.threadId) throw new Error('No active session (missing threadId)');
164
+ const response = await fetch(`${this.baseUrl}/workflows/${this.workflowId}/data/${encodeURIComponent(collection)}`, {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Content-Type': 'application/json',
168
+ ...this.headers
169
+ },
170
+ body: JSON.stringify({
171
+ threadId: this.state.threadId,
172
+ data
173
+ })
174
+ });
175
+ if (!response.ok) throw new Error('Failed to create record');
176
+ const result = await response.json();
177
+ return result.id;
178
+ }
179
+ async getRecord(collection, id) {
180
+ this.assertNotDisposed();
181
+ if (!this.state.threadId) throw new Error('No active session (missing threadId)');
182
+ const url = new URL(`${this.baseUrl}/workflows/${this.workflowId}/data/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`);
183
+ url.searchParams.set('threadId', this.state.threadId);
184
+ const response = await fetch(url.toString(), {
185
+ headers: this.headers
186
+ });
187
+ if (404 === response.status) return null;
188
+ if (!response.ok) throw new Error('Failed to get record');
189
+ const result = await response.json();
190
+ return result.data;
191
+ }
192
+ async updateRecord(collection, id, data) {
193
+ this.assertNotDisposed();
194
+ if (!this.state.threadId) throw new Error('No active session (missing threadId)');
195
+ const response = await fetch(`${this.baseUrl}/workflows/${this.workflowId}/data/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`, {
196
+ method: 'PUT',
197
+ headers: {
198
+ 'Content-Type': 'application/json',
199
+ ...this.headers
200
+ },
201
+ body: JSON.stringify({
202
+ threadId: this.state.threadId,
203
+ data
204
+ })
205
+ });
206
+ if (!response.ok) throw new Error('Failed to update record');
207
+ }
208
+ async deleteRecord(collection, id) {
209
+ this.assertNotDisposed();
210
+ if (!this.state.threadId) throw new Error('No active session (missing threadId)');
211
+ const url = new URL(`${this.baseUrl}/workflows/${this.workflowId}/data/${encodeURIComponent(collection)}/${encodeURIComponent(id)}`);
212
+ url.searchParams.set('threadId', this.state.threadId);
213
+ const response = await fetch(url.toString(), {
214
+ method: 'DELETE',
215
+ headers: this.headers
216
+ });
217
+ if (!response.ok) throw new Error('Failed to delete record');
218
+ }
219
+ async listRecords(collection, options) {
220
+ this.assertNotDisposed();
221
+ if (!this.state.threadId) throw new Error('No active session (missing threadId)');
222
+ const url = new URL(`${this.baseUrl}/workflows/${this.workflowId}/data/${encodeURIComponent(collection)}`);
223
+ url.searchParams.set('threadId', this.state.threadId);
224
+ if (null == options ? void 0 : options.page) url.searchParams.set('page', String(options.page));
225
+ if (null == options ? void 0 : options.pageSize) url.searchParams.set('pageSize', String(options.pageSize));
226
+ const response = await fetch(url.toString(), {
227
+ headers: this.headers
228
+ });
229
+ if (!response.ok) throw new Error('Failed to list records');
230
+ return response.json();
231
+ }
232
+ async clearCollection(collection) {
233
+ this.assertNotDisposed();
234
+ if (!this.state.threadId) throw new Error('No active session (missing threadId)');
235
+ const url = new URL(`${this.baseUrl}/workflows/${this.workflowId}/data/${encodeURIComponent(collection)}`);
236
+ url.searchParams.set('threadId', this.state.threadId);
237
+ const response = await fetch(url.toString(), {
238
+ method: 'DELETE',
239
+ headers: this.headers
240
+ });
241
+ if (!response.ok) throw new Error('Failed to clear collection');
242
+ const result = await response.json();
243
+ return result.deleted;
244
+ }
161
245
  async executeWorkflow(workflowId, input) {
162
246
  this.assertNotDisposed();
163
247
  const { parts: inputParts, ...rest } = input;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wtfai",
3
- "version": "1.5.6",
3
+ "version": "1.5.8",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -13,26 +13,26 @@
13
13
  "dist"
14
14
  ],
15
15
  "devDependencies": {
16
- "@eslint/js": "^9.39.2",
16
+ "@eslint/js": "^9.39.3",
17
17
  "@rsbuild/plugin-react": "^1.4.5",
18
- "@rslib/core": "^0.19.5",
19
- "@types/react": "^19.2.13",
20
- "eslint": "^9.39.2",
18
+ "@rslib/core": "^0.19.6",
19
+ "@types/react": "^19.2.14",
20
+ "eslint": "^9.39.3",
21
21
  "eslint-config-prettier": "^10.1.8",
22
22
  "eslint-plugin-prettier": "^5.5.5",
23
23
  "globals": "^17.3.0",
24
24
  "prettier": "^3.8.1",
25
25
  "react": "^19.2.4",
26
26
  "typescript": "^5.9.3",
27
- "typescript-eslint": "^8.54.0"
27
+ "typescript-eslint": "^8.56.1"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": ">=16.9.0",
31
31
  "react-dom": ">=16.9.0"
32
32
  },
33
33
  "dependencies": {
34
- "@ant-design/x": "^2.2.1",
35
- "@ant-design/x-markdown": "^2.2.1",
34
+ "@ant-design/x": "^2.2.2",
35
+ "@ant-design/x-markdown": "^2.2.2",
36
36
  "@microsoft/fetch-event-source": "^2.0.1",
37
37
  "clsx": "^2.1.1",
38
38
  "compressorjs": "^1.2.1",