yt-chat-components 1.1.8 → 1.2.0
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/package.json +1 -1
- package/src/YtChatView/chatWidget/chatWindow/callInterface/index.tsx +45 -44
- package/src/YtChatView/chatWidget/chatWindow/index.tsx +4 -1
- package/src/YtChatView/previewDialog/index.jsx +1 -1
- package/src/assets/aicenter/ai_call_thinking.png +0 -0
- package/src/assets/aicenter/ai_call_working.gif +0 -0
package/package.json
CHANGED
|
@@ -6,7 +6,10 @@ import { ChatMessageType } from '../../chatWindow/types/chatWidget';
|
|
|
6
6
|
import Recorder from 'recorder-core'
|
|
7
7
|
import 'recorder-core/src/engine/mp3'
|
|
8
8
|
import 'recorder-core/src/engine/mp3-engine'
|
|
9
|
-
import 'recorder-core/src/extensions/waveview'
|
|
9
|
+
// import 'recorder-core/src/extensions/waveview'
|
|
10
|
+
import ai_call_thinking from '../../../../assets/aicenter/ai_call_thinking.png'
|
|
11
|
+
import ai_call_working from '../../../../assets/aicenter/ai_call_working.gif'
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
interface CallInterfaceProps {
|
|
12
15
|
onHangup: () => void;
|
|
@@ -17,6 +20,9 @@ interface CallInterfaceProps {
|
|
|
17
20
|
sessionId: React.MutableRefObject<string>;
|
|
18
21
|
hostUrl: string;
|
|
19
22
|
addMessage: (message: ChatMessageType) => void;
|
|
23
|
+
userInfo: {
|
|
24
|
+
code: string;
|
|
25
|
+
};
|
|
20
26
|
}
|
|
21
27
|
|
|
22
28
|
const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
@@ -27,7 +33,8 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
27
33
|
flowId,
|
|
28
34
|
sessionId,
|
|
29
35
|
hostUrl,
|
|
30
|
-
addMessage
|
|
36
|
+
addMessage,
|
|
37
|
+
userInfo,
|
|
31
38
|
}) => {
|
|
32
39
|
const [callDuration, setCallDuration] = useState(0);
|
|
33
40
|
const [callStatus, setCallStatus] = useState<'connecting' | 'connected'>('connecting');
|
|
@@ -35,7 +42,6 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
35
42
|
|
|
36
43
|
// 添加VAD相关状态
|
|
37
44
|
const isSpeakingRef = useRef<boolean>(false);
|
|
38
|
-
const [isListening, setIsListening] = useState(false);
|
|
39
45
|
const SILENCE_THRESHOLD = 35; // 静音阈值,可根据环境调整
|
|
40
46
|
const SPEECH_DELAY = 1500; // 停止说话多久后认为一段话结束(毫秒)
|
|
41
47
|
|
|
@@ -46,7 +52,7 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
46
52
|
const remoteStreamRef = useRef<MediaStream | null>(null);
|
|
47
53
|
const remoteAudioRef = useRef<HTMLAudioElement>(null);
|
|
48
54
|
let responseAudio: any = null;
|
|
49
|
-
let wave: any = null;
|
|
55
|
+
// let wave: any = null;
|
|
50
56
|
const wsRef = useRef<WebSocket | null>(null);
|
|
51
57
|
|
|
52
58
|
// VAD相关引用
|
|
@@ -54,7 +60,8 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
54
60
|
const speechTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
55
61
|
const vadProcessorRef = useRef<ScriptProcessorNode | null>(null);
|
|
56
62
|
const audioContextRef = useRef<AudioContext | null>(null);
|
|
57
|
-
|
|
63
|
+
const [isSpeakingNow,setIsSpeakingNow] = useState(false);
|
|
64
|
+
|
|
58
65
|
|
|
59
66
|
// 添加统一的WebSocket消息发送方法
|
|
60
67
|
const sendWebSocketMessage = (type: string, data: any) => {
|
|
@@ -66,7 +73,6 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
66
73
|
};
|
|
67
74
|
|
|
68
75
|
const handleAudioData = (audioData: string) => {
|
|
69
|
-
setIsListening(true);
|
|
70
76
|
setWorkStatus('正在回复');
|
|
71
77
|
|
|
72
78
|
// 将base64音频转换为Blob
|
|
@@ -83,13 +89,6 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
83
89
|
const audio = new Audio(audioUrl);
|
|
84
90
|
responseAudio = audio;
|
|
85
91
|
|
|
86
|
-
// 监听播放结束事件
|
|
87
|
-
audio.onended = () => {
|
|
88
|
-
URL.revokeObjectURL(audioUrl);
|
|
89
|
-
setIsListening(false);
|
|
90
|
-
setWorkStatus('正在聆听');
|
|
91
|
-
};
|
|
92
|
-
|
|
93
92
|
// 监听用户打断
|
|
94
93
|
audio.onplay = () => {
|
|
95
94
|
// 设置打断检测
|
|
@@ -99,9 +98,8 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
99
98
|
console.log('用户打断播放');
|
|
100
99
|
audio.pause();
|
|
101
100
|
URL.revokeObjectURL(audioUrl);
|
|
102
|
-
setIsListening(false);
|
|
103
|
-
setWorkStatus('正在聆听');
|
|
104
101
|
clearInterval(interruptionCheck);
|
|
102
|
+
setWorkStatus('正在聆听');
|
|
105
103
|
}
|
|
106
104
|
}, 100);
|
|
107
105
|
|
|
@@ -109,7 +107,6 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
109
107
|
audio.onended = () => {
|
|
110
108
|
clearInterval(interruptionCheck);
|
|
111
109
|
URL.revokeObjectURL(audioUrl);
|
|
112
|
-
setIsListening(false);
|
|
113
110
|
setWorkStatus('正在聆听');
|
|
114
111
|
};
|
|
115
112
|
};
|
|
@@ -117,7 +114,6 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
117
114
|
// 开始播放
|
|
118
115
|
audio.play().catch(err => {
|
|
119
116
|
console.error('播放音频失败:', err);
|
|
120
|
-
setIsListening(false);
|
|
121
117
|
setWorkStatus('正在聆听');
|
|
122
118
|
});
|
|
123
119
|
};
|
|
@@ -135,10 +131,11 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
135
131
|
]
|
|
136
132
|
});
|
|
137
133
|
peerConnectionRef.current = peerConnection;
|
|
138
|
-
|
|
134
|
+
const {code} = userInfo;
|
|
135
|
+
const opeartorId = code || sessionId;
|
|
139
136
|
// 创建WebSocket连接
|
|
140
137
|
console.log('开始连接WebSocket', flowId, sessionId, api_key);
|
|
141
|
-
const ws = new WebSocket(`${hostUrl.replace('http', 'ws')}/api/v1/ws/webrtc?flow_id=${flowId}&session_id=${sessionId}&api_key=${api_key}&operator_id
|
|
138
|
+
const ws = new WebSocket(`${hostUrl.replace('http', 'ws')}/api/v1/ws/webrtc?flow_id=${flowId}&session_id=${sessionId}&api_key=${api_key}&operator_id=${opeartorId}`);
|
|
142
139
|
wsRef.current = ws;
|
|
143
140
|
|
|
144
141
|
// 添加连接超时处理
|
|
@@ -202,7 +199,7 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
202
199
|
vadProcessorRef.current = vadProcessor;
|
|
203
200
|
|
|
204
201
|
vadProcessor.onaudioprocess = (e) => {
|
|
205
|
-
if (
|
|
202
|
+
if (workStatus === '正在回复' || workStatus === '正在思考') return; // 正在听对方说话 or 思考,不处理
|
|
206
203
|
|
|
207
204
|
analyser.getByteFrequencyData(dataArray);
|
|
208
205
|
|
|
@@ -215,12 +212,13 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
215
212
|
|
|
216
213
|
// 检测是否有语音
|
|
217
214
|
const isSpeakingNow = average > SILENCE_THRESHOLD;
|
|
218
|
-
|
|
215
|
+
|
|
219
216
|
if (isSpeakingNow && !isSpeakingRef.current) {
|
|
217
|
+
setIsSpeakingNow(true)
|
|
220
218
|
// 开始说话
|
|
221
219
|
console.log('开始说话', isSpeakingRef.current);
|
|
222
220
|
isSpeakingRef.current = true;
|
|
223
|
-
|
|
221
|
+
|
|
224
222
|
// 开始录制
|
|
225
223
|
if (!recorderRef.current && localStreamRef.current) {
|
|
226
224
|
console.log('准备录制');
|
|
@@ -236,26 +234,27 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
236
234
|
//可实时上传(发送)数据,配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm上传,或使用mock方法将新数据连续的转码成其他格式上传,可以参考文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
|
|
237
235
|
|
|
238
236
|
//可实时绘制波形(extensions目录内的waveview.js、wavesurfer.view.js、frequency.histogram.view.js插件功能)
|
|
239
|
-
if(workStatus === '正在聆听'){
|
|
240
|
-
|
|
241
|
-
}
|
|
237
|
+
// if(workStatus === '正在聆听'){
|
|
238
|
+
// wave && wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
|
|
239
|
+
// }
|
|
242
240
|
}
|
|
243
241
|
});
|
|
244
242
|
recorderRef.current = recorder;
|
|
245
243
|
recorder.open(() => {
|
|
246
|
-
if(Recorder.WaveView){
|
|
247
|
-
|
|
248
|
-
}
|
|
244
|
+
// if(Recorder.WaveView){
|
|
245
|
+
// wave = Recorder.WaveView({elem: ".recwave"});
|
|
246
|
+
// }
|
|
249
247
|
});
|
|
250
248
|
recorder.start();
|
|
251
249
|
}
|
|
252
|
-
|
|
250
|
+
|
|
253
251
|
// 清除之前的超时
|
|
254
252
|
if (speechTimeoutRef.current) {
|
|
255
253
|
clearTimeout(speechTimeoutRef.current);
|
|
256
254
|
speechTimeoutRef.current = null;
|
|
257
255
|
}
|
|
258
|
-
}
|
|
256
|
+
}
|
|
257
|
+
else if (!isSpeakingNow && isSpeakingRef.current) {
|
|
259
258
|
console.log('准备停止说话');
|
|
260
259
|
// 可能停止说话,设置超时
|
|
261
260
|
if (!speechTimeoutRef.current) {
|
|
@@ -263,7 +262,7 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
263
262
|
// 确认停止说话
|
|
264
263
|
console.log('停止说话');
|
|
265
264
|
isSpeakingRef.current = false;
|
|
266
|
-
|
|
265
|
+
|
|
267
266
|
// 停止录制
|
|
268
267
|
if (recorderRef.current) {
|
|
269
268
|
recorderRef.current.stop((blob: Blob, duration:any, mime:any) => {
|
|
@@ -281,8 +280,7 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
281
280
|
audioData: base64Audio.split(',')[1], // 移除data URL前缀
|
|
282
281
|
userMessage: true,
|
|
283
282
|
audioFormat:"mp3",
|
|
284
|
-
sampleRate:16000
|
|
285
|
-
voice:"longxiang"
|
|
283
|
+
sampleRate:16000
|
|
286
284
|
});
|
|
287
285
|
console.log('发送完毕');
|
|
288
286
|
|
|
@@ -293,26 +291,26 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
293
291
|
// timestamp: new Date().toISOString()
|
|
294
292
|
// });
|
|
295
293
|
|
|
296
|
-
// 设置为正在听模式
|
|
297
|
-
setIsListening(true);
|
|
298
294
|
setWorkStatus('正在思考');
|
|
295
|
+
setIsSpeakingNow(false)
|
|
299
296
|
}
|
|
300
297
|
};
|
|
301
298
|
|
|
302
|
-
wave = null
|
|
299
|
+
// wave = null
|
|
303
300
|
recorderRef.current = null;
|
|
304
301
|
},(msg: any)=> {
|
|
305
302
|
console.log("录音失败:"+msg);
|
|
306
|
-
wave = null
|
|
303
|
+
// wave = null
|
|
307
304
|
recorderRef.current.close();//可以通过stop方法的第3个参数来自动调用close
|
|
308
305
|
recorderRef.current=null;
|
|
309
306
|
});
|
|
310
307
|
}
|
|
311
|
-
|
|
308
|
+
|
|
312
309
|
speechTimeoutRef.current = null;
|
|
313
310
|
}, SPEECH_DELAY);
|
|
314
311
|
}
|
|
315
|
-
}
|
|
312
|
+
}
|
|
313
|
+
else if (isSpeakingNow && isSpeakingRef.current) {
|
|
316
314
|
console.log('继续说话');
|
|
317
315
|
// 继续说话,重置超时
|
|
318
316
|
if (speechTimeoutRef.current) {
|
|
@@ -507,11 +505,14 @@ const CallInterface: React.FC<CallInterfaceProps> = ({
|
|
|
507
505
|
{
|
|
508
506
|
callStatus === 'connected' &&
|
|
509
507
|
<div style={{display: 'flex', flexDirection:'column', justifyContent: 'center', alignItems: 'center'}}>
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
508
|
+
{/*<div style={{width: 120, height: 50, position: 'relative'}}>*/}
|
|
509
|
+
{/* {*/}
|
|
510
|
+
{/* <div className='recwave' style={{width: 120, height: 50, display: workStatus === '正在聆听' ? 'block' : 'none'}}></div>*/}
|
|
511
|
+
{/* }*/}
|
|
512
|
+
{/*</div>*/}
|
|
513
|
+
<div className='recwave' style={{width: 120, height: 65}}>{
|
|
514
|
+
(isSpeakingNow && workStatus === '正在聆听')|| workStatus === '正在回复' ? <img src={ai_call_working}/>:<img src={ai_call_thinking}/>
|
|
515
|
+
}</div>
|
|
515
516
|
<p className="call-status">
|
|
516
517
|
{workStatus}
|
|
517
518
|
</p>
|
|
@@ -452,6 +452,7 @@ export default function ChatWindow({
|
|
|
452
452
|
});
|
|
453
453
|
setDropDownList(undefined);
|
|
454
454
|
}
|
|
455
|
+
inputRef.current.value=''
|
|
455
456
|
}
|
|
456
457
|
|
|
457
458
|
// 处理打电话按钮点击
|
|
@@ -1011,7 +1012,7 @@ export default function ChatWindow({
|
|
|
1011
1012
|
<div className="w_inputBox" style={{height: inputContainerHeight}}>
|
|
1012
1013
|
<textarea
|
|
1013
1014
|
onFocus={()=>setInputContainerHeight('120px')}
|
|
1014
|
-
onBlur={()=>setInputContainerHeight('50px')}
|
|
1015
|
+
onBlur={() => setTimeout(() => setInputContainerHeight('50px'), 200)}
|
|
1015
1016
|
// value={inputValue}
|
|
1016
1017
|
onChange={(e) => setValue(e.target.value)}
|
|
1017
1018
|
onKeyDown={(e) => {
|
|
@@ -1072,6 +1073,7 @@ export default function ChatWindow({
|
|
|
1072
1073
|
style={{ ...(sendingMessage ? { cursor: 'pointer' } : {}), padding: '0 13px', background:'transparent', display:'flex', alignItems:'center', justifyContent:'center' }}
|
|
1073
1074
|
onClick={() => {
|
|
1074
1075
|
if(sendingMessage){
|
|
1076
|
+
// 如果正在发送消息,此时点击则为取消发送
|
|
1075
1077
|
const output = {
|
|
1076
1078
|
message:nowAIContent,
|
|
1077
1079
|
type:'text'
|
|
@@ -1114,6 +1116,7 @@ export default function ChatWindow({
|
|
|
1114
1116
|
sessionId={sessionId}
|
|
1115
1117
|
hostUrl={hostUrl}
|
|
1116
1118
|
onMessage={addMessage}
|
|
1119
|
+
userInfo={userInfo}
|
|
1117
1120
|
/>
|
|
1118
1121
|
:
|
|
1119
1122
|
<div className="cl-middle-container">
|
|
@@ -350,7 +350,7 @@ export class ToolDialog extends React.Component {
|
|
|
350
350
|
|
|
351
351
|
// 获取历史对话列表
|
|
352
352
|
getHistoryList = (sceneData, isRefreshWords) => {
|
|
353
|
-
const { sceneId, appId: flowId, isShowSideLeft } = this.props;
|
|
353
|
+
const { sceneId, appId: flowId, isShowSideLeft = true } = this.props;
|
|
354
354
|
if(isEmpty(sceneId)){
|
|
355
355
|
// 单独组件情况
|
|
356
356
|
if(isRefreshWords){
|
|
Binary file
|
|
Binary file
|