@alicloud/appflow-chat 0.0.4-alpha.6 → 0.0.4-alpha.8

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.
@@ -491,6 +491,7 @@ export declare interface HistoryMessage {
491
491
  name: string;
492
492
  url: string;
493
493
  }[];
494
+ audio?: string;
494
495
  }
495
496
 
496
497
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alicloud/appflow-chat",
3
- "version": "0.0.4-alpha.6",
3
+ "version": "0.0.4-alpha.8",
4
4
  "description": "Appflow-Chat AI聊天机器人组件库,提供聊天服务和UI组件",
5
5
  "type": "module",
6
6
  "main": "./dist/appflow-chat.cjs.js",
@@ -0,0 +1,246 @@
1
+ /**
2
+ * AudioPlayer - 语音消息播放器组件
3
+ * 用于在消息气泡中展示语音消息,支持播放/暂停、进度条、波形动画、时长显示
4
+ */
5
+
6
+ import React, { useRef, useState, useCallback, useEffect } from 'react';
7
+ import styled, { keyframes, css } from 'styled-components';
8
+ import {
9
+ PlayCircleOutlined,
10
+ PauseCircleOutlined,
11
+ } from '@ant-design/icons';
12
+
13
+ // ==================== 类型定义 ====================
14
+
15
+ export interface AudioPlayerProps {
16
+ /** 音频文件URL */
17
+ src: string;
18
+ /** 消息角色,影响配色 */
19
+ role?: 'user' | 'bot';
20
+ }
21
+
22
+ // ==================== 样式组件 ====================
23
+
24
+ const waveAnimation = keyframes`
25
+ 0%, 100% { height: 4px; }
26
+ 50% { height: 16px; }
27
+ `;
28
+
29
+ const AudioCard = styled.div<{ $role: 'user' | 'bot' }>`
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 10px;
33
+ background: ${props => props.$role === 'user' ? '#e5effe' : '#e8f4fd'};
34
+ border-radius: 12px;
35
+ padding: 10px 14px;
36
+ max-width: 100%;
37
+ box-sizing: border-box;
38
+ min-width: 200px;
39
+ `;
40
+
41
+ const PlayButton = styled.span<{ $role: 'user' | 'bot' }>`
42
+ font-size: 28px;
43
+ cursor: pointer;
44
+ flex-shrink: 0;
45
+ color: ${props => props.$role === 'user' ? '#667eea' : '#1677ff'};
46
+ display: flex;
47
+ align-items: center;
48
+ transition: opacity 0.2s;
49
+
50
+ &:hover {
51
+ opacity: 0.8;
52
+ }
53
+ `;
54
+
55
+ const AudioBody = styled.div`
56
+ flex: 1;
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: 6px;
60
+ min-width: 0;
61
+ `;
62
+
63
+ const WaveformContainer = styled.div`
64
+ display: flex;
65
+ align-items: center;
66
+ gap: 2px;
67
+ height: 20px;
68
+ `;
69
+
70
+ const WaveBar = styled.div<{ $active: boolean; $playing: boolean; $delay: number }>`
71
+ width: 3px;
72
+ border-radius: 2px;
73
+ background: ${props => props.$active ? '#667eea' : '#c0c8d8'};
74
+ transition: background 0.15s;
75
+
76
+ ${props => props.$playing && props.$active ? css`
77
+ animation: ${waveAnimation} 0.6s ease-in-out infinite;
78
+ animation-delay: ${props.$delay}s;
79
+ ` : css`
80
+ height: ${props.$active ? '12px' : `${4 + Math.random() * 10}px`};
81
+ `}
82
+ `;
83
+
84
+ const ProgressBar = styled.div`
85
+ position: relative;
86
+ height: 3px;
87
+ background: rgba(0, 0, 0, 0.08);
88
+ border-radius: 2px;
89
+ cursor: pointer;
90
+ overflow: visible;
91
+ `;
92
+
93
+ const ProgressFill = styled.div<{ $progress: number }>`
94
+ height: 100%;
95
+ background: #667eea;
96
+ border-radius: 2px;
97
+ width: ${props => props.$progress}%;
98
+ transition: width 0.1s linear;
99
+ position: relative;
100
+
101
+ &::after {
102
+ content: '';
103
+ position: absolute;
104
+ right: -4px;
105
+ top: 50%;
106
+ transform: translateY(-50%);
107
+ width: 8px;
108
+ height: 8px;
109
+ border-radius: 50%;
110
+ background: #667eea;
111
+ opacity: 0;
112
+ transition: opacity 0.2s;
113
+ }
114
+
115
+ ${ProgressBar}:hover &::after {
116
+ opacity: 1;
117
+ }
118
+ `;
119
+
120
+ const AudioDuration = styled.span`
121
+ font-size: 12px;
122
+ color: #888;
123
+ flex-shrink: 0;
124
+ min-width: 32px;
125
+ text-align: right;
126
+ font-variant-numeric: tabular-nums;
127
+ `;
128
+
129
+ // ==================== 工具函数 ====================
130
+
131
+ /** 格式化秒数为 m:ss */
132
+ function formatDuration(seconds: number): string {
133
+ if (!seconds || !isFinite(seconds)) return '0:00';
134
+ const m = Math.floor(seconds / 60);
135
+ const s = Math.floor(seconds % 60);
136
+ return `${m}:${s.toString().padStart(2, '0')}`;
137
+ }
138
+
139
+ /** 生成固定的波形条高度 */
140
+ const WAVE_BARS = Array.from({ length: 20 }, (_, i) => {
141
+ // 用正弦函数生成自然的波形高度
142
+ const base = Math.sin((i / 20) * Math.PI) * 12 + 4;
143
+ return Math.max(4, Math.min(18, base + (Math.random() * 4 - 2)));
144
+ });
145
+
146
+ // ==================== 组件实现 ====================
147
+
148
+ /**
149
+ * AudioPlayer - 语音消息播放器
150
+ *
151
+ * 紧凑的播放器组件,包含播放/暂停按钮 + 波形条 + 进度条 + 时长显示。
152
+ * 风格类似钉钉/微信的聊天语音消息,可独立使用无需外层气泡包裹。
153
+ *
154
+ * @example
155
+ * ```tsx
156
+ * <AudioPlayer src="https://example.com/audio.webm" role="user" />
157
+ * ```
158
+ */
159
+ export const AudioPlayer: React.FC<AudioPlayerProps> = ({ src, role = 'user' }) => {
160
+ const audioRef = useRef<HTMLAudioElement>(null);
161
+ const progressRef = useRef<HTMLDivElement>(null);
162
+ const [isPlaying, setIsPlaying] = useState(false);
163
+ const [currentTime, setCurrentTime] = useState(0);
164
+ const [duration, setDuration] = useState(0);
165
+
166
+ const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
167
+
168
+ useEffect(() => {
169
+ const audio = audioRef.current;
170
+ if (!audio) return;
171
+
172
+ const onLoadedMetadata = () => setDuration(audio.duration);
173
+ const onTimeUpdate = () => setCurrentTime(audio.currentTime);
174
+ const onEnded = () => {
175
+ setIsPlaying(false);
176
+ setCurrentTime(0);
177
+ };
178
+
179
+ audio.addEventListener('loadedmetadata', onLoadedMetadata);
180
+ audio.addEventListener('timeupdate', onTimeUpdate);
181
+ audio.addEventListener('ended', onEnded);
182
+
183
+ return () => {
184
+ audio.removeEventListener('loadedmetadata', onLoadedMetadata);
185
+ audio.removeEventListener('timeupdate', onTimeUpdate);
186
+ audio.removeEventListener('ended', onEnded);
187
+ };
188
+ }, []);
189
+
190
+ const togglePlay = useCallback(() => {
191
+ const audio = audioRef.current;
192
+ if (!audio) return;
193
+
194
+ if (isPlaying) {
195
+ audio.pause();
196
+ setIsPlaying(false);
197
+ } else {
198
+ audio.play().then(() => setIsPlaying(true)).catch(() => {});
199
+ }
200
+ }, [isPlaying]);
201
+
202
+ const handleProgressClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
203
+ const audio = audioRef.current;
204
+ const bar = progressRef.current;
205
+ if (!audio || !bar || !duration) return;
206
+
207
+ const rect = bar.getBoundingClientRect();
208
+ const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
209
+ audio.currentTime = ratio * duration;
210
+ setCurrentTime(audio.currentTime);
211
+ }, [duration]);
212
+
213
+ return (
214
+ <AudioCard $role={role}>
215
+ <audio ref={audioRef} src={src} preload="metadata" />
216
+ <PlayButton $role={role} onClick={togglePlay}>
217
+ {isPlaying ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
218
+ </PlayButton>
219
+ <AudioBody>
220
+ <WaveformContainer>
221
+ {WAVE_BARS.map((h, i) => {
222
+ const barProgress = (i / WAVE_BARS.length) * 100;
223
+ const isActive = barProgress <= progress;
224
+ return (
225
+ <WaveBar
226
+ key={i}
227
+ $active={isActive}
228
+ $playing={isPlaying}
229
+ $delay={i * 0.05}
230
+ style={!isPlaying || !isActive ? { height: `${h}px` } : undefined}
231
+ />
232
+ );
233
+ })}
234
+ </WaveformContainer>
235
+ <ProgressBar ref={progressRef} onClick={handleProgressClick}>
236
+ <ProgressFill $progress={progress} />
237
+ </ProgressBar>
238
+ </AudioBody>
239
+ <AudioDuration>
240
+ {isPlaying || currentTime > 0 ? formatDuration(currentTime) : formatDuration(duration)}
241
+ </AudioDuration>
242
+ </AudioCard>
243
+ );
244
+ };
245
+
246
+ export default AudioPlayer;
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * MessageAttachments - 消息附件展示组件
3
- * 用于在消息气泡中展示上传的图片、文件和语音消息
3
+ * 用于在消息气泡中展示上传的图片和文件
4
4
  */
