@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
@@ -0,0 +1,120 @@
1
+
2
+ import React, { useEffect, useRef, useState } from 'react';
3
+ import { useRichBubbleContext } from '@/context/RichBubble';
4
+ import loadEchartsScript from '@/utils/loadEcharts';
5
+
6
+ interface Iprops {
7
+ options: any;
8
+ }
9
+
10
+ export const Chart: React.FC<Iprops> = ({
11
+ options,
12
+ }) => {
13
+ const chartContainerRef = useRef(null);
14
+ const chartInstanceRef = useRef(null);
15
+ const [isEchartsLoaded, setIsEchartsLoaded] = useState(false);
16
+
17
+ const { activeKey } = useRichBubbleContext();
18
+
19
+ // 加载echarts
20
+ useEffect(() => {
21
+ const initEcharts = async () => {
22
+ try {
23
+ await loadEchartsScript();
24
+ setIsEchartsLoaded(true);
25
+ } catch (error) {
26
+ console.error('echarts加载失败:', error);
27
+ setIsEchartsLoaded(false);
28
+ }
29
+ };
30
+
31
+ initEcharts();
32
+ }, []);
33
+
34
+ useEffect(() => {
35
+ // 确保echarts已加载且DOM元素存在
36
+ if (!isEchartsLoaded || !chartContainerRef.current || !options) return;
37
+
38
+ const echarts = (window as any).echarts;
39
+ if (!echarts) {
40
+ console.error('echarts未正确加载');
41
+ return;
42
+ }
43
+
44
+ // 清理之前的实例
45
+ if (chartInstanceRef.current) {
46
+ // @ts-ignore
47
+ chartInstanceRef.current.dispose();
48
+ }
49
+
50
+ // 创建新的图表实例
51
+ const chartInstance = echarts.init(chartContainerRef.current);
52
+ // @ts-ignore
53
+ chartInstanceRef.current = chartInstance;
54
+
55
+ // 设置图表选项
56
+ chartInstance.setOption(options);
57
+
58
+ // 处理窗口大小变化
59
+ const handleResize = () => {
60
+ chartInstance.resize();
61
+ };
62
+ window.addEventListener('resize', handleResize);
63
+
64
+ // 清理函数
65
+ return () => {
66
+ window.removeEventListener('resize', handleResize);
67
+ chartInstance.dispose();
68
+ chartInstanceRef.current = null;
69
+ };
70
+ }, [options, isEchartsLoaded]);
71
+
72
+ // 在步骤消息折叠框改变时更新图表
73
+ useEffect(() => {
74
+ if (chartInstanceRef.current && activeKey && isEchartsLoaded) {
75
+ // 确保折叠动画完成后再resize
76
+ const timer = setTimeout(() => {
77
+ // @ts-ignore
78
+ chartInstanceRef.current?.resize();
79
+ }, 50);
80
+
81
+ return () => clearTimeout(timer);
82
+ }
83
+ }, [activeKey, isEchartsLoaded]);
84
+
85
+ // 等待echarts加载完成
86
+ if (!isEchartsLoaded) {
87
+ return (
88
+ <div
89
+ style={{
90
+ width: '100%',
91
+ height: '400px',
92
+ marginTop: '20px',
93
+ marginBottom: '20px',
94
+ display: 'flex',
95
+ alignItems: 'center',
96
+ justifyContent: 'center',
97
+ border: '1px dashed #d9d9d9',
98
+ borderRadius: '6px',
99
+ color: '#666'
100
+ }}
101
+ >
102
+ 图表加载中...
103
+ </div>
104
+ );
105
+ }
106
+
107
+ return (
108
+ <div
109
+ ref={chartContainerRef}
110
+ style={{
111
+ width: '100%',
112
+ height: '400px',
113
+ marginTop: '20px',
114
+ marginBottom: '20px'
115
+ }}
116
+ />
117
+ );
118
+ };
119
+
120
+ export default Chart;
@@ -0,0 +1,39 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ // ==================== Styled Components ====================
5
+
6
+ const ChartErrorContainer = styled.div`
7
+ margin: 10px 0;
8
+ padding: 10px;
9
+ border-radius: 4px;
10
+ text-align: center;
11
+ background: #ffeeee;
12
+ color: rgb(200, 0, 0);
13
+ border: 1px solid #ffcccc;
14
+
15
+ span {
16
+ font-size: 14px;
17
+ line-height: 1.5;
18
+ }
19
+
20
+ &:hover {
21
+ background: #f5f5f5;
22
+ }
23
+ `;
24
+
25
+ // ==================== Component ====================
26
+
27
+ interface ChartErrorProps {
28
+ text?: string;
29
+ }
30
+
31
+ const ChartError: React.FC<ChartErrorProps> = ({ text = '' }) => {
32
+ return (
33
+ <ChartErrorContainer>
34
+ <span>{text}</span>
35
+ </ChartErrorContainer>
36
+ );
37
+ };
38
+
39
+ export default ChartError;
@@ -0,0 +1,246 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
4
+ import {
5
+ faFilePdf,
6
+ faFileImage,
7
+ faFileExcel,
8
+ faFilePowerpoint,
9
+ faFileWord,
10
+ faFileArchive,
11
+ faFileCode,
12
+ faFileVideo,
13
+ faFileAudio,
14
+ faFile,
15
+ faFileLines
16
+ } from '@fortawesome/free-solid-svg-icons';
17
+
18
+ // Styled Components
19
+ const FileDisplayContainer = styled.div`
20
+ display: flex;
21
+ align-items: center;
22
+ background-color: #f8f9fa;
23
+ border-radius: 6px;
24
+ border: 1px solid #e9ecef;
25
+ transition: all 0.3s ease;
26
+ padding: 8px 12px;
27
+
28
+ @media (max-width: 500px) {
29
+ flex-direction: column;
30
+ text-align: center;
31
+ }
32
+ `;
33
+
34
+ const FileIconWrapper = styled.div<{ $color: string }>`
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ width: 48px;
39
+ height: 48px;
40
+ min-width: 48px;
41
+ color: ${props => props.$color};
42
+ background-color: #e8f4fc;
43
+ border-radius: 8px;
44
+ margin-right: 12px;
45
+
46
+ @media (max-width: 500px) {
47
+ margin-right: 0;
48
+ margin-bottom: 10px;
49
+ }
50
+ `;
51
+
52
+ const FileInfo = styled.div`
53
+ flex: 1;
54
+ `;
55
+
56
+ const FileName = styled.div`
57
+ font-weight: bold;
58
+ font-size: 12px;
59
+ margin-bottom: 4px;
60
+ color: #2d3436;
61
+ max-width: 200px;
62
+ white-space: nowrap;
63
+ overflow: hidden;
64
+ text-overflow: ellipsis;
65
+ `;
66
+
67
+ const FileDetails = styled.div`
68
+ display: flex;
69
+ font-size: 14px;
70
+ color: #636e72;
71
+
72
+ @media (max-width: 500px) {
73
+ justify-content: center;
74
+ }
75
+ `;
76
+
77
+ const FileSize = styled.span`
78
+ margin-right: 12px;
79
+ `;
80
+
81
+ const FileType = styled.span`
82
+ background-color: rgba(0, 0, 0, 0.08);
83
+ padding: 2px 6px;
84
+ border-radius: 4px;
85
+ font-size: 12px;
86
+ font-weight: 500;
87
+ `;
88
+
89
+ interface Iprops {
90
+ fileInfo: any;
91
+ }
92
+
93
+ export const FileDisplay: React.FC<Iprops> = ({
94
+ fileInfo
95
+ }) => {
96
+ const { name, size, type = 'doc' } = fileInfo;
97
+
98
+ const getFileIcon = () => {
99
+ switch (type.toLowerCase()) {
100
+ case 'pdf':
101
+ return faFilePdf;
102
+ case 'image':
103
+ case 'img':
104
+ case 'jpg':
105
+ case 'jpeg':
106
+ case 'png':
107
+ case 'gif':
108
+ return faFileImage;
109
+ case 'excel':
110
+ case 'xlsx':
111
+ case 'xls':
112
+ case 'csv':
113
+ return faFileExcel;
114
+ case 'powerpoint':
115
+ case 'ppt':
116
+ case 'pptx':
117
+ return faFilePowerpoint;
118
+ case 'word':
119
+ case 'doc':
120
+ case 'docx':
121
+ return faFileWord;
122
+ case 'archive':
123
+ case 'zip':
124
+ case 'rar':
125
+ case 'tar':
126
+ case '7z':
127
+ return faFileArchive;
128
+ case 'code':
129
+ case 'js':
130
+ case 'jsx':
131
+ case 'ts':
132
+ case 'tsx':
133
+ case 'html':
134
+ case 'css':
135
+ case 'json':
136
+ case 'py':
137
+ case 'java':
138
+ case 'cpp':
139
+ return faFileCode;
140
+ case 'video':
141
+ case 'mp4':
142
+ case 'avi':
143
+ case 'mov':
144
+ case 'webm':
145
+ return faFileVideo;
146
+ case 'audio':
147
+ case 'mp3':
148
+ case 'wav':
149
+ case 'ogg':
150
+ return faFileAudio;
151
+ case 'md':
152
+ case 'markdown':
153
+ case 'txt':
154
+ case 'text':
155
+ return faFileLines;
156
+ default:
157
+ return faFile;
158
+ }
159
+ };
160
+
161
+ const getFileTypeColor = () => {
162
+ switch (type.toLowerCase()) {
163
+ case 'pdf':
164
+ return '#e74c3c'; // 红色
165
+ case 'image':
166
+ case 'img':
167
+ case 'jpg':
168
+ case 'jpeg':
169
+ case 'png':
170
+ case 'gif':
171
+ return '#3498db'; // 蓝色
172
+ case 'excel':
173
+ case 'xlsx':
174
+ case 'xls':
175
+ case 'csv':
176
+ return '#27ae60'; // 绿色
177
+ case 'powerpoint':
178
+ case 'ppt':
179
+ case 'pptx':
180
+ return '#e67e22'; // 橙色
181
+ case 'word':
182
+ case 'doc':
183
+ case 'docx':
184
+ return '#2980b9'; // 深蓝色
185
+ case 'archive':
186
+ case 'zip':
187
+ case 'rar':
188
+ case 'tar':
189
+ case '7z':
190
+ return '#8e44ad'; // 紫色
191
+ case 'code':
192
+ case 'js':
193
+ case 'jsx':
194
+ case 'ts':
195
+ case 'tsx':
196
+ case 'html':
197
+ case 'css':
198
+ case 'json':
199
+ case 'py':
200
+ case 'java':
201
+ case 'cpp':
202
+ return '#f1c40f'; // 黄色
203
+ case 'video':
204
+ case 'mp4':
205
+ case 'avi':
206
+ case 'mov':
207
+ case 'webm':
208
+ return '#e84393'; // 粉色
209
+ case 'audio':
210
+ case 'mp3':
211
+ case 'wav':
212
+ case 'ogg':
213
+ return '#00cec9'; // 青色
214
+ case 'md':
215
+ case 'markdown':
216
+ case 'txt':
217
+ case 'text':
218
+ return '#636e72'; // 深灰色
219
+ default:
220
+ return '#7f8c8d'; // 灰色
221
+ }
222
+ };
223
+
224
+ const formatSize = (bytes: number) => {
225
+ if (bytes < 1024) return bytes + ' B';
226
+ if (bytes < 1048576) return (bytes/1024).toFixed(1) + ' KB';
227
+ return (bytes/1048576).toFixed(1) + ' MB';
228
+ };
229
+
230
+ return (
231
+ <FileDisplayContainer>
232
+ <FileIconWrapper $color={getFileTypeColor()}>
233
+ <FontAwesomeIcon icon={getFileIcon()} style={{ fontSize: '24px' }} />
234
+ </FileIconWrapper>
235
+ <FileInfo>
236
+ <FileName>{name}</FileName>
237
+ <FileDetails>
238
+ <FileSize>{formatSize(size)}</FileSize>
239
+ <FileType>{type.toUpperCase()}</FileType>
240
+ </FileDetails>
241
+ </FileInfo>
242
+ </FileDisplayContainer>
243
+ );
244
+ };
245
+
246
+ export default FileDisplay;
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import styled from 'styled-components';
3
+
4
+ // ==================== Styled Components ====================
5
+
6
+ const ChartLoadingContainer = styled.div`
7
+ padding: 10px;
8
+ text-align: center;
9
+ background: #f9f9f9;
10
+ border-radius: 4px;
11
+ color: #666;
12
+ min-height: 100px;
13
+ display: flex;
14
+ align-items: center;
15
+ justify-content: center;
16
+
17
+ span {
18
+ font-size: 14px;
19
+ line-height: 1.5;
20
+ }
21
+
22
+ &:hover {
23
+ background: #f5f5f5;
24
+ }
25
+ `;
26
+
27
+ // ==================== Component ====================
28
+
29
+ interface ChartLoadingProps {
30
+ text?: string;
31
+ }
32
+
33
+ const ChartLoading: React.FC<ChartLoadingProps> = ({ text = '图表加载中...' }) => {
34
+ return (
35
+ <ChartLoadingContainer>
36
+ <span>{text}</span>
37
+ </ChartLoadingContainer>
38
+ );
39
+ };
40
+
41
+ export default ChartLoading;
@@ -0,0 +1,182 @@
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { loadPrism, loadPrismLanguage } from '@/utils/loadPrism';
3
+
4
+ interface ASyntaxHighLightProps {
5
+ language?: string;
6
+ children: any;
7
+ style?: React.CSSProperties;
8
+ }
9
+
10
+ const CopyIcon = () => (
11
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
12
+ <path d="M16 1H4C2.9 1 2 1.9 2 3V17H4V3H16V1ZM19 5H8C6.9 5 6 5.9 6 7V21C6 22.1 6.9 23 8 23H19C20.1 23 21 22.1 21 21V7C21 5.9 20.1 5 19 5ZM19 21H8V7H19V21Z" />
13
+ </svg>
14
+ );
15
+
16
+ const CheckIcon = () => (
17
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor">
18
+ <path d="M9 16.17L4.83 12L3.41 13.41L9 19L21 7L19.59 5.59L9 16.17Z" />
19
+ </svg>
20
+ );
21
+
22
+ export const ASyntaxHighLight: React.FC<ASyntaxHighLightProps> = ({
23
+ language = 'javascript',
24
+ children,
25
+ style
26
+ }) => {
27
+ const [copied, setCopied] = useState(false);
28
+ const [highlightedCode, setHighlightedCode] = useState<string>('');
29
+ const [isLoading, setIsLoading] = useState(true);
30
+ const codeRef = useRef<HTMLElement>(null);
31
+
32
+ // 获取代码文本
33
+ const codeText = typeof children === 'string'
34
+ ? children
35
+ : Array.isArray(children)
36
+ ? children.join('')
37
+ : String(children || '');
38
+
39
+ // 加载 Prism 并高亮代码
40
+ useEffect(() => {
41
+ let isMounted = true;
42
+
43
+ const highlight = async () => {
44
+ try {
45
+ setIsLoading(true);
46
+ const Prism = await loadPrism();
47
+ await loadPrismLanguage(language);
48
+
49
+ if (!isMounted) return;
50
+
51
+ // 获取语法定义
52
+ const grammar = Prism.languages[language] || Prism.languages.javascript;
53
+ const highlighted = Prism.highlight(codeText.replace(/\n$/, ''), grammar, language);
54
+
55
+ setHighlightedCode(highlighted);
56
+ setIsLoading(false);
57
+ } catch (error) {
58
+ console.error('Failed to highlight code:', error);
59
+ if (isMounted) {
60
+ setHighlightedCode(escapeHtml(codeText));
61
+ setIsLoading(false);
62
+ }
63
+ }
64
+ };
65
+
66
+ highlight();
67
+
68
+ return () => {
69
+ isMounted = false;
70
+ };
71
+ }, [codeText, language]);
72
+
73
+ // HTML 转义函数
74
+ const escapeHtml = (text: string) => {
75
+ const div = document.createElement('div');
76
+ div.textContent = text;
77
+ return div.innerHTML;
78
+ };
79
+
80
+ const handleCopy = async () => {
81
+ try {
82
+ await navigator.clipboard.writeText(codeText);
83
+ setCopied(true);
84
+ setTimeout(() => setCopied(false), 2000);
85
+ } catch (err) {
86
+ console.error('Failed to copy:', err);
87
+ }
88
+ };
89
+
90
+ // 计算行号
91
+ const lines = codeText.split('\n');
92
+ const lineCount = lines[lines.length - 1] === '' ? lines.length - 1 : lines.length;
93
+
94
+ return (
95
+ <div style={{ position: 'relative', ...style }}>
96
+ <pre
97
+ className="line-numbers"
98
+ style={{
99
+ margin: 0,
100
+ padding: '1em',
101
+ paddingLeft: '3.8em',
102
+ overflow: 'auto',
103
+ background: '#e3eaf2',
104
+ borderRadius: '4px',
105
+ fontSize: '13px',
106
+ lineHeight: '1.5',
107
+ fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
108
+ }}
109
+ >
110
+ {/* 行号 */}
111
+ <span
112
+ className="line-numbers-rows"
113
+ style={{
114
+ position: 'absolute',
115
+ left: 0,
116
+ top: '1em',
117
+ width: '3em',
118
+ borderRight: '1px solid #8da1b9',
119
+ textAlign: 'right',
120
+ paddingRight: '0.8em',
121
+ userSelect: 'none',
122
+ color: '#8da1b9',
123
+ fontSize: '13px',
124
+ lineHeight: '1.5',
125
+ }}
126
+ >
127
+ {Array.from({ length: lineCount }, (_, i) => (
128
+ <span key={i} style={{ display: 'block' }}>{i + 1}</span>
129
+ ))}
130
+ </span>
131
+
132
+ <code
133
+ ref={codeRef}
134
+ className={`language-${language}`}
135
+ style={{
136
+ whiteSpace: 'pre-wrap',
137
+ wordBreak: 'break-word',
138
+ }}
139
+ dangerouslySetInnerHTML={
140
+ isLoading
141
+ ? { __html: escapeHtml(codeText) }
142
+ : { __html: highlightedCode }
143
+ }
144
+ />
145
+ </pre>
146
+
147
+ {/* 复制按钮 */}
148
+ <button
149
+ onClick={handleCopy}
150
+ style={{
151
+ position: 'absolute',
152
+ right: '8px',
153
+ top: '8px',
154
+ padding: '4px 8px',
155
+ background: 'rgba(255,255,255,0.8)',
156
+ border: '1px solid #ccc',
157
+ borderRadius: '4px',
158
+ cursor: 'pointer',
159
+ opacity: 0.7,
160
+ transition: 'all 0.2s',
161
+ display: 'flex',
162
+ alignItems: 'center',
163
+ justifyContent: 'center',
164
+ fontSize: '12px',
165
+ color: '#333',
166
+ }}
167
+ onMouseEnter={e => {
168
+ e.currentTarget.style.opacity = '1';
169
+ e.currentTarget.style.background = 'rgba(255,255,255,1)';
170
+ }}
171
+ onMouseLeave={e => {
172
+ e.currentTarget.style.opacity = '0.7';
173
+ e.currentTarget.style.background = 'rgba(255,255,255,0.8)';
174
+ }}
175
+ title={copied ? 'Copied!' : 'Copy code'}
176
+ >
177
+ {copied ? <CheckIcon /> : <CopyIcon />}
178
+ <span style={{ marginLeft: '4px' }}>{copied ? 'Copied' : 'Copy'}</span>
179
+ </button>
180
+ </div>
181
+ );
182
+ };