@alicloud/appflow-chat 0.0.1-beta.1

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.
Files changed (40) hide show
  1. package/README-ZH.md +188 -0
  2. package/README.md +190 -0
  3. package/dist/appflow-chat.cjs.js +1903 -0
  4. package/dist/appflow-chat.esm.js +36965 -0
  5. package/dist/types/index.d.ts +862 -0
  6. package/package.json +87 -0
  7. package/src/components/DocReferences.tsx +64 -0
  8. package/src/components/HumanVerify/CustomParamsRenderer/ArrayField.tsx +394 -0
  9. package/src/components/HumanVerify/CustomParamsRenderer/FieldRenderer.tsx +202 -0
  10. package/src/components/HumanVerify/CustomParamsRenderer/ObjectField.tsx +126 -0
  11. package/src/components/HumanVerify/CustomParamsRenderer/index.tsx +166 -0
  12. package/src/components/HumanVerify/CustomParamsRenderer/types.ts +203 -0
  13. package/src/components/HumanVerify/HistoryCard.tsx +156 -0
  14. package/src/components/HumanVerify/HumanVerify.tsx +184 -0
  15. package/src/components/HumanVerify/index.ts +11 -0
  16. package/src/components/MarkdownRenderer.tsx +195 -0
  17. package/src/components/MessageBubble.tsx +400 -0
  18. package/src/components/RichMessageBubble.tsx +283 -0
  19. package/src/components/WebSearchPanel.tsx +68 -0
  20. package/src/context/RichBubble.tsx +21 -0
  21. package/src/core/BubbleContent.tsx +75 -0
  22. package/src/core/RichBubbleContent.tsx +324 -0
  23. package/src/core/SourceContent.tsx +285 -0
  24. package/src/core/WebSearchContent.tsx +219 -0
  25. package/src/core/index.ts +16 -0
  26. package/src/hooks/usePreSignUpload.ts +36 -0
  27. package/src/index.ts +80 -0
  28. package/src/markdown/components/Chart.tsx +120 -0
  29. package/src/markdown/components/Error.tsx +39 -0
  30. package/src/markdown/components/FileDisplay.tsx +246 -0
  31. package/src/markdown/components/Loading.tsx +41 -0
  32. package/src/markdown/components/SyntaxHighlight.tsx +182 -0
  33. package/src/markdown/index.tsx +250 -0
  34. package/src/markdown/styled.ts +234 -0
  35. package/src/markdown/utils/dataProcessor.ts +89 -0
  36. package/src/services/ChatService.ts +926 -0
  37. package/src/utils/fetchEventSource.ts +65 -0
  38. package/src/utils/loadEcharts.ts +32 -0
  39. package/src/utils/loadPrism.ts +156 -0
  40. package/src/vite-env.d.ts +1 -0