5
5
 
6
- import React, { useRef, useState, useCallback, useEffect } from 'react';
7
- import styled, { keyframes, css } from 'styled-components';
6
+ import React from 'react';
7
+ import styled from 'styled-components';
8
8
  import { Image } from 'antd';
9
9
  import {
10
10
  FileWordOutlined,
@@ -12,8 +12,6 @@ import {
12
12
  FileExcelOutlined,
13
13
  FileTextOutlined,
14
14
  FileOutlined,
15
- PlayCircleOutlined,
16
- PauseCircleOutlined,
17
15
  } from '@ant-design/icons';
18
16
 
19
17
  // ==================== 类型定义 ====================
@@ -25,8 +23,6 @@ export interface MessageAttachmentsProps {
25
23
  images?: string[];
26
24
  /** 文件列表 */
27
25
  files?: { name: string; url: string }[];
28
- /** 语音消息URL */
29
- audio?: string;
30
26
  }
31
27
 
32
28
  // ==================== 样式组件 ====================
@@ -127,217 +123,6 @@ const FileCard = styled.a<{ $role: 'user' | 'bot' }>`
127
123
  }
128
124
  `;
129
125
 
130
- // ==================== 语音播放器样式 ====================
131
-
132
- const waveAnimation = keyframes`
133
- 0%, 100% { height: 4px; }
134
- 50% { height: 16px; }
135
- `;
136
-
137
- const AudioCard = styled.div<{ $role: 'user' | 'bot' }>`
138
- display: flex;
139
- align-items: center;
140
- gap: 10px;
141
- background: ${props => props.$role === 'user' ? '#eef0ff' : '#e8f4fd'};
142
- border-radius: 8px;
143
- padding: 10px 14px;
144
- max-width: 100%;
145
- box-sizing: border-box;
146
- min-width: 200px;
147
- `;
148
-
149
- const PlayButton = styled.span<{ $role: 'user' | 'bot' }>`
150
- font-size: 28px;
151
- cursor: pointer;
152
- flex-shrink: 0;
153
- color: ${props => props.$role === 'user' ? '#667eea' : '#1677ff'};
154
- display: flex;
155
- align-items: center;
156
- transition: opacity 0.2s;
157
-
158
- &:hover {
159
- opacity: 0.8;
160
- }
161
- `;
162
-
163
- const AudioBody = styled.div`
164
- flex: 1;
165
- display: flex;
166
- flex-direction: column;
167
- gap: 6px;
168
- min-width: 0;
169
- `;
170
-
171
- const WaveformContainer = styled.div`
172
- display: flex;
173
- align-items: center;
174
- gap: 2px;
175
- height: 20px;
176
- `;
177
-
178
- const WaveBar = styled.div<{ $active: boolean; $playing: boolean; $delay: number }>`
179
- width: 3px;
180
- border-radius: 2px;
181
- background: ${props => props.$active ? '#667eea' : '#c0c8d8'};
182
- transition: background 0.15s;
183
-
184
- ${props => props.$playing && props.$active ? css`
185
- animation: ${waveAnimation} 0.6s ease-in-out infinite;
186
- animation-delay: ${props.$delay}s;
187
- ` : css`
188
- height: ${props.$active ? '12px' : `${4 + Math.random() * 10}px`};
189
- `}
190
- `;
191
-
192
- const ProgressBar = styled.div`
193
- position: relative;
194
- height: 3px;
195
- background: rgba(0, 0, 0, 0.08);
196
- border-radius: 2px;
197
- cursor: pointer;
198
- overflow: visible;
199
- `;
200
-
201
- const ProgressFill = styled.div<{ $progress: number }>`
202
- height: 100%;
203
- background: #667eea;
204
- border-radius: 2px;
205
- width: ${props => props.$progress}%;
206
- transition: width 0.1s linear;
207
- position: relative;
208
-
209
- &::after {
210
- content: '';
211
- position: absolute;
212
- right: -4px;
213
- top: 50%;
214
- transform: translateY(-50%);
215
- width: 8px;
216
- height: 8px;
217
- border-radius: 50%;
218
- background: #667eea;
219
- opacity: 0;
220
- transition: opacity 0.2s;
221
- }
222
-
223
- ${ProgressBar}:hover &::after {
224
- opacity: 1;
225
- }
226
- `;
227
-
228
- const AudioDuration = styled.span`
229
- font-size: 12px;
230
- color: #888;
231
- flex-shrink: 0;
232
- min-width: 32px;
233
- text-align: right;
234
- font-variant-numeric: tabular-nums;
235
- `;
236
-
237
- // ==================== 语音播放器组件 ====================
238
-
239
- /** 格式化秒数为 m:ss */
240
- function formatDuration(seconds: number): string {
241
- if (!seconds || !isFinite(seconds)) return '0:00';
242
- const m = Math.floor(seconds / 60);
243
- const s = Math.floor(seconds % 60);
244
- return `${m}:${s.toString().padStart(2, '0')}`;
245
- }
246
-
247
- /** 生成固定的波形条高度 */
248
- const WAVE_BARS = Array.from({ length: 20 }, (_, i) => {
249
- // 用正弦函数生成自然的波形高度
250
- const base = Math.sin((i / 20) * Math.PI) * 12 + 4;
251
- return Math.max(4, Math.min(18, base + (Math.random() * 4 - 2)));
252
- });
253
-
254
- const AudioPlayer: React.FC<{ src: string; role: 'user' | 'bot' }> = ({ src, role }) => {
255
- const audioRef = useRef<HTMLAudioElement>(null);
256
- const progressRef = useRef<HTMLDivElement>(null);
257
- const [isPlaying, setIsPlaying] = useState(false);
258
- const [currentTime, setCurrentTime] = useState(0);
259
- const [duration, setDuration] = useState(0);
260
-
261
- const progress = duration > 0 ? (currentTime / duration) * 100 : 0;
262
-
263
- useEffect(() => {
264
- const audio = audioRef.current;
265
- if (!audio) return;
266
-
267
- const onLoadedMetadata = () => setDuration(audio.duration);
268
- const onTimeUpdate = () => setCurrentTime(audio.currentTime);
269
- const onEnded = () => {
270
- setIsPlaying(false);
271
- setCurrentTime(0);
272
- };
273
-
274
- audio.addEventListener('loadedmetadata', onLoadedMetadata);
275
- audio.addEventListener('timeupdate', onTimeUpdate);
276
- audio.addEventListener('ended', onEnded);
277
-
278
- return () => {
279
- audio.removeEventListener('loadedmetadata', onLoadedMetadata);
280
- audio.removeEventListener('timeupdate', onTimeUpdate);
281
- audio.removeEventListener('ended', onEnded);
282
- };
283
- }, []);
284
-
285
- const togglePlay = useCallback(() => {
286
- const audio = audioRef.current;
287
- if (!audio) return;
288
-
289
- if (isPlaying) {
290
- audio.pause();
291
- setIsPlaying(false);
292
- } else {
293
- audio.play().then(() => setIsPlaying(true)).catch(() => {});
294
- }
295
- }, [isPlaying]);
296
-
297
- const handleProgressClick = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
298
- const audio = audioRef.current;
299
- const bar = progressRef.current;
300
- if (!audio || !bar || !duration) return;
301
-
302
- const rect = bar.getBoundingClientRect();
303
- const ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
304
- audio.currentTime = ratio * duration;
305
- setCurrentTime(audio.currentTime);
306
- }, [duration]);
307
-
308
- return (
309
- <AudioCard $role={role}>
310
- <audio ref={audioRef} src={src} preload="metadata" />
311
- <PlayButton $role={role} onClick={togglePlay}>
312
- {isPlaying ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
313
- </PlayButton>
314
- <AudioBody>
315
- <WaveformContainer>
316
- {WAVE_BARS.map((h, i) => {
317
- const barProgress = (i / WAVE_BARS.length) * 100;
318
- const isActive = barProgress <= progress;
319
- return (
320
- <WaveBar
321
- key={i}
322
- $active={isActive}
323
- $playing={isPlaying}
324
- $delay={i * 0.05}
325
- style={!isPlaying || !isActive ? { height: `${h}px` } : undefined}
326
- />
327
- );
328
- })}
329
- </WaveformContainer>
330
- <ProgressBar ref={progressRef} onClick={handleProgressClick}>
331
- <ProgressFill $progress={progress} />
332
- </ProgressBar>
333
- </AudioBody>
334
- <AudioDuration>
335
- {isPlaying || currentTime > 0 ? formatDuration(currentTime) : formatDuration(duration)}
336
- </AudioDuration>
337
- </AudioCard>
338
- );
339
- };
340
-
341
126
  // ==================== 工具函数 ====================
