@alicloud/appflow-chat 0.0.4-alpha.2 → 0.0.4-alpha.4
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/dist/appflow-chat.cjs.js +253 -159
- package/dist/appflow-chat.esm.js +7881 -7602
- package/dist/types/index.d.ts +17 -6
- package/package.json +1 -1
- package/src/App.tsx +182 -0
- package/src/components/ChatSender.tsx +253 -92
- package/src/components/MessageAttachments.tsx +227 -0
- package/src/components/MessageBubble.tsx +19 -5
- package/src/main.tsx +9 -0
- package/src/services/ChatService.ts +1 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MessageAttachments - 消息附件展示组件
|
|
3
|
+
* 用于在消息气泡中展示上传的图片和文件
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React from 'react';
|
|
7
|
+
import styled from 'styled-components';
|
|
8
|
+
import { Image } from 'antd';
|
|
9
|
+
import {
|
|
10
|
+
FileWordOutlined,
|
|
11
|
+
FilePdfOutlined,
|
|
12
|
+
FileExcelOutlined,
|
|
13
|
+
FileTextOutlined,
|
|
14
|
+
FileOutlined,
|
|
15
|
+
} from '@ant-design/icons';
|
|
16
|
+
|
|
17
|
+
// ==================== 类型定义 ====================
|
|
18
|
+
|
|
19
|
+
export interface MessageAttachmentsProps {
|
|
20
|
+
/** 消息角色,影响卡片配色 */
|
|
21
|
+
role?: 'user' | 'bot';
|
|
22
|
+
/** 图片URL列表 */
|
|
23
|
+
images?: string[];
|
|
24
|
+
/** 文件列表 */
|
|
25
|
+
files?: { name: string; url: string }[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ==================== 样式组件 ====================
|
|
29
|
+
|
|
30
|
+
const AttachmentsArea = styled.div`
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
gap: 8px;
|
|
34
|
+
margin-top: 4px;
|
|
35
|
+
`;
|
|
36
|
+
|
|
37
|
+
const ImageCard = styled.div<{ $role: 'user' | 'bot' }>`
|
|
38
|
+
background: ${props => props.$role === 'user' ? '#eef0ff' : '#e8f4fd'};
|
|
39
|
+
border-radius: 8px;
|
|
40
|
+
padding: 8px;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
gap: 6px;
|
|
44
|
+
max-width: 180px;
|
|
45
|
+
color: #333;
|
|
46
|
+
|
|
47
|
+
.image-preview {
|
|
48
|
+
border-radius: 6px;
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
cursor: pointer;
|
|
51
|
+
|
|
52
|
+
.ant-image {
|
|
53
|
+
display: block;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
img {
|
|
57
|
+
max-width: 100%;
|
|
58
|
+
height: auto;
|
|
59
|
+
display: block;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
`;
|
|
63
|
+
|
|
64
|
+
const ImagesRow = styled.div`
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-wrap: wrap;
|
|
67
|
+
gap: 8px;
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const FileCard = styled.a<{ $role: 'user' | 'bot' }>`
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 10px;
|
|
74
|
+
background: ${props => props.$role === 'user' ? '#eef0ff' : '#e8f4fd'};
|
|
75
|
+
border-radius: 8px;
|
|
76
|
+
padding: 10px 12px;
|
|
77
|
+
text-decoration: none;
|
|
78
|
+
color: #333;
|
|
79
|
+
transition: background 0.2s;
|
|
80
|
+
max-width: 100%;
|
|
81
|
+
box-sizing: border-box;
|
|
82
|
+
|
|
83
|
+
&:hover {
|
|
84
|
+
background: ${props => props.$role === 'user' ? '#e2e5ff' : '#d6ecf8'};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.file-icon {
|
|
88
|
+
font-size: 28px;
|
|
89
|
+
flex-shrink: 0;
|
|
90
|
+
color: #1677ff;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.file-info {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: 2px;
|
|
97
|
+
min-width: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.file-name {
|
|
101
|
+
font-size: 13px;
|
|
102
|
+
font-weight: 500;
|
|
103
|
+
overflow: hidden;
|
|
104
|
+
text-overflow: ellipsis;
|
|
105
|
+
white-space: nowrap;
|
|
106
|
+
color: #333;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.file-meta {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: center;
|
|
112
|
+
gap: 6px;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.file-type {
|
|
116
|
+
font-size: 11px;
|
|
117
|
+
padding: 1px 4px;
|
|
118
|
+
border-radius: 3px;
|
|
119
|
+
background: rgba(0, 0, 0, 0.06);
|
|
120
|
+
color: #666;
|
|
121
|
+
text-transform: uppercase;
|
|
122
|
+
font-weight: 500;
|
|
123
|
+
}
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
// ==================== 工具函数 ====================
|
|
127
|
+
|
|
128
|
+
/** 根据文件名获取文件扩展名 */
|
|
129
|
+
function getFileExtension(fileName: string): string {
|
|
130
|
+
const parts = fileName.split('.');
|
|
131
|
+
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** 根据文件扩展名获取对应的图标组件 */
|
|
135
|
+
function getFileIcon(ext: string): React.ReactNode {
|
|
136
|
+
switch (ext) {
|
|
137
|
+
case 'doc':
|
|
138
|
+
case 'docx':
|
|
139
|
+
return <FileWordOutlined />;
|
|
140
|
+
case 'pdf':
|
|
141
|
+
return <FilePdfOutlined />;
|
|
142
|
+
case 'xls':
|
|
143
|
+
case 'xlsx':
|
|
144
|
+
case 'csv':
|
|
145
|
+
return <FileExcelOutlined />;
|
|
146
|
+
case 'txt':
|
|
147
|
+
case 'md':
|
|
148
|
+
case 'json':
|
|
149
|
+
return <FileTextOutlined />;
|
|
150
|
+
default:
|
|
151
|
+
return <FileOutlined />;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ==================== 组件实现 ====================
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* MessageAttachments - 消息附件展示组件
|
|
159
|
+
*
|
|
160
|
+
* 在消息气泡中展示上传的图片(缩略图 + 点击预览)和文件(图标 + 文件名 + 类型标签 + 点击下载)。
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```tsx
|
|
164
|
+
* <MessageAttachments
|
|
165
|
+
* role="user"
|
|
166
|
+
* images={['https://example.com/image.png']}
|
|
167
|
+
* files={[{ name: '文档.docx', url: 'https://example.com/doc.docx' }]}
|
|
168
|
+
* />
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export const MessageAttachments: React.FC<MessageAttachmentsProps> = ({
|
|
172
|
+
role = 'user',
|
|
173
|
+
images,
|
|
174
|
+
files,
|
|
175
|
+
}) => {
|
|
176
|
+
const hasImages = images && images.length > 0;
|
|
177
|
+
const hasFiles = files && files.length > 0;
|
|
178
|
+
|
|
179
|
+
if (!hasImages && !hasFiles) return null;
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<AttachmentsArea>
|
|
183
|
+
{/* 图片列表 */}
|
|
184
|
+
{hasImages && (
|
|
185
|
+
<ImagesRow>
|
|
186
|
+
{images.map((url, index) => (
|
|
187
|
+
<ImageCard key={index} $role={role}>
|
|
188
|
+
<div className="image-preview">
|
|
189
|
+
<Image
|
|
190
|
+
src={url}
|
|
191
|
+
width={160}
|
|
192
|
+
style={{ borderRadius: 6, objectFit: 'cover' }}
|
|
193
|
+
preview={{ mask: '预览' }}
|
|
194
|
+
/>
|
|
195
|
+
</div>
|
|
196
|
+
</ImageCard>
|
|
197
|
+
))}
|
|
198
|
+
</ImagesRow>
|
|
199
|
+
)}
|
|
200
|
+
|
|
201
|
+
{/* 文件列表 */}
|
|
202
|
+
{hasFiles && files.map((file, index) => {
|
|
203
|
+
const ext = getFileExtension(file.name);
|
|
204
|
+
return (
|
|
205
|
+
<FileCard
|
|
206
|
+
key={index}
|
|
207
|
+
className="appflow-file-card"
|
|
208
|
+
$role={role}
|
|
209
|
+
href={file.url}
|
|
210
|
+
target="_blank"
|
|
211
|
+
rel="noopener noreferrer"
|
|
212
|
+
>
|
|
213
|
+
<span className="file-icon">{getFileIcon(ext)}</span>
|
|
214
|
+
<div className="file-info">
|
|
215
|
+
<span className="file-name">{file.name}</span>
|
|
216
|
+
<div className="file-meta">
|
|
217
|
+
{ext && <span className="file-type">{ext}</span>}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</FileCard>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</AttachmentsArea>
|
|
224
|
+
);
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
export default MessageAttachments;
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import React, { useEffect, useState, useCallback } from 'react';
|
|
8
8
|
import styled from 'styled-components';
|
|
9
9
|
import { Modal, Image, Space } from 'antd';
|
|
10
|
+
import { MessageAttachments } from './MessageAttachments';
|
|
10
11
|
import { loadEchartsScript } from '@/utils/loadEcharts';
|
|
11
12
|
import { DocReferences, DocReferenceItem } from './DocReferences';
|
|
12
13
|
import { WebSearchPanel } from './WebSearchPanel';
|
|
@@ -47,6 +48,10 @@ export interface MessageBubbleProps {
|
|
|
47
48
|
status?: 'Running' | 'Success' | 'Error';
|
|
48
49
|
/** 参考资料列表 */
|
|
49
50
|
references?: DocReferenceItem[];
|
|
51
|
+
/** 图片URL列表(用户消息中上传的图片) */
|
|
52
|
+
images?: string[];
|
|
53
|
+
/** 文件列表(用户消息中上传的文件) */
|
|
54
|
+
files?: { name: string; url: string }[];
|
|
50
55
|
/** 自定义类名 */
|
|
51
56
|
className?: string;
|
|
52
57
|
/** 自定义样式 */
|
|
@@ -80,11 +85,10 @@ const StyledContainer = styled.div<{ $role: 'user' | 'bot' }>`
|
|
|
80
85
|
const StyledBubble = styled.div<{ $role: 'user' | 'bot' }>`
|
|
81
86
|
padding: 12px 16px;
|
|
82
87
|
border-radius: 12px;
|
|
83
|
-
background: ${props => props.$role === 'user'
|
|
84
|
-
|
|
85
|
-
: 'rgba(205, 208, 220, 0.15)'};
|
|
86
|
-
color: ${props => props.$role === 'user' ? '#fff' : '#333'};
|
|
88
|
+
background: ${props => props.$role === 'user' ? '#e5effe' : 'rgba(205, 208, 220, 0.15)'};
|
|
89
|
+
color: '#333';
|
|
87
90
|
word-break: break-word;
|
|
91
|
+
overflow: hidden;
|
|
88
92
|
|
|
89
93
|
/* 样式隔离 */
|
|
90
94
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
|
@@ -178,7 +182,7 @@ const StyledBubble = styled.div<{ $role: 'user' | 'bot' }>`
|
|
|
178
182
|
border-radius: 4px;
|
|
179
183
|
}
|
|
180
184
|
|
|
181
|
-
a {
|
|
185
|
+
a:not(.appflow-file-card) {
|
|
182
186
|
color: ${props => props.$role === 'user' ? '#fff' : '#1890ff'};
|
|
183
187
|
text-decoration: underline;
|
|
184
188
|
}
|
|
@@ -261,6 +265,8 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
|
|
261
265
|
role = 'bot',
|
|
262
266
|
status = 'Success',
|
|
263
267
|
references = [],
|
|
268
|
+
images,
|
|
269
|
+
files,
|
|
264
270
|
className,
|
|
265
271
|
style,
|
|
266
272
|
onReferenceClick,
|
|
@@ -375,6 +381,14 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
|
|
|
375
381
|
</ReferencesContainer>
|
|
376
382
|
)}
|
|
377
383
|
</BubbleContent>
|
|
384
|
+
|
|
385
|
+
{/* 附件展示区域:图片和文件 */}
|
|
386
|
+
<MessageAttachments
|
|
387
|
+
role={role}
|
|
388
|
+
images={images}
|
|
389
|
+
files={files}
|
|
390
|
+
/>
|
|
391
|
+
|
|
378
392
|
{contextHolder}
|
|
379
393
|
</StyledBubble>
|
|
380
394
|
</StyledContainer>
|
package/src/main.tsx
ADDED