@alicloud/appflow-chat 0.0.4-alpha.2 → 0.0.4-alpha.3

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.
@@ -0,0 +1,216 @@
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: 200px;
45
+ color: #333;
46
+
47
+ .image-preview {
48
+ border-radius: 6px;
49
+ overflow: hidden;
50
+ cursor: pointer;
51
+ }
52
+ `;
53
+
54
+ const ImagesRow = styled.div`
55
+ display: flex;
56
+ flex-wrap: wrap;
57
+ gap: 8px;
58
+ `;
59
+
60
+ const FileCard = styled.a<{ $role: 'user' | 'bot' }>`
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 10px;
64
+ background: ${props => props.$role === 'user' ? '#eef0ff' : '#e8f4fd'};
65
+ border-radius: 8px;
66
+ padding: 10px 12px;
67
+ text-decoration: none;
68
+ color: #333;
69
+ transition: background 0.2s;
70
+ max-width: 280px;
71
+
72
+ &:hover {
73
+ background: ${props => props.$role === 'user' ? '#e2e5ff' : '#d6ecf8'};
74
+ }
75
+
76
+ .file-icon {
77
+ font-size: 28px;
78
+ flex-shrink: 0;
79
+ color: #1677ff;
80
+ }
81
+
82
+ .file-info {
83
+ display: flex;
84
+ flex-direction: column;
85
+ gap: 2px;
86
+ min-width: 0;
87
+ }
88
+
89
+ .file-name {
90
+ font-size: 13px;
91
+ font-weight: 500;
92
+ overflow: hidden;
93
+ text-overflow: ellipsis;
94
+ white-space: nowrap;
95
+ color: #333;
96
+ }
97
+
98
+ .file-meta {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 6px;
102
+ }
103
+
104
+ .file-type {
105
+ font-size: 11px;
106
+ padding: 1px 4px;
107
+ border-radius: 3px;
108
+ background: rgba(0, 0, 0, 0.06);
109
+ color: #666;
110
+ text-transform: uppercase;
111
+ font-weight: 500;
112
+ }
113
+ `;
114
+
115
+ // ==================== 工具函数 ====================
116
+
117
+ /** 根据文件名获取文件扩展名 */
118
+ function getFileExtension(fileName: string): string {
119
+ const parts = fileName.split('.');
120
+ return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
121
+ }
122
+
123
+ /** 根据文件扩展名获取对应的图标组件 */
124
+ function getFileIcon(ext: string): React.ReactNode {
125
+ switch (ext) {
126
+ case 'doc':
127
+ case 'docx':
128
+ return <FileWordOutlined />;
129
+ case 'pdf':
130
+ return <FilePdfOutlined />;
131
+ case 'xls':
132
+ case 'xlsx':
133
+ case 'csv':
134
+ return <FileExcelOutlined />;
135
+ case 'txt':
136
+ case 'md':
137
+ case 'json':
138
+ return <FileTextOutlined />;
139
+ default:
140
+ return <FileOutlined />;
141
+ }
142
+ }
143
+
144
+ // ==================== 组件实现 ====================
145
+
146
+ /**
147
+ * MessageAttachments - 消息附件展示组件
148
+ *
149
+ * 在消息气泡中展示上传的图片(缩略图 + 点击预览)和文件(图标 + 文件名 + 类型标签 + 点击下载)。
150
+ *
151
+ * @example
152
+ * ```tsx
153
+ * <MessageAttachments
154
+ * role="user"
155
+ * images={['https://example.com/image.png']}
156
+ * files={[{ name: '文档.docx', url: 'https://example.com/doc.docx' }]}
157
+ * />
158
+ * ```
159
+ */
160
+ export const MessageAttachments: React.FC<MessageAttachmentsProps> = ({
161
+ role = 'user',
162
+ images,
163
+ files,
164
+ }) => {
165
+ const hasImages = images && images.length > 0;
166
+ const hasFiles = files && files.length > 0;
167
+
168
+ if (!hasImages && !hasFiles) return null;
169
+
170
+ return (
171
+ <AttachmentsArea>
172
+ {/* 图片列表 */}
173
+ {hasImages && (
174
+ <ImagesRow>
175
+ {images.map((url, index) => (
176
+ <ImageCard key={index} $role={role}>
177
+ <div className="image-preview">
178
+ <Image
179
+ src={url}
180
+ width={160}
181
+ style={{ borderRadius: 6, objectFit: 'cover' }}
182
+ preview={{ mask: '预览' }}
183
+ />
184
+ </div>
185
+ </ImageCard>
186
+ ))}
187
+ </ImagesRow>
188
+ )}
189
+
190
+ {/* 文件列表 */}
191
+ {hasFiles && files.map((file, index) => {
192
+ const ext = getFileExtension(file.name);
193
+ return (
194
+ <FileCard
195
+ key={index}
196
+ className="appflow-file-card"
197
+ $role={role}
198
+ href={file.url}
199
+ target="_blank"
200
+ rel="noopener noreferrer"
201
+ >
202
+ <span className="file-icon">{getFileIcon(ext)}</span>
203
+ <div className="file-info">
204
+ <span className="file-name">{file.name}</span>
205
+ <div className="file-meta">
206
+ {ext && <span className="file-type">{ext}</span>}
207
+ </div>
208
+ </div>
209
+ </FileCard>
210
+ );
211
+ })}
212
+ </AttachmentsArea>
213
+ );
214
+ };
215
+
216
+ 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,10 +85,8 @@ 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
- ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)'
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;
88
91
 
89
92
  /* 样式隔离 */
@@ -178,7 +181,7 @@ const StyledBubble = styled.div<{ $role: 'user' | 'bot' }>`
178
181
  border-radius: 4px;
179
182
  }
180
183
 
181
- a {
184
+ a:not(.appflow-file-card) {
182
185
  color: ${props => props.$role === 'user' ? '#fff' : '#1890ff'};
183
186
  text-decoration: underline;
184
187
  }
@@ -261,6 +264,8 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
261
264
  role = 'bot',
262
265
  status = 'Success',
263
266
  references = [],
267
+ images,
268
+ files,
264
269
  className,
265
270
  style,
266
271
  onReferenceClick,
@@ -375,6 +380,14 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
375
380
  </ReferencesContainer>
376
381
  )}
377
382
  </BubbleContent>
383
+
384
+ {/* 附件展示区域:图片和文件 */}
385
+ <MessageAttachments
386
+ role={role}
387
+ images={images}
388
+ files={files}
389
+ />
390
+
378
391
  {contextHolder}
379
392
  </StyledBubble>
380
393
  </StyledContainer>
package/src/main.tsx ADDED
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ ReactDOM.createRoot(document.getElementById('root')!).render(
6
+ <React.StrictMode>
7
+ <App />
8
+ </React.StrictMode>
9
+ );
@@ -35,6 +35,7 @@ export interface ModelInfo {
35
35
  config?: {
36
36
  image?: boolean;
37
37
  file?: boolean;
38
+ audio?: boolean;
38
39
  webSearch?: boolean;
39
40
  fileConfig?: string;
40
41
  };