342
127
 
343
128
  /** 根据文件名获取文件扩展名 */
@@ -387,19 +172,14 @@ export const MessageAttachments: React.FC<MessageAttachmentsProps> = ({
387
172
  role = 'user',
388
173
  images,
389
174
  files,
390
- audio,
391
175
  }) => {
392
176
  const hasImages = images && images.length > 0;
393
177
  const hasFiles = files && files.length > 0;
394
- const hasAudio = !!audio;
395
178
 
396
- if (!hasImages && !hasFiles && !hasAudio) return null;
179
+ if (!hasImages && !hasFiles) return null;
397
180
 
398
181
  return (
399
182
  <AttachmentsArea>
400
- {/* 语音播放器 */}
401
- {hasAudio && <AudioPlayer src={audio} role={role} />}
402
-
403
183
  {/* 图片列表 */}
404
184
  {hasImages && (
405
185
  <ImagesRow>
@@ -8,6 +8,7 @@ import React, { useEffect, useState, useCallback } from 'react';
8
8
  import styled from 'styled-components';
9
9
  import { Modal, Image, Space } from 'antd';
10
10
  import { MessageAttachments } from './MessageAttachments';
11
+ import { AudioPlayer } from './AudioPlayer';
11
12
  import { loadEchartsScript } from '@/utils/loadEcharts';
12
13
  import { DocReferences, DocReferenceItem } from './DocReferences';
13
14
  import { WebSearchPanel } from './WebSearchPanel';
@@ -343,6 +344,9 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
343
344
  const handleReferenceClick = onReferenceClick || defaultReferenceClick;
344
345
  const handleWebSearchClick = onWebSearchClick || defaultWebSearchClick;
345
346
 
347
+ // 纯语音消息:跳过 StyledBubble 包裹,直接渲染 AudioPlayer
348
+ const isAudioOnly = !!(audio && !content);
349
+
346
350
  return (
347
351
  <>
348
352
  <StyledContainer
@@ -350,9 +354,12 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
350
354
  className={`appflow-sdk-message-bubble ${className || ''}`}
351
355
  style={style}
352
356
  >
353
- <StyledBubble $role={role}>
354
- {/* 使用核心组件渲染内容(语音消息且无文本时跳过,避免显示加载动画) */}
355
- {!(audio && !content) && (
357
+ {isAudioOnly ? (
358
+ /* 纯语音消息:直接渲染播放器,无气泡包裹 */
359
+ <AudioPlayer src={audio} role={role} />
360
+ ) : (
361
+ <StyledBubble $role={role}>
362
+ {/* 使用核心组件渲染内容 */}
356
363
  <BubbleContent
357
364
  content={content}
358
365
  status={status}
@@ -385,18 +392,17 @@ export const MessageBubble: React.FC<MessageBubbleProps> = ({
385
392
  </ReferencesContainer>
386
393
  )}
387
394
  </BubbleContent>
388
- )}
389
395
 
390
- {/* 附件展示区域:语音、图片和文件 */}
391
- <MessageAttachments
392
- role={role}
393
- images={images}
394
- files={files}
395
- audio={audio}
396
- />
396
+ {/* 附件展示区域:图片和文件 */}
397
+ <MessageAttachments
398
+ role={role}
399
+ images={images}
400
+ files={files}
401
+ />
397
402
 
398
- {contextHolder}
399
- </StyledBubble>
403
+ {contextHolder}
404
+ </StyledBubble>
405
+ )}
400
406
  </StyledContainer>
401
407
 
402
408
  {/* 默认的网页搜索抽屉(仅在用户未传入onWebSearchClick时使用) */}
@@ -81,6 +81,7 @@ export interface HistoryMessage {
81
81
  sessionId?: string;
82
82
  images?: string[];
83
83
  files?: { name: string; url: string }[];
84
+ audio?: string;
84
85
  }
85
86
 
86
87
  export interface ChatSession {
@@ -695,6 +696,23 @@ class ChatService {
695
696
  // 处理消息列表
696
697
  if (Array.isArray(data)) {
697
698
  for (const item of data) {
699
+ // 注意:接口返回数据是倒序的(最新在前),最终会 reverse()
700
+ // 因此这里先推入 AI 回复,再推入用户消息,reverse() 后顺序变为 user → assistant
701
+
702
+ // AI回复 - 在 assistant 数组中
703
+ if (item.assistant && Array.isArray(item.assistant) && item.assistant.length > 0) {
704
+ // 取第一个assistant回复
705
+ const assistantMsg = item.assistant[0];
706
+ messages.push({
707
+ id: assistantMsg.messageId || `assistant_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
708
+ role: 'assistant',
709
+ content: assistantMsg.content || '',
710
+ messageType: assistantMsg.messageType || 'text',
711
+ gmtCreate: item.gmtCreate,
712
+ sessionId: assistantMsg.sessionId || item.sessionId,
713
+ });
714
+ }
715
+
698
716
  // 用户消息 - 在 message 字段中
699
717
  if (item.message !== undefined && item.message !== null) {
700
718
  const messageType = item.messageType || 'text';
@@ -729,29 +747,19 @@ class ChatService {
729
747
  content = item.message;
730
748
  }
731
749
 
750
+ // 语音消息:messageType === 'audio' 时,message 是音频文件 URL
751
+ const isAudioMessage = messageType === 'audio';
752
+
732
753
  messages.push({
733
754
  id: item.id || `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
734
755
  role: 'user',
735
- content,
756
+ content: isAudioMessage ? '' : content,
736
757
  messageType,
737
758
  gmtCreate: item.gmtCreate,
738
759
  sessionId: item.sessionId,
739
760
  images,
740
761
  files,
741
- });
742
- }
743
-
744
- // AI回复 - 在 assistant 数组中
745
- if (item.assistant && Array.isArray(item.assistant) && item.assistant.length > 0) {
746
- // 取第一个assistant回复
747
- const assistantMsg = item.assistant[0];
748
- messages.push({
749
- id: assistantMsg.messageId || `assistant_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
750
- role: 'assistant',
751
- content: assistantMsg.content || '',
752
- messageType: assistantMsg.messageType || 'text',
753
- gmtCreate: item.gmtCreate,
754
- sessionId: assistantMsg.sessionId || item.sessionId,
762
+ audio: isAudioMessage ? content : undefined,
755
763
  });
756
764
  }
757
765
  }