package/package.json ADDED
@@ -0,0 +1,87 @@
1
+ {
2
+ "name": "@alicloud/appflow-chat",
3
+ "version": "0.0.1-beta.1",
4
+ "description": "Appflow-Chat AI聊天机器人组件库,提供聊天服务和UI组件",
5
+ "type": "module",
6
+ "main": "./dist/appflow-chat.cjs.js",
7
+ "module": "./dist/appflow-chat.esm.js",
8
+ "types": "./dist/types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": "./dist/appflow-chat.esm.js",
12
+ "require": "./dist/appflow-chat.cjs.js",
13
+ "types": "./dist/types/index.d.ts"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "dev": "vite",
18
+ "build": "tsc -b && vite build",
19
+ "lint": "eslint .",
20
+ "preview": "vite preview",
21
+ "prepublishOnly": "npm run build",
22
+ "publish:ali": "npm run build && node scripts/publish-ali.js",
23
+ "publish:npm": "npm run build && node scripts/publish-npm.js"
24
+ },
25
+ "dependencies": {
26
+ "axios": "^1.7.9",
27
+ "js-cookie": "^3.0.5",
28
+ "katex": "^0.16.11",
29
+ "lodash-es": "^4.17.21",
30
+ "react-markdown": "^9.0.1",
31
+ "rehype-katex": "^7.0.1",
32
+ "rehype-raw": "^7.0.0",
33
+ "remark-breaks": "^4.0.0",
34
+ "remark-gfm": "^4.0.0",
35
+ "remark-math": "^6.0.0",
36
+ "styled-components": "^6.1.13",
37
+ "@fortawesome/fontawesome-svg-core": "^6.4.2",
38
+ "@fortawesome/free-regular-svg-icons": "^6.4.2",
39
+ "@fortawesome/free-solid-svg-icons": "^6.4.2",
40
+ "@fortawesome/react-fontawesome": "^0.2.0"
41
+ },
42
+ "devDependencies": {
43
+ "@eslint/js": "^9.13.0",
44
+ "@types/js-cookie": "^3.0.6",
45
+ "@types/lodash-es": "^4.17.12",
46
+ "@types/node": "^22.9.0",
47
+ "@types/react": "^18.3.12",
48
+ "@types/react-dom": "^18.3.1",
49
+ "@vitejs/plugin-react": "^4.3.3",
50
+ "antd": "^5.24.8",
51
+ "eslint": "^9.13.0",
52
+ "eslint-plugin-react-hooks": "^5.0.0",
53
+ "eslint-plugin-react-refresh": "^0.4.14",
54
+ "less": "^4.2.0",
55
+ "typescript": "~5.6.2",
56
+ "typescript-eslint": "^8.11.0",
57
+ "vite": "^5.4.10",
58
+ "vite-plugin-dts": "^4.3.0"
59
+ },
60
+ "peerDependencies": {
61
+ "antd": "^5.0.0",
62
+ "react": "^18.0.0",
63
+ "react-dom": "^18.0.0"
64
+ },
65
+ "files": [
66
+ "dist",
67
+ "src",
68
+ "README.md",
69
+ "README-ZH.md"
70
+ ],
71
+ "keywords": [
72
+ "appflow",
73
+ "chat",
74
+ "chatbot",
75
+ "ai",
76
+ "react",
77
+ "components",
78
+ "markdown",
79
+ "streaming"
80
+ ],
81
+ "author": "Appflow Team",
82
+ "license": "MIT",
83
+ "repository": "git@github.com:aliyun/appflow-chatbot.git",
84
+ "overrides": {
85
+ "mdast-util-gfm-autolink-literal": "2.0.0"
86
+ }
87
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * DocReferences - SDK封装的参考资料组件
3
+ * 内部复用 SourceContent 核心组件,提供简化的接口
4
+ */
5
+
6
+ import React from 'react';
7
+ import { SourceContent, SourceItem } from '../core';
8
+
9
+ // 重新导出类型,保持向后兼容
10
+ export type DocReferenceItem = SourceItem;
11
+
12
+ export interface DocReferencesProps {
13
+ /** 参考资料列表 */
14
+ items: DocReferenceItem[];
15
+ /** 渲染状态 */
16
+ status?: 'Running' | 'Success' | 'Error';
17
+ /** 点击参考资料回调 */
18
+ onItemClick?: (item: DocReferenceItem) => void;
19
+ /** 点击网页搜索结果回调 */
20
+ onWebSearchClick?: (items: DocReferenceItem[]) => void;
21
+ /** 自定义类名 */
22
+ className?: string;
23
+ /** 自定义样式 */
24
+ style?: React.CSSProperties;
25
+ }
26
+
27
+ /**
28
+ * DocReferences - 参考资料组件
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * <DocReferences
33
+ * items={[
34
+ * { title: '参考文档1', text: '内容...', index: '1', type: 'rag' },
35
+ * { title: '搜索结果1', url: 'https://...', type: 'web_search' }
36
+ * ]}
37
+ * status="Success"
38
+ * onItemClick={(item) => console.log('点击了', item)}
39
+ * onWebSearchClick={(items) => console.log('网页搜索', items)}
40
+ * />
41
+ * ```
42
+ */
43
+ export const DocReferences: React.FC<DocReferencesProps> = ({
44
+ items,
45
+ status = 'Success',
46
+ onItemClick,
47
+ onWebSearchClick,
48
+ className,
49
+ style,
50
+ }) => {
51
+ return (
52
+ <SourceContent
53
+ className={`appflow-sdk-doc-references ${className || ''}`}
54
+ style={style}
55
+ items={items}
56
+ status={status}
57
+ isPageConfig={false}
58
+ onItemClick={onItemClick}
59
+ onWebSearchClick={onWebSearchClick}
60
+ />
61
+ );
62
+ };
63
+
64
+ export default DocReferences;
@@ -0,0 +1,394 @@
1
+ import React, { useCallback } from 'react';
2
+ import { Button, Input, InputNumber, Switch, Space } from 'antd';
3
+ import { PlusOutlined, MinusOutlined } from '@ant-design/icons';
4
+ import styled from 'styled-components';
5
+ import { ArrayFieldProps, CustomParamSchema } from './types';
6
+
7
+ // 前向声明,避免循环依赖
8
+ const FieldRenderer = React.lazy(() => import('./FieldRenderer'));
9
+
10
+ // ==================== Styled Components ====================
11
+
12
+ const ArrayFieldContainer = styled.div`
13
+ border: 1px solid #dbdbdb;
14
+ border-radius: 10px;
15
+ padding: 24px 16px;
16
+ position: relative;
17
+ margin-top: 20px;
18
+ margin-bottom: 20px;
19
+ background-color: #fff;
20
+ `;
21
+
22
+ const ArrayTitle = styled.div`
23
+ position: absolute;
24
+ top: -12px;
25
+ left: 15px;
26
+ background: #fff;
27
+ padding: 2px 10px;
28
+ white-space: nowrap;
29
+ text-overflow: ellipsis;
30
+ overflow: hidden;
31
+ word-break: break-all;
32
+ max-width: calc(100% - 30px);
33
+ font-size: 14px;
34
+ font-weight: 500;
35
+ color: #1f2329;
36
+ `;
37
+
38
+ const Required = styled.span`
39
+ display: inline-block;
40
+ margin-right: 4px;
41
+ color: #E00000;
42
+ font-size: 14px;
43
+ font-family: SimSun, sans-serif;
44
+ line-height: 1;
45
+ `;
46
+
47
+ const ArrayDescription = styled.div`
48
+ font-size: 12px;
49
+ color: #8f959e;
50
+ margin-bottom: 12px;
51
+ word-break: break-word;
52
+ `;
53
+
54
+ const FieldDescription = styled.div`
55
+ font-size: 12px;
56
+ color: #8f959e;
57
+ margin-top: 4px;
58
+ word-break: break-word;
59
+ `;
60
+
61
+ const FieldError = styled.div`
62
+ font-size: 12px;
63
+ color: #f54a45;
64
+ margin-top: 4px;
65
+ `;
66
+
67
+ const ArrayItems = styled.div``;
68
+
69
+ const ArrayItem = styled.div`
70
+ margin-bottom: 12px;
71
+
72
+ &:last-child {
73
+ margin-bottom: 0;
74
+ }
75
+ `;
76
+
77
+ const ArrayItemContent = styled.div`
78
+ width: 100%;
79
+ `;
80
+
81
+ const ArrayItemRow = styled.div`
82
+ display: flex;
83
+ align-items: flex-start;
84
+ gap: 8px;
85
+ margin-bottom: 8px;
86
+
87
+ &:last-child {
88
+ margin-bottom: 0;
89
+ }
90
+ `;
91
+
92
+ const ArrayItemInput = styled.div`
93
+ flex: 1;
94
+ `;
95
+
96
+ const ArrayItemActions = styled.div`
97
+ flex-shrink: 0;
98
+ padding-top: 4px;
99
+ `;
100
+
101
+ const ArrayActions = styled.div`
102
+ margin-top: 12px;
103
+ display: flex;
104
+ gap: 8px;
105
+ `;
106
+
107
+ /**
108
+ * 获取数组项的默认值
109
+ */
110
+ const getDefaultValue = (schema: CustomParamSchema): any => {
111
+ switch (schema.Type) {
112
+ case 'string':
113
+ return '';
114
+ case 'number':
115
+ return undefined;
116
+ case 'boolean':
117
+ return false;
118
+ case 'array':
119
+ // 所有数组类型都自动初始化一条数据
120
+ if (schema.Items) {
121
+ return [getDefaultValue(schema.Items)];
122
+ }
123
+ return [];
124
+ case 'object':
125
+ if (schema.Properties) {
126
+ const obj: Record<string, any> = {};
127
+ Object.keys(schema.Properties).forEach((key) => {
128
+ obj[key] = getDefaultValue(schema.Properties![key]);
129
+ });
130
+ return obj;
131
+ }
132
+ return {};
133
+ default:
134
+ return undefined;
135
+ }
136
+ };
137
+
138
+ /**
139
+ * 数组类型字段渲染器
140
+ * 不展示 Items 层级,直接渲染数组项内容
141
+ */
142
+ export const ArrayField: React.FC<ArrayFieldProps> = ({
143
+ name,
144
+ schema,
145
+ value = [],
146
+ onChange,
147
+ required = false,
148
+ disabled = false,
149
+ level = 0,
150
+ errors = {},
151
+ fieldPath = '',
152
+ }) => {
153
+ const { Title, Description, Items } = schema;
154
+ const displayTitle = Title || name;
155
+
156
+ // 计算当前数组的完整路径
157
+ const currentPath = fieldPath ? `${fieldPath}.${name}` : name;
158
+
159
+ // 添加数组项
160
+ const handleAdd = useCallback(() => {
161
+ if (!Items) return;
162
+ const newItem = getDefaultValue(Items);
163
+ onChange?.([...value, newItem]);
164
+ }, [Items, value, onChange]);
165
+
166
+ // 删除数组项
167
+ const handleRemove = useCallback(
168
+ (index: number) => {
169
+ const newValue = [...value];
170
+ newValue.splice(index, 1);
171
+ onChange?.(newValue);
172
+ },
173
+ [value, onChange]
174
+ );
175
+
176
+ // 更新数组项
177
+ const handleItemChange = useCallback(
178
+ (index: number, itemValue: any) => {
179
+ const newValue = [...value];
180
+ newValue[index] = itemValue;
181
+ onChange?.(newValue);
182
+ },
183
+ [value, onChange]
184
+ );
185
+
186
+ // 更新对象类型数组项的属性
187
+ const handleObjectPropertyChange = useCallback(
188
+ (index: number, propertyName: string, propertyValue: any) => {
189
+ const newValue = [...value];
190
+ newValue[index] = {
191
+ ...newValue[index],
192
+ [propertyName]: propertyValue,
193
+ };
194
+ onChange?.(newValue);
195
+ },
196
+ [value, onChange]
197
+ );
198
+
199
+ // 判断数组项类型(必须在 early return 之前)
200
+ const isObjectItems = Items?.Type === 'object' && Items?.Properties;
201
+ const isArrayItems = Items?.Type === 'array';
202
+
203
+ if (!Items) {
204
+ return null;
205
+ }
206
+
207
+ const itemRequired = Items.Required || [];
208
+
209
+ // 渲染基础类型的数组项输入框
210
+ const renderPrimitiveInput = (item: any, index: number) => {
211
+ let inputElement;
212
+
213
+ // 计算当前数组项的路径
214
+ const itemPath = `${currentPath}[${index}]`;
215
+ // 获取当前数组项的错误信息
216
+ const itemError = errors[itemPath];
217
+
218
+ switch (Items.Type) {
219
+ case 'string':
220
+ inputElement = (
221
+ <Input
222
+ value={item}
223
+ onChange={(e) => handleItemChange(index, e.target.value)}
224
+ disabled={disabled}
225
+ />
226
+ );
227
+ break;
228
+ case 'number':
229
+ inputElement = (
230
+ <InputNumber
231
+ value={item}
232
+ onChange={(val) => handleItemChange(index, val)}
233
+ disabled={disabled}
234
+ style={{ width: '100%' }}
235
+ />
236
+ );
237
+ break;
238
+ case 'boolean':
239
+ inputElement = (
240
+ <Switch
241
+ checked={item}
242
+ onChange={(val) => handleItemChange(index, val)}
243
+ disabled={disabled}
244
+ />
245
+ );
246
+ break;
247
+ default:
248
+ inputElement = (
249
+ <Input
250
+ value={item}
251
+ onChange={(e) => handleItemChange(index, e.target.value)}
252
+ disabled={disabled}
253
+ />
254
+ );
255
+ }
256
+
257
+ return (
258
+ <div>
259
+ {inputElement}
260
+ {itemError && (
261
+ <FieldError>{itemError}</FieldError>
262
+ )}
263
+ {Items.Description && !itemError && (
264
+ <FieldDescription>{Items.Description}</FieldDescription>
265
+ )}
266
+ </div>
267
+ );
268
+ };
269
+
270
+ // 渲染对象类型的数组项内容(直接渲染 Properties,不展示 Items 层级)
271
+ const renderObjectItemContent = (item: any, index: number) => {
272
+ if (!Items.Properties) return null;
273
+
274
+ return Object.entries(Items.Properties).map(([propertyName, propertySchema]) => {
275
+ const isRequired = itemRequired.includes(propertyName);
276
+ return (
277
+ <React.Suspense key={propertyName} fallback={<div>加载中...</div>}>
278
+ <FieldRenderer
279
+ name={propertyName}
280
+ schema={propertySchema}
281
+ value={item?.[propertyName]}
282
+ onChange={(newValue) => handleObjectPropertyChange(index, propertyName, newValue)}
283
+ required={isRequired}
284
+ disabled={disabled}
285
+ level={level + 1}
286
+ errors={errors}
287
+ fieldPath={`${currentPath}[${index}]`}
288
+ />
289
+ </React.Suspense>
290
+ );
291
+ });
292
+ };
293
+
294
+ // 渲染嵌套数组类型的数组项
295
+ const renderArrayItemContent = (item: any, index: number) => {
296
+ return (
297
+ <React.Suspense fallback={<div>加载中...</div>}>
298
+ <FieldRenderer
299
+ name={`${name}[${index}]`}
300
+ schema={Items}
301
+ value={item}
302
+ onChange={(newValue) => handleItemChange(index, newValue)}
303
+ disabled={disabled}
304
+ level={level + 1}
305
+ errors={errors}
306
+ fieldPath={currentPath}
307
+ />
308
+ </React.Suspense>
309
+ );
310
+ };
311
+
312
+ return (
313
+ <ArrayFieldContainer>
314
+ <ArrayTitle>
315
+ {required && <Required>*</Required>}
316
+ <span>{displayTitle}</span>
317
+ </ArrayTitle>
318
+
319
+ {Description && (
320
+ <ArrayDescription>{Description}</ArrayDescription>
321
+ )}
322
+
323
+ <ArrayItems>
324
+ {value.map((item, index) => {
325
+ // 渲染数组项内容
326
+ const renderItemContent = () => {
327
+ if (isObjectItems) {
328
+ // 对象类型:直接渲染 Properties 内容
329
+ return (
330
+ <ArrayItemContent>
331
+ {renderObjectItemContent(item, index)}
332
+ </ArrayItemContent>
333
+ );
334
+ }
335
+ if (isArrayItems) {
336
+ // 嵌套数组类型
337
+ return (
338
+ <ArrayItemContent>
339
+ {renderArrayItemContent(item, index)}
340
+ </ArrayItemContent>
341
+ );
342
+ }
343
+ // 基础类型:渲染输入框和删除按钮
344
+ return (
345
+ <ArrayItemRow>
346
+ <ArrayItemInput>
347
+ {renderPrimitiveInput(item, index)}
348
+ </ArrayItemInput>
349
+ <ArrayItemActions>
350
+ <Button
351
+ type="text"
352
+ danger
353
+ icon={<MinusOutlined />}
354
+ onClick={() => handleRemove(index)}
355
+ disabled={disabled}
356
+ size="small"
357
+ />
358
+ </ArrayItemActions>
359
+ </ArrayItemRow>
360
+ );
361
+ };
362
+
363
+ return (
364
+ <ArrayItem key={index}>
365
+ {renderItemContent()}
366
+ </ArrayItem>
367
+ );
368
+ })}
369
+ </ArrayItems>
370
+
371
+ <ArrayActions>
372
+ <Space>
373
+ <Button
374
+ onClick={handleAdd}
375
+ disabled={disabled}
376
+ shape="circle"
377
+ icon={<PlusOutlined />}
378
+ />
379
+ {(isObjectItems || isArrayItems) && value.length > 0 && (
380
+ <Button
381
+ onClick={() => handleRemove(value.length - 1)}
382
+ shape="circle"
383
+ icon={<MinusOutlined />}
384
+ danger
385
+ disabled={disabled}
386
+ />
387
+ )}
388
+ </Space>
389
+ </ArrayActions>
390
+ </ArrayFieldContainer>
391
+ );
392
+ };
393
+
394
+ export default ArrayField;