wtfai 1.4.4 → 1.5.0
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 +137 -0
- package/dist/iframe-bridge.d.ts +33 -0
- package/dist/iframe-bridge.js +94 -0
- package/dist/index.d.ts +1 -1
- package/dist/session.d.ts +38 -0
- package/dist/session.js +139 -0
- package/dist/ui/code.js +21 -3
- package/dist/ui/context.d.ts +3 -0
- package/dist/ui/context.js +4 -0
- package/dist/ui/markdown.d.ts +5 -1
- package/dist/ui/markdown.js +20 -16
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -256,6 +256,143 @@ console.log(state.isExecuting)
|
|
|
256
256
|
session.abort()
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
+
##### `setWorkflowVariables(values)`
|
|
260
|
+
|
|
261
|
+
更新服务端会话变量。
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
await session.setWorkflowVariables({
|
|
265
|
+
key: 'value',
|
|
266
|
+
userPreference: { theme: 'dark' }
|
|
267
|
+
})
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
##### `getWorkflowVariables()`
|
|
271
|
+
|
|
272
|
+
获取服务端会话变量。
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
const vars = await session.getWorkflowVariables()
|
|
276
|
+
console.log(vars.userPreference)
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
##### `registerIframeMethods(methods)`
|
|
280
|
+
|
|
281
|
+
注册可供 Iframe (Guest) 调用的方法。
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
session.registerIframeMethods({
|
|
285
|
+
// 定义一个获取用户信息的方法
|
|
286
|
+
getUserInfo: async () => {
|
|
287
|
+
return { name: 'Alice', id: 123 }
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
// 定义一个打开弹窗的方法
|
|
291
|
+
openModal: (type) => {
|
|
292
|
+
setModalVisible(type)
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Iframe SDK (Guest Side)
|
|
300
|
+
|
|
301
|
+
我们在 `packages/sdk/iframe-sdk.js` 提供了轻量级的 SDK,供嵌入的 HTML 工具使用。
|
|
302
|
+
|
|
303
|
+
该 SDK 不需要构建工具,直接通过 `<script>` 标签引入即可。
|
|
304
|
+
|
|
305
|
+
### 引入 SDK
|
|
306
|
+
|
|
307
|
+
```html
|
|
308
|
+
<script src="/iframe-sdk.js"></script>
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
### 使用方法
|
|
312
|
+
|
|
313
|
+
SDK 会在全局挂载 `IframeBridge` 对象。
|
|
314
|
+
|
|
315
|
+
#### `IframeBridge.call(method, params?)`
|
|
316
|
+
|
|
317
|
+
调用宿主 (Host) 方法。
|
|
318
|
+
|
|
319
|
+
```javascript
|
|
320
|
+
// 调用自定义方法
|
|
321
|
+
const userInfo = await IframeBridge.call('getUserInfo');
|
|
322
|
+
|
|
323
|
+
// 调用宿主方法并传参
|
|
324
|
+
await IframeBridge.call('log', ['Hello from iframe']);
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
#### `IframeBridge.hasMethod(methodName)`
|
|
328
|
+
|
|
329
|
+
检查宿主是否支持某个方法。
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
const supportsLog = await IframeBridge.hasMethod('log');
|
|
333
|
+
if (supportsLog) {
|
|
334
|
+
// ...
|
|
335
|
+
}
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
#### `IframeBridge.saveData(key, value)`
|
|
340
|
+
|
|
341
|
+
持久化存储数据(存储在当前工作流会话的 variables 中)。
|
|
342
|
+
|
|
343
|
+
```javascript
|
|
344
|
+
await IframeBridge.saveData('draft', { content: '...' });
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
#### `IframeBridge.loadData(key)`
|
|
348
|
+
|
|
349
|
+
读取持久化数据。
|
|
350
|
+
|
|
351
|
+
```javascript
|
|
352
|
+
const draft = await IframeBridge.loadData('draft');
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
#### `IframeBridge.executeWorkflow(workflowId, input)`
|
|
356
|
+
|
|
357
|
+
临时/嵌套执行另一个工作流。
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
const result = await IframeBridge.executeWorkflow('target-workflow-id', {
|
|
361
|
+
parts: [
|
|
362
|
+
{ type: 'image', url: 'https://...' }
|
|
363
|
+
]
|
|
364
|
+
});
|
|
365
|
+
console.log(result); // 执行产生的所有消息
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
#### `IframeBridge.uploadFile(file, options?)`
|
|
369
|
+
|
|
370
|
+
上传文件到云存储,返回 CDN URL。
|
|
371
|
+
|
|
372
|
+
```javascript
|
|
373
|
+
// 从 file input 上传
|
|
374
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
375
|
+
const url = await IframeBridge.uploadFile(fileInput.files[0]);
|
|
376
|
+
console.log('文件 URL:', url);
|
|
377
|
+
|
|
378
|
+
// 从 canvas 上传截图
|
|
379
|
+
canvas.toBlob(async (blob) => {
|
|
380
|
+
const file = new File([blob], 'screenshot.png', { type: 'image/png' });
|
|
381
|
+
const url = await IframeBridge.uploadFile(file);
|
|
382
|
+
console.log('截图 URL:', url);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// 指定资源类型
|
|
386
|
+
const url = await IframeBridge.uploadFile(file, {
|
|
387
|
+
resourceType: 'conversation' // 'conversation' | 'workflow' | 'knowledge-base'
|
|
388
|
+
});
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**说明**:
|
|
392
|
+
- 图片类型会自动压缩到 1920px 以内
|
|
393
|
+
- 音视频文件会自动获取时长信息
|
|
394
|
+
- 返回的是可直接访问的 CDN URL
|
|
395
|
+
|
|
259
396
|
---
|
|
260
397
|
|
|
261
398
|
### UploadService
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { WorkflowSession } from './session';
|
|
2
|
+
/**
|
|
3
|
+
* Host-side Iframe Bridge
|
|
4
|
+
* Handles communication between the SDK (Host) and the embedded Iframe (Guest).
|
|
5
|
+
*/
|
|
6
|
+
export declare class IframeBridgeHost {
|
|
7
|
+
private readonly session;
|
|
8
|
+
private iframeWindow;
|
|
9
|
+
private messageHandler;
|
|
10
|
+
constructor(session: WorkflowSession);
|
|
11
|
+
/**
|
|
12
|
+
* Attach the bridge to an iframe element.
|
|
13
|
+
* Starts listening for messages from the iframe.
|
|
14
|
+
*/
|
|
15
|
+
attach(iframe: HTMLIFrameElement): void;
|
|
16
|
+
/**
|
|
17
|
+
* Detach the bridge.
|
|
18
|
+
* Stops listening for messages.
|
|
19
|
+
*/
|
|
20
|
+
detach(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Handle an incoming call message from the iframe.
|
|
23
|
+
*/
|
|
24
|
+
private handleCall;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a method is available (built-in or user-defined).
|
|
27
|
+
*/
|
|
28
|
+
private hasMethod;
|
|
29
|
+
/**
|
|
30
|
+
* Send a response message back to the iframe.
|
|
31
|
+
*/
|
|
32
|
+
private sendResponse;
|
|
33
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
function _define_property(obj, key, value) {
|
|
2
|
+
if (key in obj) Object.defineProperty(obj, key, {
|
|
3
|
+
value: value,
|
|
4
|
+
enumerable: true,
|
|
5
|
+
configurable: true,
|
|
6
|
+
writable: true
|
|
7
|
+
});
|
|
8
|
+
else obj[key] = value;
|
|
9
|
+
return obj;
|
|
10
|
+
}
|
|
11
|
+
class IframeBridgeHost {
|
|
12
|
+
attach(iframe) {
|
|
13
|
+
if (this.messageHandler) this.detach();
|
|
14
|
+
this.iframeWindow = iframe.contentWindow;
|
|
15
|
+
this.messageHandler = async (event)=>{
|
|
16
|
+
const data = event.data;
|
|
17
|
+
if (!data || 'iframe-bridge-call' !== data.type) return;
|
|
18
|
+
await this.handleCall(data);
|
|
19
|
+
};
|
|
20
|
+
window.addEventListener('message', this.messageHandler);
|
|
21
|
+
}
|
|
22
|
+
detach() {
|
|
23
|
+
if (this.messageHandler) {
|
|
24
|
+
window.removeEventListener('message', this.messageHandler);
|
|
25
|
+
this.messageHandler = null;
|
|
26
|
+
}
|
|
27
|
+
this.iframeWindow = null;
|
|
28
|
+
}
|
|
29
|
+
async handleCall(message) {
|
|
30
|
+
const { callId, method, params = [] } = message;
|
|
31
|
+
let result;
|
|
32
|
+
let error;
|
|
33
|
+
try {
|
|
34
|
+
if ('hasMethod' === method) {
|
|
35
|
+
const [targetMethod] = params;
|
|
36
|
+
result = this.hasMethod(targetMethod);
|
|
37
|
+
} else if ('saveData' === method) {
|
|
38
|
+
const [key, value] = params;
|
|
39
|
+
const safeKey = `iframe_data_${key}`;
|
|
40
|
+
await this.session.setWorkflowVariables({
|
|
41
|
+
[safeKey]: value
|
|
42
|
+
});
|
|
43
|
+
result = true;
|
|
44
|
+
} else if ('loadData' === method) {
|
|
45
|
+
const [key] = params;
|
|
46
|
+
const variables = await this.session.getWorkflowVariables();
|
|
47
|
+
result = variables[`iframe_data_${key}`];
|
|
48
|
+
} else if ('executeWorkflow' === method) {
|
|
49
|
+
const [workflowId, input] = params;
|
|
50
|
+
result = await this.session.executeWorkflow(workflowId, input);
|
|
51
|
+
} else if ('uploadFile' === method) {
|
|
52
|
+
const [options] = params;
|
|
53
|
+
result = await this.session.uploadFileFromIframe(options);
|
|
54
|
+
} else {
|
|
55
|
+
const userMethods = this.session.getIframeMethods();
|
|
56
|
+
const fn = userMethods[method];
|
|
57
|
+
if ('function' == typeof fn) result = await fn(...params);
|
|
58
|
+
else throw new Error(`Method "${method}" not found or not registered on host.`);
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
error = e instanceof Error ? e.message : String(e);
|
|
62
|
+
}
|
|
63
|
+
this.sendResponse({
|
|
64
|
+
type: 'iframe-bridge-response',
|
|
65
|
+
callId,
|
|
66
|
+
result,
|
|
67
|
+
error
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
hasMethod(methodName) {
|
|
71
|
+
const builtIns = [
|
|
72
|
+
'saveData',
|
|
73
|
+
'loadData',
|
|
74
|
+
'hasMethod',
|
|
75
|
+
'executeWorkflow',
|
|
76
|
+
'uploadFile'
|
|
77
|
+
];
|
|
78
|
+
if (builtIns.includes(methodName)) return true;
|
|
79
|
+
const userMethods = this.session.getIframeMethods();
|
|
80
|
+
return 'function' == typeof userMethods[methodName];
|
|
81
|
+
}
|
|
82
|
+
sendResponse(message) {
|
|
83
|
+
if (this.iframeWindow) this.iframeWindow.postMessage(message, '*');
|
|
84
|
+
}
|
|
85
|
+
constructor(session){
|
|
86
|
+
_define_property(this, "session", void 0);
|
|
87
|
+
_define_property(this, "iframeWindow", void 0);
|
|
88
|
+
_define_property(this, "messageHandler", void 0);
|
|
89
|
+
this.session = session;
|
|
90
|
+
this.iframeWindow = null;
|
|
91
|
+
this.messageHandler = null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export { IframeBridgeHost };
|
package/dist/index.d.ts
CHANGED
|
@@ -4,4 +4,4 @@ export { WorkflowSession } from './session';
|
|
|
4
4
|
export { UploadService } from './upload';
|
|
5
5
|
export type { ClientConfig, SessionOptions, SessionState, SessionEventListeners, SendInput, UploadParams, UploadImageParams, CompressOptions, WorkflowInfo, ContentPart, } from './types';
|
|
6
6
|
export type { SimpleMessage, Workflow, WorkflowConfig, } from '@our-llm/shared/types';
|
|
7
|
-
export { Markdown } from './ui/markdown';
|
|
7
|
+
export { Markdown, type MarkdownProps } from './ui/markdown';
|
package/dist/session.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SimpleMessage } from '@our-llm/shared/types';
|
|
1
2
|
import type { SessionOptions, SessionState, SessionEventListeners, SendInput } from './types';
|
|
2
3
|
import { UploadService } from './upload';
|
|
3
4
|
/**
|
|
@@ -13,6 +14,26 @@ export declare class WorkflowSession {
|
|
|
13
14
|
private listeners;
|
|
14
15
|
private abortController?;
|
|
15
16
|
constructor(workflowId: string, baseUrl: string, uploadService: UploadService, headers?: Record<string, string>, options?: SessionOptions);
|
|
17
|
+
private iframeMethods;
|
|
18
|
+
/**
|
|
19
|
+
* 注册可供 iframe 调用的自定义方法
|
|
20
|
+
*/
|
|
21
|
+
registerIframeMethods(methods: Record<string, (...args: any[]) => any>): this;
|
|
22
|
+
/**
|
|
23
|
+
* 获取已注册的 iframe 方法(供内部使用)
|
|
24
|
+
*/
|
|
25
|
+
getIframeMethods(): Record<string, (...args: any[]) => any>;
|
|
26
|
+
/**
|
|
27
|
+
* 上传文件(供 iframe-bridge 使用)
|
|
28
|
+
* @param options 上传选项
|
|
29
|
+
* @returns 文件的 CDN URL
|
|
30
|
+
*/
|
|
31
|
+
uploadFileFromIframe(options: {
|
|
32
|
+
base64: string;
|
|
33
|
+
filename: string;
|
|
34
|
+
mimeType: string;
|
|
35
|
+
resourceType?: 'conversation' | 'workflow' | 'knowledge-base';
|
|
36
|
+
}): Promise<string>;
|
|
16
37
|
/**
|
|
17
38
|
* 监听事件
|
|
18
39
|
*/
|
|
@@ -37,6 +58,23 @@ export declare class WorkflowSession {
|
|
|
37
58
|
* 恢复历史会话
|
|
38
59
|
*/
|
|
39
60
|
restore(): Promise<void>;
|
|
61
|
+
/**
|
|
62
|
+
* 获取工作流变量
|
|
63
|
+
*/
|
|
64
|
+
getWorkflowVariables(): Promise<Record<string, any>>;
|
|
65
|
+
/**
|
|
66
|
+
* 更新会话状态(服务端变量)
|
|
67
|
+
* @param values 需要更新的变量键值对
|
|
68
|
+
*/
|
|
69
|
+
setWorkflowVariables(values: Record<string, unknown>): Promise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* 执行指定的工作流(临时/嵌套执行)
|
|
72
|
+
* 不会影响当前会话的状态(messages 等)
|
|
73
|
+
* @param workflowId 目标工作流 ID
|
|
74
|
+
* @param input 输入数据(与 send 方法一致)
|
|
75
|
+
* @returns 执行结果的所有消息
|
|
76
|
+
*/
|
|
77
|
+
executeWorkflow(workflowId: string, input: SendInput): Promise<SimpleMessage[]>;
|
|
40
78
|
/**
|
|
41
79
|
* 发送消息执行工作流
|
|
42
80
|
*/
|
package/dist/session.js
CHANGED
|
@@ -11,6 +11,41 @@ function _define_property(obj, key, value) {
|
|
|
11
11
|
return obj;
|
|
12
12
|
}
|
|
13
13
|
class WorkflowSession {
|
|
14
|
+
registerIframeMethods(methods) {
|
|
15
|
+
this.iframeMethods = {
|
|
16
|
+
...this.iframeMethods,
|
|
17
|
+
...methods
|
|
18
|
+
};
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
getIframeMethods() {
|
|
22
|
+
return this.iframeMethods;
|
|
23
|
+
}
|
|
24
|
+
async uploadFileFromIframe(options) {
|
|
25
|
+
const { base64, filename, mimeType, resourceType = 'conversation' } = options;
|
|
26
|
+
const byteString = atob(base64);
|
|
27
|
+
const arrayBuffer = new ArrayBuffer(byteString.length);
|
|
28
|
+
const uint8Array = new Uint8Array(arrayBuffer);
|
|
29
|
+
for(let i = 0; i < byteString.length; i++)uint8Array[i] = byteString.charCodeAt(i);
|
|
30
|
+
const blob = new Blob([
|
|
31
|
+
uint8Array
|
|
32
|
+
], {
|
|
33
|
+
type: mimeType
|
|
34
|
+
});
|
|
35
|
+
const file = new File([
|
|
36
|
+
blob
|
|
37
|
+
], filename, {
|
|
38
|
+
type: mimeType
|
|
39
|
+
});
|
|
40
|
+
if (mimeType.startsWith('image/')) return this.uploadService.uploadImage({
|
|
41
|
+
file,
|
|
42
|
+
resourceType
|
|
43
|
+
});
|
|
44
|
+
return this.uploadService.uploadFile({
|
|
45
|
+
file,
|
|
46
|
+
resourceType
|
|
47
|
+
});
|
|
48
|
+
}
|
|
14
49
|
on(event, listener) {
|
|
15
50
|
if (!this.listeners.has(event)) this.listeners.set(event, new Set());
|
|
16
51
|
this.listeners.get(event).add(listener);
|
|
@@ -52,6 +87,108 @@ class WorkflowSession {
|
|
|
52
87
|
messages: data.messages
|
|
53
88
|
});
|
|
54
89
|
}
|
|
90
|
+
async getWorkflowVariables() {
|
|
91
|
+
if (!this.state.threadId) throw new Error('No active session (missing threadId)');
|
|
92
|
+
const response = await fetch(`${this.baseUrl}/workflows/${this.workflowId}/state?threadId=${this.state.threadId}`, {
|
|
93
|
+
headers: this.headers
|
|
94
|
+
});
|
|
95
|
+
if (!response.ok) throw new Error('Failed to get workflow variables');
|
|
96
|
+
const data = await response.json();
|
|
97
|
+
return data.variables || {};
|
|
98
|
+
}
|
|
99
|
+
async setWorkflowVariables(values) {
|
|
100
|
+
if (!this.state.threadId || this.state.threadId.startsWith('tmp_')) throw new Error('No active session (missing valid threadId)');
|
|
101
|
+
const response = await fetch(`${this.baseUrl}/workflows/${this.workflowId}/variables`, {
|
|
102
|
+
method: 'POST',
|
|
103
|
+
headers: {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
...this.headers
|
|
106
|
+
},
|
|
107
|
+
body: JSON.stringify({
|
|
108
|
+
threadId: this.state.threadId,
|
|
109
|
+
values
|
|
110
|
+
})
|
|
111
|
+
});
|
|
112
|
+
if (!response.ok) throw new Error('Failed to update state');
|
|
113
|
+
}
|
|
114
|
+
async executeWorkflow(workflowId, input) {
|
|
115
|
+
const { parts: inputParts, ...rest } = input;
|
|
116
|
+
const parts = [];
|
|
117
|
+
for (const part of inputParts)switch(part.type){
|
|
118
|
+
case 'text':
|
|
119
|
+
parts.push({
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: part.text
|
|
122
|
+
});
|
|
123
|
+
break;
|
|
124
|
+
case 'image':
|
|
125
|
+
{
|
|
126
|
+
const imageUrl = await this.resolveMediaUrl(part, 'image');
|
|
127
|
+
parts.push({
|
|
128
|
+
type: 'image',
|
|
129
|
+
url: imageUrl
|
|
130
|
+
});
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
case 'document':
|
|
134
|
+
{
|
|
135
|
+
const docInfo = await this.resolveDocumentInfo(part);
|
|
136
|
+
parts.push({
|
|
137
|
+
type: 'document',
|
|
138
|
+
url: docInfo.url,
|
|
139
|
+
filename: docInfo.filename,
|
|
140
|
+
mimeType: docInfo.mimeType
|
|
141
|
+
});
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
case 'video':
|
|
145
|
+
{
|
|
146
|
+
const videoUrl = await this.resolveMediaUrl(part, 'video');
|
|
147
|
+
parts.push({
|
|
148
|
+
type: 'video',
|
|
149
|
+
url: videoUrl
|
|
150
|
+
});
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
case 'audio':
|
|
154
|
+
{
|
|
155
|
+
const audioUrl = await this.resolveMediaUrl(part, 'audio');
|
|
156
|
+
parts.push({
|
|
157
|
+
type: 'audio',
|
|
158
|
+
url: audioUrl
|
|
159
|
+
});
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const executionInput = {
|
|
164
|
+
messages: [
|
|
165
|
+
{
|
|
166
|
+
content: parts
|
|
167
|
+
}
|
|
168
|
+
]
|
|
169
|
+
};
|
|
170
|
+
const resultMessages = [];
|
|
171
|
+
const tempThreadId = v7();
|
|
172
|
+
return new Promise((resolve, reject)=>{
|
|
173
|
+
executeWorkflowSSE(`${this.baseUrl}/workflows/${workflowId}/execute`, {
|
|
174
|
+
input: executionInput,
|
|
175
|
+
threadId: tempThreadId,
|
|
176
|
+
...rest
|
|
177
|
+
}, {
|
|
178
|
+
onNodeEnd: (data)=>{
|
|
179
|
+
if (data.m && data.m.length > 0) {
|
|
180
|
+
for (const msg of data.m)if ('human' !== msg.type) resultMessages.push(msg);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
onComplete: ()=>{
|
|
184
|
+
resolve(resultMessages);
|
|
185
|
+
},
|
|
186
|
+
onError: (err)=>{
|
|
187
|
+
reject(new Error(err.message));
|
|
188
|
+
}
|
|
189
|
+
}, this.headers).catch(reject);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
55
192
|
async send({ parts: inputParts, ...rest }) {
|
|
56
193
|
if (this.state.isExecuting) throw new Error('工作流正在执行中');
|
|
57
194
|
this.updateState({
|
|
@@ -310,6 +447,7 @@ class WorkflowSession {
|
|
|
310
447
|
_define_property(this, "state", void 0);
|
|
311
448
|
_define_property(this, "listeners", void 0);
|
|
312
449
|
_define_property(this, "abortController", void 0);
|
|
450
|
+
_define_property(this, "iframeMethods", void 0);
|
|
313
451
|
this.workflowId = workflowId;
|
|
314
452
|
this.baseUrl = baseUrl;
|
|
315
453
|
this.uploadService = uploadService;
|
|
@@ -319,6 +457,7 @@ class WorkflowSession {
|
|
|
319
457
|
isExecuting: false
|
|
320
458
|
};
|
|
321
459
|
this.listeners = new Map();
|
|
460
|
+
this.iframeMethods = {};
|
|
322
461
|
if (options.threadId) this.state.threadId = options.threadId;
|
|
323
462
|
else this.state.threadId = v7();
|
|
324
463
|
}
|
package/dist/ui/code.js
CHANGED
|
@@ -1,18 +1,36 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useRef } from "react";
|
|
2
3
|
import { CodeHighlighter, Mermaid } from "@ant-design/x";
|
|
3
4
|
import { jsonrepair } from "jsonrepair";
|
|
5
|
+
import { useWorkflowSession } from "./context.js";
|
|
6
|
+
import { IframeBridgeHost } from "../iframe-bridge.js";
|
|
4
7
|
const Code = (props)=>{
|
|
5
8
|
var _className_match;
|
|
6
9
|
const { className, children } = props;
|
|
7
10
|
const lang = (null == className ? void 0 : null == (_className_match = className.match(/language-(\w+)/)) ? void 0 : _className_match[1]) || '';
|
|
11
|
+
const session = useWorkflowSession();
|
|
12
|
+
const iframeRef = useRef(null);
|
|
13
|
+
const bridgeRef = useRef(null);
|
|
14
|
+
useEffect(()=>{
|
|
15
|
+
if (session && iframeRef.current) {
|
|
16
|
+
if (!bridgeRef.current) bridgeRef.current = new IframeBridgeHost(session);
|
|
17
|
+
bridgeRef.current.attach(iframeRef.current);
|
|
18
|
+
}
|
|
19
|
+
return ()=>{
|
|
20
|
+
var _bridgeRef_current;
|
|
21
|
+
null == (_bridgeRef_current = bridgeRef.current) || _bridgeRef_current.detach();
|
|
22
|
+
};
|
|
23
|
+
}, [
|
|
24
|
+
session
|
|
25
|
+
]);
|
|
8
26
|
if ('string' != typeof children) return null;
|
|
9
27
|
if ('mermaid' === lang) return /*#__PURE__*/ jsx(Mermaid, {
|
|
10
28
|
children: children
|
|
11
29
|
});
|
|
12
|
-
|
|
13
|
-
if ('tmpl' === lang) {
|
|
30
|
+
if ('tmpl' === lang) try {
|
|
14
31
|
const json = JSON.parse(jsonrepair(children));
|
|
15
32
|
if ('iframe' === json.type) return /*#__PURE__*/ jsx("iframe", {
|
|
33
|
+
ref: iframeRef,
|
|
16
34
|
src: json.data.src,
|
|
17
35
|
width: "100%",
|
|
18
36
|
allow: "clipboard-read; clipboard-write; camera; microphone",
|
|
@@ -23,7 +41,7 @@ const Code = (props)=>{
|
|
|
23
41
|
display: 'block'
|
|
24
42
|
}
|
|
25
43
|
});
|
|
26
|
-
}
|
|
44
|
+
} catch {}
|
|
27
45
|
return /*#__PURE__*/ jsx(CodeHighlighter, {
|
|
28
46
|
lang: lang,
|
|
29
47
|
children: children
|
package/dist/ui/markdown.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { XMarkdownProps } from '@ant-design/x-markdown';
|
|
2
2
|
import '@ant-design/x-markdown/themes/light.css';
|
|
3
|
-
|
|
3
|
+
import type { WorkflowSession } from '../session';
|
|
4
|
+
export interface MarkdownProps extends XMarkdownProps {
|
|
5
|
+
session?: WorkflowSession;
|
|
6
|
+
}
|
|
7
|
+
declare const Markdown: ({ className, streaming, config, components, session, ...props }: MarkdownProps) => import("react/jsx-runtime").JSX.Element;
|
|
4
8
|
export { Markdown };
|
package/dist/ui/markdown.js
CHANGED
|
@@ -6,23 +6,27 @@ import zh_CN from "@ant-design/x/locale/zh_CN";
|
|
|
6
6
|
import clsx from "clsx";
|
|
7
7
|
import { Code } from "./code.js";
|
|
8
8
|
import { XProvider } from "@ant-design/x";
|
|
9
|
-
|
|
9
|
+
import { SessionContext } from "./context.js";
|
|
10
|
+
const Markdown = ({ className, streaming, config, components, session, ...props })=>/*#__PURE__*/ jsx(XProvider, {
|
|
10
11
|
locale: zh_CN,
|
|
11
|
-
children: /*#__PURE__*/ jsx(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
12
|
+
children: /*#__PURE__*/ jsx(SessionContext.Provider, {
|
|
13
|
+
value: session || null,
|
|
14
|
+
children: /*#__PURE__*/ jsx(XMarkdown, {
|
|
15
|
+
className: clsx(className, 'x-markdown-light'),
|
|
16
|
+
streaming: {
|
|
17
|
+
...streaming,
|
|
18
|
+
enableAnimation: true
|
|
19
|
+
},
|
|
20
|
+
config: {
|
|
21
|
+
...config,
|
|
22
|
+
extensions: Latex()
|
|
23
|
+
},
|
|
24
|
+
components: {
|
|
25
|
+
...components,
|
|
26
|
+
code: Code
|
|
27
|
+
},
|
|
28
|
+
...props
|
|
29
|
+
})
|
|
26
30
|
})
|
|
27
31
|
});
|
|
28
32
|
export { Markdown };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wtfai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -15,16 +15,16 @@
|
|
|
15
15
|
"devDependencies": {
|
|
16
16
|
"@eslint/js": "^9.39.2",
|
|
17
17
|
"@rsbuild/plugin-react": "^1.4.3",
|
|
18
|
-
"@rslib/core": "^0.19.
|
|
19
|
-
"@types/react": "^19.2.
|
|
18
|
+
"@rslib/core": "^0.19.3",
|
|
19
|
+
"@types/react": "^19.2.9",
|
|
20
20
|
"eslint": "^9.39.2",
|
|
21
21
|
"eslint-config-prettier": "^10.1.8",
|
|
22
22
|
"eslint-plugin-prettier": "^5.5.5",
|
|
23
|
-
"globals": "^17.
|
|
24
|
-
"prettier": "^3.8.
|
|
23
|
+
"globals": "^17.1.0",
|
|
24
|
+
"prettier": "^3.8.1",
|
|
25
25
|
"react": "^19.2.3",
|
|
26
26
|
"typescript": "^5.9.3",
|
|
27
|
-
"typescript-eslint": "^8.53.
|
|
27
|
+
"typescript-eslint": "^8.53.1"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": ">=16.9.0",
|