@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,400 @@
1
+ /**
2
+ * MessageBubble - SDK封装的消息气泡组件
3
+ * 内部复用 BubbleContent 核心组件,提供简化的接口
4
+ * 包含默认的参考资料和网页搜索交互实现
5
+ */
6
+
7
+ import React, { useEffect, useState, useCallback } from 'react';
8
+ import styled from 'styled-components';
9
+ import { Modal, Image, Space } from 'antd';
10
+ import { loadEchartsScript } from '@/utils/loadEcharts';
11
+ import { DocReferences, DocReferenceItem } from './DocReferences';
12
+ import { WebSearchPanel } from './WebSearchPanel';
13
+ import { HumanVerify, HistoryCard, CustomParamSchema } from './HumanVerify';
14
+ import { BubbleContent } from '@/core';
15
+
16
+ /** HumanVerify 提交数据类型 */
17
+ export interface HumanVerifySubmitData {
18
+ verifyId: string;
19
+ sessionWebhook: string;
20
+ status: string;
21
+ customParamsKey: string;
22
+ customParamsValue: Record<string, any>;
23
+ }
24
+
25
+ /** HumanVerify 相关数据 */
26
+ export interface HumanVerifyData {
27
+ verifyId?: string;
28
+ sessionWebhook?: string;
29
+ approved?: boolean;
30
+ customParams?: CustomParamSchema;
31
+ customParamsKey?: string;
32
+ }
33
+
34
+ /** HistoryCard 相关数据 */
35
+ export interface HistoryCardData {
36
+ approvalStatus?: string;
37
+ formValues?: Record<string, any>;
38
+ formSchema?: CustomParamSchema;
39
+ }
40
+
41
+ export interface MessageBubbleProps {
42
+ /** 消息内容(Markdown格式) */
43
+ content: string;
44
+ /** 消息角色 */
45
+ role?: 'user' | 'bot';
46
+ /** 渲染状态 */
47
+ status?: 'Running' | 'Success' | 'Error';
48
+ /** 参考资料列表 */
49
+ references?: DocReferenceItem[];
50
+ /** 自定义类名 */
51
+ className?: string;
52
+ /** 自定义样式 */
53
+ style?: React.CSSProperties;
54
+ /** 点击参考资料回调(不传则使用默认实现) */
55
+ onReferenceClick?: (item: DocReferenceItem) => void;
56
+ /** 点击网页搜索结果回调(不传则使用默认实现) */
57
+ onWebSearchClick?: (items: DocReferenceItem[]) => void;
58
+
59
+ // ==================== HumanVerify相关Props ====================
60
+
61
+ /** 事件类型(用于特殊消息如 humanVerify、historyCard) */
62
+ eventType?: 'humanVerify' | 'historyCard';
63
+ /** HumanVerify 相关数据 */
64
+ humanVerifyData?: HumanVerifyData;
65
+ /** HistoryCard 相关数据(历史对话中的审核卡片) */
66
+ historyCardData?: HistoryCardData;
67
+ /** HumanVerify 提交回调 */
68
+ onHumanVerifySubmit?: (data: HumanVerifySubmitData) => void;
69
+ }
70
+
71
+ // 样式隔离容器
72
+ const StyledContainer = styled.div<{ $role: 'user' | 'bot' }>`
73
+ display: flex;
74
+ flex-direction: column;
75
+ max-width: ${props => props.$role === 'user' ? '80%' : '100%'};
76
+ align-self: ${props => props.$role === 'user' ? 'flex-end' : 'flex-start'};
77
+ `;
78
+
79
+ // 消息气泡样式
80
+ const StyledBubble = styled.div<{ $role: 'user' | 'bot' }>`
81
+ padding: 12px 16px;
82
+ 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'};
87
+ word-break: break-word;
88
+
89
+ /* 样式隔离 */
90
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
91
+ font-size: 14px;
92
+ line-height: 1.6;
93
+
94
+ * {
95
+ box-sizing: border-box;
96
+ }
97
+
98
+ /* Markdown内容样式 */
99
+ p {
100
+ margin: 0 0 8px 0;
101
+ &:last-child {
102
+ margin-bottom: 0;
103
+ }
104
+ }
105
+
106
+ ul, ol {
107
+ margin: 8px 0;
108
+ padding-left: 20px;
109
+ }
110
+
111
+ li {
112
+ margin: 4px 0;
113
+ p {
114
+ display: block !important;
115
+ }
116
+ }
117
+
118
+ code {
119
+ background: ${props => props.$role === 'user'
120
+ ? 'rgba(255, 255, 255, 0.2)'
121
+ : 'rgba(0, 0, 0, 0.05)'};
122
+ padding: 2px 6px;
123
+ border-radius: 4px;
124
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
125
+ font-size: 13px;
126
+ }
127
+
128
+ pre {
129
+ background: ${props => props.$role === 'user'
130
+ ? 'rgba(0, 0, 0, 0.2)'
131
+ : '#f6f8fa'};
132
+ padding: 12px;
133
+ border-radius: 6px;
134
+ overflow-x: auto;
135
+
136
+ code {
137
+ background: transparent;
138
+ padding: 0;
139
+ }
140
+ }
141
+
142
+ blockquote {
143
+ margin: 8px 0;
144
+ padding: 8px 16px;
145
+ border-left: 4px solid ${props => props.$role === 'user'
146
+ ? 'rgba(255, 255, 255, 0.5)'
147
+ : '#ddd'};
148
+ background: ${props => props.$role === 'user'
149
+ ? 'rgba(0, 0, 0, 0.1)'
150
+ : '#f9f9f9'};
151
+ }
152
+
153
+ table {
154
+ border-collapse: collapse;
155
+ width: 100%;
156
+ margin: 8px 0;
157
+
158
+ th, td {
159
+ border: 1px solid ${props => props.$role === 'user'
160
+ ? 'rgba(255, 255, 255, 0.3)'
161
+ : '#ddd'};
162
+ padding: 8px 12px;
163
+ text-align: left;
164
+ }
165
+
166
+ th {
167
+ background: ${props => props.$role === 'user'
168
+ ? 'rgba(0, 0, 0, 0.1)'
169
+ : '#f6f8fa'};
170
+ font-weight: 600;
171
+ }
172
+ }
173
+
174
+ img {
175
+ max-width: 200px;
176
+ max-height: 150px;
177
+ object-fit: contain;
178
+ border-radius: 4px;
179
+ }
180
+
181
+ a {
182
+ color: ${props => props.$role === 'user' ? '#fff' : '#1890ff'};
183
+ text-decoration: underline;
184
+ }
185
+
186
+ h1, h2, h3, h4, h5, h6 {
187
+ margin: 16px 0 8px 0;
188
+ font-weight: 600;
189
+ line-height: 1.4;
190
+
191
+ &:first-child {
192
+ margin-top: 0;
193
+ }
194
+ }
195
+
196
+ /* 深度思考样式 */
197
+ details {
198
+ margin: 8px 0;
199
+ padding: 8px 12px;
200
+ background: ${props => props.$role === 'user'
201
+ ? 'rgba(0, 0, 0, 0.1)'
202
+ : '#f6f8fa'};
203
+ border-radius: 6px;
204
+
205
+ summary {
206
+ cursor: pointer;
207
+ font-weight: 500;
208
+ }
209
+
210
+ pre.think {
211
+ margin: 8px 0 0 0;
212
+ padding: 8px;
213
+ background: ${props => props.$role === 'user'
214
+ ? 'rgba(0, 0, 0, 0.1)'
215
+ : '#fff'};
216
+ border-radius: 4px;
217
+ white-space: pre-wrap;
218
+ font-size: 13px;
219
+ }
220
+ }
221
+ `;
222
+
223
+ // 参考资料容器
224
+ const ReferencesContainer = styled.div`
225
+ margin-top: 12px;
226
+ `;
227
+
228
+ // 图片容器样式
229
+ const StyledWrapSpace = styled.div`
230
+ display: flex;
231
+ flex-wrap: wrap;
232
+ gap: 8px;
233
+ `;
234
+
235
+ /**
236
+ * MessageBubble - 消息气泡组件
237
+ *
238
+ * @example
239
+ * ```tsx
240
+ * 使用默认交互
241
+ * <MessageBubble
242
+ * content="这是AI的回复,支持**Markdown**格式"
243
+ * role="bot"
244
+ * status="Success"
245
+ * references={[
246
+ * { title: '参考文档1', text: '文档内容...', type: 'rag' }
247
+ * ]}
248
+ * />
249
+ *
250
+ * // 自定义交互
251
+ * <MessageBubble
252
+ * content="这是AI的回复"
253
+ * references={references}
254
+ * onReferenceClick={(item) => console.log('自定义处理', item)}
255
+ * onWebSearchClick={(items) => console.log('自定义处理', items)}
256
+ * />
257
+ * ```
258
+ */
259
+ export const MessageBubble: React.FC<MessageBubbleProps> = ({
260
+ content,
261
+ role = 'bot',
262
+ status = 'Success',
263
+ references = [],
264
+ className,
265
+ style,
266
+ onReferenceClick,
267
+ onWebSearchClick,
268
+ // HumanVerify 相关 props
269
+ eventType,
270
+ humanVerifyData,
271
+ historyCardData,
272
+ onHumanVerifySubmit,
273
+ }) => {
274
+ const [modal, contextHolder] = Modal.useModal();
275
+
276
+ // 网页搜索抽屉状态
277
+ const [webSearchOpen, setWebSearchOpen] = useState(false);
278
+ const [webSearchItems, setWebSearchItems] = useState<DocReferenceItem[]>([]);
279
+
280
+ // 自动加载ECharts(如果内容中包含echarts代码块)
281
+ useEffect(() => {
282
+ if (content?.includes('```echarts')) {
283
+ loadEchartsScript().catch(console.error);
284
+ }
285
+ }, [content]);
286
+
287
+ // 默认的参考资料点击处理
288
+ const defaultReferenceClick = useCallback((item: DocReferenceItem) => {
289
+ const title = item.Title || item.title;
290
+ const text = item.Text || item.text;
291
+ const images = item.Images || item.images;
292
+
293
+ modal.confirm({
294
+ bodyStyle: { maxHeight: '80vh', overflow: 'auto' },
295
+ icon: null,
296
+ title: '参考资料',
297
+ destroyOnClose: true,
298
+ maskClosable: true,
299
+ closable: true,
300
+ width: 800,
301
+ content: (
302
+ <Space direction='vertical' style={{ width: '100%' }}>
303
+ <div style={{ borderRadius: '4px', backgroundColor: '#f0f2f5', padding: '8px' }}>
304
+ {title && <div style={{ fontWeight: 'bold', marginBottom: '8px' }}>{title}</div>}
305
+ {text && <div>{text}</div>}
306
+ </div>
307
+ {images && images.length > 0 && (
308
+ <div style={{ borderRadius: '4px', backgroundColor: '#f0f2f5', padding: '8px' }}>
309
+ <StyledWrapSpace>
310
+ {images.map((url, index) => (
311
+ <Image key={index} src={url} height={50} />
312
+ ))}
313
+ </StyledWrapSpace>
314
+ </div>
315
+ )}
316
+ </Space>
317
+ ),
318
+ footer: null,
319
+ });
320
+ }, [modal]);
321
+
322
+ // 默认的网页搜索点击处理
323
+ const defaultWebSearchClick = useCallback((items: DocReferenceItem[]) => {
324
+ setWebSearchItems(items);
325
+ setWebSearchOpen(true);
326
+ }, []);
327
+
328
+ // 关闭网页搜索抽屉
329
+ const handleCloseWebSearch = useCallback(() => {
330
+ setWebSearchOpen(false);
331
+ }, []);
332
+
333
+ // 优先使用用户传入的回调,否则使用默认实现
334
+ const handleReferenceClick = onReferenceClick || defaultReferenceClick;
335
+ const handleWebSearchClick = onWebSearchClick || defaultWebSearchClick;
336
+
337
+ return (
338
+ <>
339
+ <StyledContainer
340
+ $role={role}
341
+ className={`appflow-sdk-message-bubble ${className || ''}`}
342
+ style={style}
343
+ >
344
+ <StyledBubble $role={role}>
345
+ {/* 使用核心组件渲染内容 */}
346
+ <BubbleContent
347
+ content={content}
348
+ status={status}
349
+ role={role}
350
+ >
351
+ {/* HumanVerify事件 - 人工审核表单 */}
352
+ {eventType === 'humanVerify' && humanVerifyData && (
353
+ <HumanVerify
354
+ verifyId={humanVerifyData.verifyId}
355
+ sessionWebhook={humanVerifyData.sessionWebhook}
356
+ approved={humanVerifyData.approved}
357
+ customParamsSchema={humanVerifyData.customParams}
358
+ customParamsKey={humanVerifyData.customParamsKey}
359
+ onSubmit={onHumanVerifySubmit}
360
+ />
361
+ )}
362
+
363
+ {/* HistoryCard 事件 - 历史对话中的审核卡片 */}
364
+ {eventType === 'historyCard' && historyCardData && (
365
+ <HistoryCard
366
+ approvalStatus={historyCardData.approvalStatus}
367
+ formValues={historyCardData.formValues}
368
+ formSchema={historyCardData.formSchema}
369
+ />
370
+ )}
371
+
372
+ {/* 参考资料 */}
373
+ {references.length > 0 && status === 'Success' && (
374
+ <ReferencesContainer>
375
+ <DocReferences
376
+ items={references}
377
+ status={status}
378
+ onItemClick={handleReferenceClick}
379
+ onWebSearchClick={handleWebSearchClick}
380
+ />
381
+ </ReferencesContainer>
382
+ )}
383
+ </BubbleContent>
384
+ {contextHolder}
385
+ </StyledBubble>
386
+ </StyledContainer>
387
+
388
+ {/* 默认的网页搜索抽屉(仅在用户未传入onWebSearchClick时使用) */}
389
+ {!onWebSearchClick && (
390
+ <WebSearchPanel
391
+ items={webSearchItems}
392
+ open={webSearchOpen}
393
+ onClose={handleCloseWebSearch}
394
+ />
395
+ )}
396
+ </>
397
+ );
398
+ };
399
+
400
+ export default MessageBubble;
@@ -0,0 +1,283 @@
1
+ /**
2
+ * RichMessageBubble - SDK封装的富文本消息气泡组件
3
+ * 内部复用 RichBubbleContent 核心组件,提供简化的接口
4
+ * 支持多种消息类型:markdown、rich、ant_table、code、echart、step、error
5
+ * 包含默认的参考资料和网页搜索交互实现
6
+ */
7
+
8
+ import React, { useEffect, useState, useCallback } from 'react';
9
+ import styled from 'styled-components';
10
+ import { Modal, Image, Space } from 'antd';
11
+ import { loadEchartsScript } from '../utils/loadEcharts';
12
+ import { DocReferences, DocReferenceItem } from './DocReferences';
13
+ import { WebSearchPanel } from './WebSearchPanel';
14
+ import { RichBubbleContent } from '../core';
15
+
16
+ export interface RichMessageBubbleProps {
17
+ /** 消息内容 */
18
+ content: string;
19
+ /** 消息类型 */
20
+ messageType?: 'markdown' | 'rich';
21
+ /** 渲染状态 */
22
+ status?: 'Running' | 'Success' | 'Error';
23
+ /** 参考资料列表 */
24
+ references?: DocReferenceItem[];
25
+ /** 自定义类名 */
26
+ className?: string;
27
+ /** 自定义样式 */
28
+ style?: React.CSSProperties;
29
+ /** 点击参考资料回调(不传则使用默认实现) */
30
+ onReferenceClick?: (item: DocReferenceItem) => void;
31
+ /** 点击网页搜索结果回调(不传则使用默认实现) */
32
+ onWebSearchClick?: (items: DocReferenceItem[]) => void;
33
+ }
34
+
35
+ // 样式组件 - SDK专用样式隔离
36
+ const StyledBubble = styled.div`
37
+ padding: 12px 16px;
38
+ border-radius: 12px;
39
+ background: rgba(205, 208, 220, 0.15);
40
+ color: #333;
41
+ word-break: break-word;
42
+
43
+ /* 样式隔离 */
44
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
45
+ font-size: 14px;
46
+ line-height: 1.6;
47
+
48
+ * {
49
+ box-sizing: border-box;
50
+ }
51
+
52
+ /* Markdown内容样式 */
53
+ p {
54
+ margin: 0 0 8px 0;
55
+ &:last-child {
56
+ margin-bottom: 0;
57
+ }
58
+ }
59
+
60
+ ul, ol {
61
+ margin: 8px 0;
62
+ padding-left: 20px;
63
+ }
64
+
65
+ li {
66
+ margin: 4px 0;
67
+ p {
68
+ display: block !important;
69
+ }
70
+ }
71
+
72
+ code {
73
+ background: rgba(0, 0, 0, 0.05);
74
+ padding: 2px 6px;
75
+ border-radius: 4px;
76
+ font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
77
+ font-size: 13px;
78
+ }
79
+
80
+ pre {
81
+ background: #f6f8fa;
82
+ padding: 12px;
83
+ border-radius: 6px;
84
+ overflow-x: auto;
85
+
86
+ code {
87
+ background: transparent;
88
+ padding: 0;
89
+ }
90
+ }
91
+
92
+ table {
93
+ border-collapse: collapse;
94
+ width: 100%;
95
+ margin: 8px 0;
96
+
97
+ th, td {
98
+ border: 1px solid #ddd;
99
+ padding: 8px 12px;
100
+ text-align: left;
101
+ }
102
+
103
+ th {
104
+ background: #f6f8fa;
105
+ font-weight: 600;
106
+ }
107
+
108
+ tr:nth-child(even) {
109
+ background: #f9f9f9;
110
+ }
111
+ }
112
+
113
+ img {
114
+ max-width: 200px;
115
+ max-height: 150px;
116
+ object-fit: contain;
117
+ border-radius: 4px;
118
+ }
119
+
120
+ a {
121
+ color: #1890ff;
122
+ text-decoration: none;
123
+
124
+ &:hover {
125
+ text-decoration: underline;
126
+ }
127
+ }
128
+ `;
129
+
130
+ // 参考资料容器
131
+ const ReferencesContainer = styled.div`
132
+ margin-top: 12px;
133
+ `;
134
+
135
+ // 图片容器样式
136
+ const StyledWrapSpace = styled.div`
137
+ display: flex;
138
+ flex-wrap: wrap;
139
+ gap: 8px;
140
+ `;
141
+
142
+ /**
143
+ * RichMessageBubble - 富文本消息气泡组件
144
+ *
145
+ * @example
146
+ * ```tsx
147
+ * // 使用默认交互
148
+ * <RichMessageBubble
149
+ * content="# 标题\n正文内容"
150
+ * messageType="markdown"
151
+ * status="Success"
152
+ * references={[
153
+ * { title: '参考文档1', text: '文档内容...', type: 'rag' }
154
+ * ]}
155
+ * />
156
+ *
157
+ * // 自定义交互
158
+ * <RichMessageBubble
159
+ * content={JSON.stringify([
160
+ * { messageType: 'markdown', content: '# 分析结果' },
161
+ * { messageType: 'echart', content: JSON.stringify({ option: {...} }) }
162
+ * ])}
163
+ * messageType="rich"
164
+ * references={references}
165
+ * onReferenceClick={(item) => console.log('自定义处理', item)}
166
+ * onWebSearchClick={(items) => console.log('自定义处理', items)}
167
+ * />
168
+ * ```
169
+ */
170
+ export const RichMessageBubble: React.FC<RichMessageBubbleProps> = ({
171
+ content,
172
+ messageType = 'markdown',
173
+ status = 'Success',
174
+ references = [],
175
+ className,
176
+ style,
177
+ onReferenceClick,
178
+ onWebSearchClick,
179
+ }) => {
180
+ const [modal, contextHolder] = Modal.useModal();
181
+
182
+ // 网页搜索抽屉状态
183
+ const [webSearchOpen, setWebSearchOpen] = useState(false);
184
+ const [webSearchItems, setWebSearchItems] = useState<DocReferenceItem[]>([]);
185
+
186
+ // 自动加载ECharts
187
+ useEffect(() => {
188
+ if (content?.includes('echart') || content?.includes('echarts')) {
189
+ loadEchartsScript().catch(console.error);
190
+ }
191
+ }, [content]);
192
+
193
+ // 默认的参考资料点击处理
194
+ const defaultReferenceClick = useCallback((item: DocReferenceItem) => {
195
+ const title = item.Title || item.title;
196
+ const text = item.Text || item.text;
197
+ const images = item.Images || item.images;
198
+
199
+ modal.confirm({
200
+ bodyStyle: { maxHeight: '80vh', overflow: 'auto' },
201
+ icon: null,
202
+ title: '参考资料',
203
+ destroyOnClose: true,
204
+ maskClosable: true,
205
+ closable: true,
206
+ width: 800,
207
+ content: (
208
+ <Space direction='vertical' style={{ width: '100%' }}>
209
+ <div style={{ borderRadius: '4px', backgroundColor: '#f0f2f5', padding: '8px' }}>
210
+ {title && <div style={{ fontWeight: 'bold', marginBottom: '8px' }}>{title}</div>}
211
+ {text && <div>{text}</div>}
212
+ </div>
213
+ {images && images.length > 0 && (
214
+ <div style={{ borderRadius: '4px', backgroundColor: '#f0f2f5', padding: '8px' }}>
215
+ <StyledWrapSpace>
216
+ {images.map((url: string, index: number) => (
217
+ <Image key={index} src={url} height={50} />
218
+ ))}
219
+ </StyledWrapSpace>
220
+ </div>
221
+ )}
222
+ </Space>
223
+ ),
224
+ footer: null,
225
+ });
226
+ }, [modal]);
227
+
228
+ // 默认的网页搜索点击处理
229
+ const defaultWebSearchClick = useCallback((items: DocReferenceItem[]) => {
230
+ setWebSearchItems(items);
231
+ setWebSearchOpen(true);
232
+ }, []);
233
+
234
+ // 关闭网页搜索抽屉
235
+ const handleCloseWebSearch = useCallback(() => {
236
+ setWebSearchOpen(false);
237
+ }, []);
238
+
239
+ // 优先使用用户传入的回调,否则使用默认实现
240
+ const handleReferenceClick = onReferenceClick || defaultReferenceClick;
241
+ const handleWebSearchClick = onWebSearchClick || defaultWebSearchClick;
242
+
243
+ return (
244
+ <>
245
+ <StyledBubble
246
+ className={`appflow-sdk-rich-message-bubble ${className || ''}`}
247
+ style={style}
248
+ >
249
+ {/* 使用核心组件渲染内容 */}
250
+ <RichBubbleContent
251
+ content={content}
252
+ messageType={messageType}
253
+ status={status}
254
+ role="bot"
255
+ >
256
+ {/* 参考资料 */}
257
+ {references.length > 0 && status === 'Success' && (
258
+ <ReferencesContainer>
259
+ <DocReferences
260
+ items={references}
261
+ status={status}
262
+ onItemClick={handleReferenceClick}
263
+ onWebSearchClick={handleWebSearchClick}
264
+ />
265
+ </ReferencesContainer>
266
+ )}
267
+ </RichBubbleContent>
268
+ {contextHolder}
269
+ </StyledBubble>
270
+
271
+ {/* 默认的网页搜索抽屉(仅在用户未传入onWebSearchClick时使用) */}
272
+ {!onWebSearchClick && (
273
+ <WebSearchPanel
274
+ items={webSearchItems}
275
+ open={webSearchOpen}
276
+ onClose={handleCloseWebSearch}
277
+ />
278
+ )}
279
+ </>
280
+ );
281
+ };
282
+
283
+ export default RichMessageBubble;