wtfai 1.6.8 → 1.7.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 +43 -0
- package/dist/iframe-bridge.js +5 -1
- package/dist/session.d.ts +6 -0
- package/dist/session.js +16 -0
- package/dist/ui/code.css +1 -45
- package/dist/ui/code.js +24 -45
- package/dist/ui/context.d.ts +13 -2
- package/dist/ui/context.js +3 -3
- package/dist/ui/markdown.d.ts +10 -3
- package/dist/ui/markdown.js +10 -3
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -219,6 +219,49 @@ SDK 会自动将其渲染为一个美观的“药丸状”卡片按钮,包含
|
|
|
219
219
|
|
|
220
220
|
点击该卡片会触发 `contentAction` 事件,`type` 为 `'workflow'`。
|
|
221
221
|
|
|
222
|
+
#### 3. 内容包装器 (contentWrapper)
|
|
223
|
+
|
|
224
|
+
`WorkflowRegistry` 支持 `contentWrapper` 属性,允许开发者在渲染 Markdown 内容前对其进行包装或增强。目前支持对 `code` 块进行包装。
|
|
225
|
+
|
|
226
|
+
**配置类型**:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
export type ContentWrapperConfig = {
|
|
230
|
+
code?: (
|
|
231
|
+
props: ComponentProps, // props 包含 lang, children, streamStatus 等
|
|
232
|
+
) => ((children: ReactNode) => ReactNode) | null | undefined | false
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**使用示例**:
|
|
237
|
+
|
|
238
|
+
比如,你想给所有的 `html:run` 类型的代码预览块增加一个自定义的消息提示:
|
|
239
|
+
|
|
240
|
+
```tsx
|
|
241
|
+
import { WorkflowRegistry } from 'wtfai';
|
|
242
|
+
|
|
243
|
+
<WorkflowRegistry
|
|
244
|
+
contentWrapper={{
|
|
245
|
+
code: (props) => {
|
|
246
|
+
// 只有特定语言才进行包装
|
|
247
|
+
if (props.lang === 'html:run') {
|
|
248
|
+
return (children) => (
|
|
249
|
+
<div className="my-custom-wrapper">
|
|
250
|
+
<div className="wrapper-header">这是我自定义加的内容</div>
|
|
251
|
+
{children}
|
|
252
|
+
</div>
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
return false // 返回 false 表示不包装,由 SDK 按默认方式渲染
|
|
256
|
+
},
|
|
257
|
+
}}
|
|
258
|
+
>
|
|
259
|
+
<ChatPage />
|
|
260
|
+
</WorkflowRegistry>
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
这种机制非常适合在不修改 SDK 源码的前提下,为特定类型的内容增加业务相关的 UI 装饰(如操作按钮、免责声明、权限校验提示等)。
|
|
264
|
+
|
|
222
265
|
**最佳实践(推荐组合)**
|
|
223
266
|
|
|
224
267
|
1. **打字机效果**:使用 `token` 拼接流式内容。
|
package/dist/iframe-bridge.js
CHANGED
|
@@ -76,6 +76,9 @@ class IframeBridgeHost {
|
|
|
76
76
|
} else if ('clearCollection' === method) {
|
|
77
77
|
const [collection] = params;
|
|
78
78
|
result = await this.session.clearCollection(collection);
|
|
79
|
+
} else if ('fetch' === method) {
|
|
80
|
+
const [url, options] = params;
|
|
81
|
+
result = await this.session.fetch(url, options);
|
|
79
82
|
} else {
|
|
80
83
|
const userMethods = this.session.getIframeMethods();
|
|
81
84
|
const fn = userMethods[method];
|
|
@@ -107,7 +110,8 @@ class IframeBridgeHost {
|
|
|
107
110
|
'updateRecord',
|
|
108
111
|
'deleteRecord',
|
|
109
112
|
'listRecords',
|
|
110
|
-
'clearCollection'
|
|
113
|
+
'clearCollection',
|
|
114
|
+
'fetch'
|
|
111
115
|
];
|
|
112
116
|
if (builtIns.includes(methodName)) return true;
|
|
113
117
|
const userMethods = this.session.getIframeMethods();
|
package/dist/session.d.ts
CHANGED
|
@@ -129,6 +129,12 @@ export declare class WorkflowSession {
|
|
|
129
129
|
* @returns 执行结果的所有消息
|
|
130
130
|
*/
|
|
131
131
|
executeWorkflow(workflowId: string, input: SendInput): Promise<SimpleMessage[]>;
|
|
132
|
+
/**
|
|
133
|
+
* 通用的 fetch 方法,支持调用后端 API
|
|
134
|
+
* @param url 请求地址,如果是相对路径则会自动拼接 baseUrl
|
|
135
|
+
* @param options 请求配置
|
|
136
|
+
*/
|
|
137
|
+
fetch(url: string, options?: RequestInit): Promise<any>;
|
|
132
138
|
/**
|
|
133
139
|
* 发送消息执行工作流
|
|
134
140
|
*/
|
package/dist/session.js
CHANGED
|
@@ -346,6 +346,22 @@ class WorkflowSession {
|
|
|
346
346
|
}, this.headers).catch(reject);
|
|
347
347
|
});
|
|
348
348
|
}
|
|
349
|
+
async fetch(url, options = {}) {
|
|
350
|
+
this.assertNotDisposed();
|
|
351
|
+
const fullUrl = url.startsWith('http') ? url : `${this.baseUrl}${url}`;
|
|
352
|
+
const response = await fetch(fullUrl, {
|
|
353
|
+
...options,
|
|
354
|
+
headers: {
|
|
355
|
+
...this.headers,
|
|
356
|
+
...options.headers
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
if (!response.ok) {
|
|
360
|
+
const errorData = await response.json().catch(()=>({}));
|
|
361
|
+
throw new Error(errorData.message || errorData.error || `Fetch failed with status ${response.status}`);
|
|
362
|
+
}
|
|
363
|
+
return response.json();
|
|
364
|
+
}
|
|
349
365
|
async send({ parts: inputParts, ...rest }) {
|
|
350
366
|
this.assertNotDisposed();
|
|
351
367
|
if (this.state.isExecuting) throw new Error('工作流正在执行中');
|
package/dist/ui/code.css
CHANGED
|
@@ -1,51 +1,7 @@
|
|
|
1
|
-
.iframe-code {
|
|
1
|
+
.iframe-code, .html-preview-iframe {
|
|
2
2
|
aspect-ratio: 16 / 9;
|
|
3
3
|
border: none;
|
|
4
4
|
min-width: 50vw;
|
|
5
5
|
display: block;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
-
.html-preview-container {
|
|
9
|
-
background: #fff;
|
|
10
|
-
border: 1px solid #e5e7eb;
|
|
11
|
-
border-radius: 12px;
|
|
12
|
-
width: 100%;
|
|
13
|
-
min-width: min(900px, 100%);
|
|
14
|
-
margin: 20px 0;
|
|
15
|
-
transition: all .3s cubic-bezier(.4, 0, .2, 1);
|
|
16
|
-
overflow: hidden;
|
|
17
|
-
box-shadow: 0 10px 15px -3px #0000001a, 0 4px 6px -4px #0000001a;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
.html-preview-container:hover {
|
|
21
|
-
transform: translateY(-2px);
|
|
22
|
-
box-shadow: 0 20px 25px -5px #0000001a, 0 8px 10px -6px #0000001a;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.html-preview-header {
|
|
26
|
-
color: #374151;
|
|
27
|
-
background: #fdfdfd;
|
|
28
|
-
border-bottom: 1px solid #f3f4f6;
|
|
29
|
-
justify-content: space-between;
|
|
30
|
-
align-items: center;
|
|
31
|
-
padding: 10px 16px;
|
|
32
|
-
font-size: 13px;
|
|
33
|
-
font-weight: 600;
|
|
34
|
-
display: flex;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.html-preview-header .preview-label {
|
|
38
|
-
color: #2563eb;
|
|
39
|
-
align-items: center;
|
|
40
|
-
gap: 8px;
|
|
41
|
-
display: flex;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.html-preview-iframe {
|
|
45
|
-
background: #fff;
|
|
46
|
-
border: none;
|
|
47
|
-
width: 100%;
|
|
48
|
-
min-height: 500px;
|
|
49
|
-
display: block;
|
|
50
|
-
}
|
|
51
|
-
|
package/dist/ui/code.js
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
|
-
import { jsx
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { CodeHighlighter, Mermaid } from "@ant-design/x";
|
|
4
|
-
import { EyeOutlined, LoadingOutlined } from "@ant-design/icons";
|
|
5
4
|
import { jsonrepair } from "jsonrepair";
|
|
6
|
-
import { useWorkflowSession } from "./context.js";
|
|
5
|
+
import { useWorkflowRegistry, useWorkflowSession } from "./context.js";
|
|
7
6
|
import { IframeBridgeHost } from "../iframe-bridge.js";
|
|
8
7
|
import "./code.css";
|
|
8
|
+
import clsx from "clsx";
|
|
9
9
|
const Code = (props)=>{
|
|
10
|
-
|
|
11
|
-
const { className, children, streamStatus, lang: infoString } = props;
|
|
12
|
-
const fullLang = infoString || (null == className ? void 0 : null == (_className_match = className.match(/language-([\w:-]+)/)) ? void 0 : _className_match[1]) || '';
|
|
13
|
-
const lang = fullLang.split(':')[0];
|
|
10
|
+
const { children, streamStatus, lang } = props;
|
|
14
11
|
const session = useWorkflowSession();
|
|
15
12
|
const iframeRef = useRef(null);
|
|
16
13
|
const bridgeRef = useRef(null);
|
|
14
|
+
const { contentWrapper } = useWorkflowRegistry();
|
|
17
15
|
useEffect(()=>{
|
|
18
16
|
if (session && iframeRef.current) {
|
|
19
17
|
if (!bridgeRef.current) bridgeRef.current = new IframeBridgeHost(session);
|
|
@@ -26,13 +24,14 @@ const Code = (props)=>{
|
|
|
26
24
|
}, [
|
|
27
25
|
session
|
|
28
26
|
]);
|
|
29
|
-
|
|
30
|
-
if ('
|
|
27
|
+
let node = null;
|
|
28
|
+
if ('string' != typeof children) node = null;
|
|
29
|
+
else if ('mermaid' === lang) node = /*#__PURE__*/ jsx(Mermaid, {
|
|
31
30
|
children: children
|
|
32
31
|
});
|
|
33
|
-
if ('tmpl' === lang) try {
|
|
32
|
+
else if ('tmpl' === lang) try {
|
|
34
33
|
const json = JSON.parse(jsonrepair(children));
|
|
35
|
-
if ('iframe' === json.type)
|
|
34
|
+
if ('iframe' === json.type) node = /*#__PURE__*/ jsx("iframe", {
|
|
36
35
|
ref: iframeRef,
|
|
37
36
|
src: json.data.src,
|
|
38
37
|
className: "iframe-code",
|
|
@@ -40,44 +39,24 @@ const Code = (props)=>{
|
|
|
40
39
|
allow: "clipboard-read; clipboard-write; camera; microphone"
|
|
41
40
|
});
|
|
42
41
|
} catch {}
|
|
43
|
-
if ('html:run' ===
|
|
42
|
+
else if ('html:run' === lang) {
|
|
44
43
|
const isLoading = 'loading' === streamStatus;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
className: "preview-label",
|
|
53
|
-
children: [
|
|
54
|
-
/*#__PURE__*/ jsx(EyeOutlined, {}),
|
|
55
|
-
/*#__PURE__*/ jsx("span", {
|
|
56
|
-
children: "预览"
|
|
57
|
-
})
|
|
58
|
-
]
|
|
59
|
-
}),
|
|
60
|
-
isLoading && /*#__PURE__*/ jsx(LoadingOutlined, {
|
|
61
|
-
spin: true,
|
|
62
|
-
className: "loading-icon",
|
|
63
|
-
style: {
|
|
64
|
-
color: '#3b82f6'
|
|
65
|
-
}
|
|
66
|
-
})
|
|
67
|
-
]
|
|
68
|
-
}),
|
|
69
|
-
/*#__PURE__*/ jsx("iframe", {
|
|
70
|
-
srcDoc: children,
|
|
71
|
-
className: "html-preview-iframe",
|
|
72
|
-
title: "HTML Preview",
|
|
73
|
-
sandbox: "allow-scripts allow-forms allow-popups"
|
|
74
|
-
})
|
|
75
|
-
]
|
|
44
|
+
node = /*#__PURE__*/ jsx("iframe", {
|
|
45
|
+
srcDoc: children,
|
|
46
|
+
className: clsx('html-preview-iframe', {
|
|
47
|
+
'html-preview-iframe-loading': isLoading
|
|
48
|
+
}),
|
|
49
|
+
title: "HTML Preview",
|
|
50
|
+
sandbox: "allow-scripts allow-forms allow-popups"
|
|
76
51
|
});
|
|
77
|
-
}
|
|
78
|
-
return /*#__PURE__*/ jsx(CodeHighlighter, {
|
|
52
|
+
} else node = /*#__PURE__*/ jsx(CodeHighlighter, {
|
|
79
53
|
lang: lang,
|
|
80
54
|
children: children
|
|
81
55
|
});
|
|
56
|
+
if (null == contentWrapper ? void 0 : contentWrapper.code) {
|
|
57
|
+
const result = contentWrapper.code(props);
|
|
58
|
+
if (false !== result && null != result) return result(node);
|
|
59
|
+
}
|
|
60
|
+
return node;
|
|
82
61
|
};
|
|
83
62
|
export { Code };
|
package/dist/ui/context.d.ts
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
1
2
|
import type { WorkflowSession } from '../session';
|
|
3
|
+
import { type ComponentProps } from '@ant-design/x-markdown';
|
|
2
4
|
export interface WorkflowInfoMapping {
|
|
3
5
|
name: string;
|
|
4
6
|
icon?: React.ReactNode;
|
|
5
7
|
/** 额外的信息,会在点击的时候透传给调用方,方便使用 */
|
|
6
8
|
extra?: Record<string, any>;
|
|
7
9
|
}
|
|
10
|
+
export type ContentWrapperConfig = {
|
|
11
|
+
code?: (props: ComponentProps) => ((children: ReactNode) => ReactNode) | null | undefined | false;
|
|
12
|
+
};
|
|
8
13
|
export declare const SessionContext: import("react").Context<WorkflowSession | null>;
|
|
9
|
-
export declare const WorkflowRegistryContext: import("react").Context<
|
|
14
|
+
export declare const WorkflowRegistryContext: import("react").Context<{
|
|
15
|
+
mapping?: Record<string, WorkflowInfoMapping>;
|
|
16
|
+
contentWrapper?: ContentWrapperConfig;
|
|
17
|
+
}>;
|
|
10
18
|
export declare const MarkdownContext: import("react").Context<{
|
|
11
19
|
isStreaming?: boolean;
|
|
12
20
|
}>;
|
|
13
21
|
export declare const useWorkflowSession: () => WorkflowSession | null;
|
|
14
|
-
export declare const useWorkflowRegistry: () =>
|
|
22
|
+
export declare const useWorkflowRegistry: () => {
|
|
23
|
+
mapping?: Record<string, WorkflowInfoMapping>;
|
|
24
|
+
contentWrapper?: ContentWrapperConfig;
|
|
25
|
+
};
|
|
15
26
|
export declare const useMarkdownContext: () => {
|
|
16
27
|
isStreaming?: boolean;
|
|
17
28
|
};
|
package/dist/ui/context.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createContext, useContext } from "react";
|
|
2
|
-
const SessionContext = createContext(null);
|
|
3
|
-
const WorkflowRegistryContext = createContext({});
|
|
4
|
-
const MarkdownContext = createContext({
|
|
2
|
+
const SessionContext = /*#__PURE__*/ createContext(null);
|
|
3
|
+
const WorkflowRegistryContext = /*#__PURE__*/ createContext({});
|
|
4
|
+
const MarkdownContext = /*#__PURE__*/ createContext({
|
|
5
5
|
isStreaming: false
|
|
6
6
|
});
|
|
7
7
|
const useWorkflowSession = ()=>useContext(SessionContext);
|
package/dist/ui/markdown.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { XMarkdownProps } from '@ant-design/x-markdown';
|
|
2
2
|
import '@ant-design/x-markdown/themes/light.css';
|
|
3
3
|
import './markdown.css';
|
|
4
|
-
import { WorkflowInfoMapping } from './context';
|
|
4
|
+
import { WorkflowInfoMapping, ContentWrapperConfig } from './context';
|
|
5
5
|
import type { WorkflowSession } from '../session';
|
|
6
6
|
export interface MarkdownProps extends XMarkdownProps {
|
|
7
7
|
session?: WorkflowSession;
|
|
@@ -9,8 +9,15 @@ export interface MarkdownProps extends XMarkdownProps {
|
|
|
9
9
|
/**
|
|
10
10
|
* 工作流信息注册表提供者
|
|
11
11
|
*/
|
|
12
|
-
export declare const WorkflowRegistry: ({ children, mapping, }: {
|
|
12
|
+
export declare const WorkflowRegistry: ({ children, mapping, contentWrapper, }: {
|
|
13
13
|
children: React.ReactNode;
|
|
14
|
-
|
|
14
|
+
/**
|
|
15
|
+
* 工作流信息映射,控制工作流跳转 a 标签的样式
|
|
16
|
+
*/
|
|
17
|
+
mapping?: Record<string, WorkflowInfoMapping>;
|
|
18
|
+
/**
|
|
19
|
+
* 内容包装器,对渲染的内容进行包装
|
|
20
|
+
*/
|
|
21
|
+
contentWrapper?: ContentWrapperConfig;
|
|
15
22
|
}) => import("react/jsx-runtime").JSX.Element;
|
|
16
23
|
export declare const Markdown: ({ className, streaming, config, components, session, ...props }: MarkdownProps) => import("react/jsx-runtime").JSX.Element;
|
package/dist/ui/markdown.js
CHANGED
|
@@ -9,16 +9,23 @@ import { Code } from "./code.js";
|
|
|
9
9
|
import { XProvider } from "@ant-design/x";
|
|
10
10
|
import "./markdown.css";
|
|
11
11
|
import { MarkdownContext, SessionContext, WorkflowRegistryContext, useWorkflowRegistry } from "./context.js";
|
|
12
|
-
const WorkflowRegistry = ({ children, mapping })=>{
|
|
12
|
+
const WorkflowRegistry = ({ children, mapping, contentWrapper })=>{
|
|
13
13
|
const parentRegistry = useWorkflowRegistry();
|
|
14
14
|
const mergedRegistry = {
|
|
15
|
-
...parentRegistry,
|
|
15
|
+
...parentRegistry.mapping,
|
|
16
16
|
...mapping
|
|
17
17
|
};
|
|
18
|
+
const mergedContentWrapper = {
|
|
19
|
+
...parentRegistry.contentWrapper,
|
|
20
|
+
...contentWrapper
|
|
21
|
+
};
|
|
18
22
|
return /*#__PURE__*/ jsx(SessionContext.Provider, {
|
|
19
23
|
value: null,
|
|
20
24
|
children: /*#__PURE__*/ jsx(WorkflowRegistryContext.Provider, {
|
|
21
|
-
value:
|
|
25
|
+
value: {
|
|
26
|
+
mapping: mergedRegistry,
|
|
27
|
+
contentWrapper: mergedContentWrapper
|
|
28
|
+
},
|
|
22
29
|
children: children
|
|
23
30
|
})
|
|
24
31
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wtfai",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@ant-design/icons": "^6.1.0",
|
|
35
|
-
"@ant-design/x": "^2.
|
|
36
|
-
"@ant-design/x-markdown": "^2.
|
|
35
|
+
"@ant-design/x": "^2.4.0",
|
|
36
|
+
"@ant-design/x-markdown": "^2.4.0",
|
|
37
37
|
"@microsoft/fetch-event-source": "^2.0.1",
|
|
38
38
|
"clsx": "^2.1.1",
|
|
39
39
|
"compressorjs": "^1.2.1",
|