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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yt-chat-components",
3
- "version": "1.1.8",
3
+ "version": "1.2.0",
4
4
  "main": "build/static/js/bundle.min.js",
5
5
  "module": "build/static/js/bundle.min.js",
6
6
  "types": "build/static/js/index.d.ts",
@@ -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=test`);
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 (isListening) return; // 正在听对方说话,不处理
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
- wave && wave.input(buffers[buffers.length - 1], powerLevel, bufferSampleRate);
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
- wave = Recorder.WaveView({elem: ".recwave"});
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
- } else if (!isSpeakingNow && isSpeakingRef.current) {
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
- } else if (isSpeakingNow && isSpeakingRef.current) {
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
- <div style={{width: 120, height: 50, position: 'relative'}}>
511
- {
512
- <div className='recwave' style={{width: 120, height: 50, display: workStatus === '正在聆听' ? 'block' : 'none'}}></div>
513
- }
514
- </div>
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){