yt-chat-components 1.5.7 → 1.5.9

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.
@@ -18,12 +18,13 @@ import typeRPubPng from '../../../../assets/aicenter/type-rpub.png';
18
18
  import playPng from '../../../../assets/aicenter/play.png';
19
19
  import playRunGif from '../../../../assets/aicenter/play-run.gif';
20
20
  import copyPng from '../../../../assets/aicenter/copy.png';
21
- import {CheckCircleOutlined, EllipsisOutlined, InfoCircleOutlined, ClockCircleOutlined} from '@ant-design/icons';
21
+ import {CheckCircleOutlined, EllipsisOutlined, InfoCircleOutlined, ClockCircleOutlined,EyeOutlined} from '@ant-design/icons';
22
22
  import ChatMessagePlaceholder, {ChatMessagePlaceholderInThought} from "../chatPlaceholder";
23
23
  import {UrlCard} from "./urlCard"
24
24
  import {CustomThoughtChainItem, CustomThoughtChainProps} from "./CustomThoughtChain";
25
25
  import CustomThoughtChain from "./CustomThoughtChain";
26
26
  import {isFunction} from "lodash";
27
+ import {AudioPlayer} from "../audioPlayer/AudioPlayer";
27
28
  let speechSynth = window.speechSynthesis;
28
29
  let utterance = null;
29
30
 
@@ -63,6 +64,7 @@ export default function ChatMessage({
63
64
  key,
64
65
  isShowReadIcon,
65
66
  renderInitNode,
67
+ voice
66
68
  }: ChatMessageType) {
67
69
  const parseFileName = (
68
70
  text: string,
@@ -135,24 +137,33 @@ export default function ChatMessage({
135
137
  }
136
138
  };
137
139
 
138
- /**
139
- * 播放文字
140
- * @param text 文字
141
- */
142
140
  const playVoice = (text)=>{
143
141
  if (isPlay) {
144
- speechSynth.cancel();
145
142
  setIsPlay(false)
146
- }else{
147
- if (text) {
148
- utterance = new SpeechSynthesisUtterance(text);
149
- const voices = speechSynth.getVoices();
150
- utterance.voice = voices[61];
151
- utterance.rate = 1;
152
- utterance.pitch = 1;
153
- speechSynth.speak(utterance);
154
- setIsPlay(true)
143
+ if (window.messageAudioPlayer){
144
+ window.messageAudioPlayer.stop();
145
+ }
146
+ return;
147
+ }
148
+
149
+ if (text) {
150
+ if (window.messageAudioPlayer){
151
+ window.messageAudioPlayer.stop();
152
+ window.messageAudioPlayer = null;
155
153
  }
154
+ console.log('voice = ' + voice)
155
+ window.messageAudioPlayer = new AudioPlayer(voice);
156
+ window.messageAudioPlayer.onReady = () => {
157
+ console.log('已连接到语音合成服务');
158
+ window.messageAudioPlayer.sendRunSynthesis(text)// 尽快发送 StartSynthesis
159
+ };
160
+ window.messageAudioPlayer.onComplete = () => {
161
+ setIsPlay(false);
162
+ };
163
+ window.messageAudioPlayer.onError = (err) => {
164
+ // alert('发生错误:' + err);
165
+ };
166
+ setIsPlay(true)
156
167
  }
157
168
  }
158
169
 
@@ -197,7 +208,7 @@ export default function ChatMessage({
197
208
  src={`${host_url}/api/v1/files/images/${item}`}
198
209
  alt={item}
199
210
  preview={{
200
- mask: <span className="custom-mask"></span>,
211
+ mask: <div><EyeOutlined style={{ marginRight: '4px' }}/>预览</div>
201
212
  }}
202
213
  />
203
214
  )}
@@ -343,6 +354,9 @@ export default function ChatMessage({
343
354
  return <Image
344
355
  height={'auto'}
345
356
  width={'100%'}
357
+ preview={{
358
+ mask: <div><EyeOutlined style={{ marginRight: '4px' }}/>预览</div>
359
+ }}
346
360
  src={url}
347
361
  alt={''}
348
362
  fallback="data:image/png;base64,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"
@@ -371,6 +385,9 @@ export default function ChatMessage({
371
385
  height={'auto'}
372
386
  width={'100%'}
373
387
  src={src}
388
+ preview={{
389
+ mask: <div><EyeOutlined style={{ marginRight: '4px' }}/>预览</div>
390
+ }}
374
391
  alt={''}
375
392
  fallback="data:image/png;base64,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"
376
393
  />
@@ -29,7 +29,9 @@ import {isEmpty, isFunction} from 'lodash';
29
29
  import btn_answer from '../../../assets/aicenter/btn_answer.png';
30
30
  import {MethodContext} from "../../previewDialogV2";
31
31
  import TabSelector from "../../components/TabSelector";
32
-
32
+ import {AudioRecorder} from "./audioRecorder";
33
+ import InputMobile from "./inputMobile/inputMobile";
34
+ import {EyeOutlined} from "@ant-design/icons";
33
35
 
34
36
  let mediaRecorder = null; // 语音对象,用于录音
35
37
  let recognition = null; // 语音识别对象
@@ -92,6 +94,9 @@ export default function ChatWindow({
92
94
  isTagsHidden,
93
95
  isEnableForV1,
94
96
  questions,
97
+ voice,
98
+ onSmartRobotClick,
99
+ isShowMobileInputArea,
95
100
  }: {
96
101
  is_enable_call:boolean;
97
102
  tags: [];
@@ -143,12 +148,17 @@ export default function ChatWindow({
143
148
  isTagsHidden?: boolean;
144
149
  isEnableForV1?: boolean;
145
150
  questions?: any[];
151
+ voice?: string;
152
+ onSmartRobotClick?: Function;
153
+ isShowMobileInputArea?: boolean;
146
154
  }) {
147
155
  const ref = useRef<HTMLDivElement>(null);
148
156
  const lastMessage = useRef<HTMLDivElement>(null);
149
157
  const [showCallInterface, setShowCallInterface] = useState(false); // 添加通话界面状态
150
158
  const [aiStatus, setAiStatus] = useState(null);
159
+ const inputMobileRef = useRef(null);
151
160
  const inputRef = useRef<HTMLInputElement>(null); /* User input Ref */
161
+ const audioRecorder = useRef(null);
152
162
  /* Initial listener for loss of focus that refocuses User input after a small delay */
153
163
  const [nowAIContentList, setNowAIContentList] = useState<Array<MessageItem>>([]);
154
164
  const [receivingMessage, setReceivingMessage] = useState(false);
@@ -164,7 +174,7 @@ export default function ChatWindow({
164
174
  const isStream = true;//是否流式输出(手动开关)
165
175
  const [recordState, setRecordState] = useState(false); // 录音状态。true为正在录音,false为停止录音
166
176
  const [tagList, setTagList] = useState([]); // 问题标签列表
167
- const {isTitleSideIcon, logoWidth, agentUrl, stopMessageUrl, sendMessageUrl, speakUrl} = baseConfig;
177
+ const {isTitleSideIcon, logoWidth, agentUrl, stopMessageUrl, sendMessageUrl, speakUrl, callUrl} = baseConfig;
168
178
  const [inputContainerHeight, setInputContainerHeight] = useState('120px')
169
179
  let content_id: string = null
170
180
  let content_ns: string = null
@@ -880,6 +890,39 @@ export default function ChatWindow({
880
890
  }
881
891
  };
882
892
 
893
+ const stopRecord = () =>{
894
+ audioRecorder.current.stopRecorder();
895
+ audioRecorder.current = undefined;
896
+ setRecordState(false)
897
+ }
898
+ const startConnectAndRecord = () => {
899
+ if (recordState){
900
+ stopRecord()
901
+ }else {
902
+ setRecordState(true)
903
+ audioRecorder.current = new AudioRecorder();
904
+ audioRecorder.current.startRecorder();
905
+ audioRecorder.current.onTextUpdate = (text: string) => {
906
+ setValue(text);
907
+ if (inputMobileRef.current){
908
+ inputMobileRef.current.handleSetInputText(text);
909
+ }
910
+ if (inputRef.current) {
911
+ inputRef.current.value = text;
912
+ }
913
+ };
914
+ audioRecorder.current.onRecordError = (error: string) => {
915
+ stopRecord()
916
+ };
917
+ audioRecorder.current.onRecordTimeout = () => {
918
+ stopRecord()
919
+ if (inputMobileRef.current){
920
+ inputMobileRef.current.stopRecording();
921
+ }
922
+ };
923
+ }
924
+ };
925
+
883
926
  /**
884
927
  * 输出消息时,滚动到底部
885
928
  */
@@ -968,9 +1011,20 @@ export default function ChatWindow({
968
1011
  </div>
969
1012
  }
970
1013
  <TabSelector
1014
+ isMobile={isMobile}
1015
+ isSimple={isMobile}
1016
+ isShowByPages={true}
1017
+ keyShowSize={4}
1018
+ agentName={window_title}
1019
+ agentUrl={agentUrl}
971
1020
  welcomeWords={`Hi,欢迎使用${window_title},您可以这样问我:`}
972
1021
  handleRowClick={(word) => {
973
- handleSendMessage(word)
1022
+ if(!receivingMessageRef.current){
1023
+ handleSendMessage(word)
1024
+ }else{
1025
+ messageTip.destroy();
1026
+ messageTip.info("请等待回复结束后再发送")
1027
+ }
974
1028
  }}
975
1029
  dataList={questions}
976
1030
  />
@@ -1150,7 +1204,40 @@ export default function ChatWindow({
1150
1204
  return <></>
1151
1205
  }
1152
1206
 
1207
+ const renderInputAreaMobile = () => {
1208
+ return (
1209
+ <InputMobile
1210
+ ref={inputMobileRef}
1211
+ isShowVoiceButton={isShowVoiceButton}
1212
+ inputRef={inputRef}
1213
+ disabled={receivingMessage}
1214
+ placeholder={
1215
+ receivingMessage
1216
+ ? placeholder_sending || '思考中...'
1217
+ : placeholder || '请输入您的问题...'
1218
+ }
1219
+ onValueChange={ value => setValue(value)}
1220
+ onSendClick={() => {
1221
+ if (receivingMessage) {
1222
+ addMessage({
1223
+ messageItemList: [...nowAIContentList],
1224
+ isSend: false,
1225
+ });
1226
+ abortControllerRef.current.abort('disconnect');
1227
+ abortControllerRef.current = new AbortController();
1228
+ } else {
1229
+ handleSendMessage()
1230
+ }
1231
+ }}
1232
+ onStartRecordClick={startConnectAndRecord}
1233
+ onStopRecordClick={stopRecord}
1234
+ onSmartRobotClick={onSmartRobotClick}
1235
+ />
1236
+ )
1237
+ }
1238
+
1153
1239
  const renderInputArea = () => {
1240
+ if (isShowMobileInputArea) renderInputAreaMobile();
1154
1241
  let isRender = messages.length === 0
1155
1242
  if (messages.length > 0) {
1156
1243
  const {messageItemList} = messages[messages.length - 1]
@@ -1182,7 +1269,7 @@ export default function ChatWindow({
1182
1269
  src={item.fileUrl}
1183
1270
  className="w_upImg"
1184
1271
  preview={{
1185
- mask: <span className="custom-mask"></span>,
1272
+ mask: <div><EyeOutlined style={{ marginRight: '4px' }}/>预览</div>
1186
1273
  }}
1187
1274
  />
1188
1275
  )
@@ -1260,7 +1347,7 @@ export default function ChatWindow({
1260
1347
  <div
1261
1348
  className="w_send_voice_box"
1262
1349
  style={receivingMessage ? {cursor: 'not-allowed'} : {}}
1263
- onClick={startRecord}
1350
+ onClick={startConnectAndRecord}
1264
1351
  >
1265
1352
  <img src={recordState ? soundWavePng : (speakUrl || luyinPng)} style={{width: 35}}
1266
1353
  className={recordState ? "w_recordIng" : ''}></img>
@@ -1275,7 +1362,7 @@ export default function ChatWindow({
1275
1362
  style={receivingMessage ? {cursor: 'not-allowed'} : {}}
1276
1363
  onClick={handleCallButtonClick}
1277
1364
  >
1278
- <img src={phonePng} style={{width: 23}}></img>
1365
+ <img src={(callUrl || phonePng)} style={{width: 35}}></img>
1279
1366
  </div>
1280
1367
  </Tooltip>
1281
1368
  }
@@ -1296,7 +1383,7 @@ export default function ChatWindow({
1296
1383
  style={{
1297
1384
  ...(receivingMessage ? {cursor: 'pointer'} : {}),
1298
1385
  height:'100%',
1299
- padding: '0 13px 16px',
1386
+ padding: '0 13px 16px 0',
1300
1387
  background: 'transparent',
1301
1388
  display: 'flex',
1302
1389
  alignItems: 'end',
@@ -1386,6 +1473,7 @@ export default function ChatWindow({
1386
1473
  </>
1387
1474
  )
1388
1475
  }
1476
+
1389
1477
  return (
1390
1478
  <div
1391
1479
  style={{...chat_window_style, width: width, height: "100%"}}
@@ -1458,6 +1546,7 @@ export default function ChatWindow({
1458
1546
  handleSendMessage={handleSendMessage}
1459
1547
  isShowReadIcon={isShowReadIcon}
1460
1548
  renderInitNode={message.renderInitNode}
1549
+ voice={voice}
1461
1550
  />
1462
1551
  ))
1463
1552
  }
@@ -1476,6 +1565,7 @@ export default function ChatWindow({
1476
1565
  isSend={false}
1477
1566
  handleSendMessage={handleSendMessage}
1478
1567
  isShowReadIcon={isShowReadIcon}
1568
+ voice={voice}
1479
1569
  /> : <ChatMessagePlaceholder bot_message_style={bot_message_style} aiStatus={aiStatus}/>
1480
1570
  )
1481
1571
  }
@@ -0,0 +1,134 @@
1
+ .m_tool_box {
2
+ position: relative;
3
+ width: 100%;
4
+ height: fit-content;
5
+ display: flex;
6
+ background-color: white;
7
+
8
+ .m_avatar {
9
+ position: absolute;
10
+ left: 10px;
11
+ bottom: 60px;
12
+ /* 头像容器样式 */
13
+ img{
14
+ z-index: 10;
15
+ position: relative;
16
+ }
17
+ }
18
+
19
+ .m_input_box {
20
+ /* 输入框整体容器样式 */
21
+ display: flex;
22
+ align-items: center;
23
+ width: 100%;
24
+ padding: 8px 8px;
25
+ background-color: #f9f9f9;
26
+
27
+ /* 在移动设备上优化触摸体验 */
28
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
29
+ }
30
+
31
+ .m_voiceToggle {
32
+ /* 语音/键盘切换按钮样式 */
33
+ transition: background-color 0.3s ease;
34
+ }
35
+
36
+ .m_center_box {
37
+ margin-left: 8px;
38
+ /* 中间输入区域容器 */
39
+ flex: 1;
40
+ position: relative;
41
+ /* 优化移动端点击区域 */
42
+ -webkit-user-select: auto;
43
+ user-select: auto;
44
+ }
45
+
46
+ .m_clickToTalk {
47
+ /* 点击说话样式 */
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: center;
51
+ height: 40px;
52
+ border-radius: 8px;
53
+ background-color: #f0f0f0;
54
+ font-size: 14px;
55
+ color: #666;
56
+ }
57
+
58
+ .m_inputBox-textarea {
59
+ /* 输入框样式 */
60
+ width: 100%;
61
+ font-size: 14px;
62
+ line-height: 20px;
63
+ border: none;
64
+ resize: none;
65
+ overflow-y: hidden;
66
+ background-color: transparent;
67
+
68
+ /* 设置输入法中文输入时的兼容样式 */
69
+ -webkit-box-sizing: border-box;
70
+ -moz-box-sizing: border-box;
71
+ box-sizing: border-box;
72
+
73
+ /* 获得焦点时的样式 */
74
+
75
+ &:focus {
76
+ outline: none;
77
+ }
78
+ }
79
+
80
+ .m_sendButton {
81
+ /* 发送按钮样式 */
82
+ padding: 0px 14px;
83
+ height: 34px;
84
+ display: flex;
85
+ align-items: center;
86
+ justify-content: center;
87
+ margin-left: 10px;
88
+ border-radius: 8px;
89
+ background-color: #4a90e2;
90
+ transition: background-color 0.3s ease;
91
+
92
+ &:active {
93
+ background-color: #3a80d2;
94
+ }
95
+ }
96
+
97
+ .m_sendButton {
98
+ /* 发送按钮样式 */
99
+ padding: 0px 14px;
100
+ height: 34px;
101
+ display: flex;
102
+ align-items: center;
103
+ justify-content: center;
104
+ margin-left: 10px;
105
+ border-radius: 8px;
106
+ background-color: #4a90e2;
107
+ transition: background-color 0.3s ease;
108
+
109
+ &:active {
110
+ background-color: #3a80d2;
111
+ }
112
+ }
113
+
114
+ /* 音波动画样式 */
115
+
116
+ .audioWave {
117
+ fill: #fff;
118
+ opacity: 1;
119
+
120
+ &.active {
121
+ animation: wavePulse 1s ease-out infinite;
122
+ }
123
+ }
124
+ }
125
+
126
+ /* 音波动画关键帧 */
127
+ @keyframes wavePulse {
128
+ 0%, 100% {
129
+ transform: scaleY(1);
130
+ }
131
+ 50% {
132
+ transform: scaleY(1.5);
133
+ }
134
+ }
@@ -0,0 +1,170 @@
1
+ import React from "react";// 音波动画组件
2
+ import styles from "./index.module.css";
3
+ import {Input, message} from "antd";
4
+
5
+ class InputMobile extends React.Component {
6
+ constructor(props) {
7
+ super(props);
8
+ this.state = {
9
+ isVoice: false,
10
+ inputText: '',
11
+ isRecording: false,
12
+ isAudioActive: false
13
+ };
14
+ }
15
+
16
+ handleSetInputText = (text) => {
17
+ this.setState({inputText: text});
18
+ };
19
+
20
+ // 处理输入变化
21
+ handleInputChange = (e) => {
22
+ const {onValueChange} = this.props;
23
+ onValueChange(e.target.value);
24
+ this.setState({inputText: e.target.value}, () => {
25
+ // this.autoResizeTextarea();
26
+ });
27
+ }
28
+
29
+ // 开始录音时激活音波动画
30
+ startRecording = () => {
31
+ if (this.props.disabled){
32
+ message.destroy();
33
+ message.warning('对话结束后重试');
34
+ return;
35
+ }
36
+ this.setState({
37
+ isRecording: true,
38
+ isAudioActive: true
39
+ });
40
+ // 可以在这里添加实际录音的初始化逻辑
41
+ }
42
+
43
+ // 停止录音时停止音波动画
44
+ stopRecording = () => {
45
+ const {onStopRecordClick} = this.props;
46
+ onStopRecordClick()
47
+ this.setState({
48
+ isRecording: false,
49
+ isAudioActive: false,
50
+ isVoice: false
51
+ });
52
+ }
53
+
54
+ handleRecordClick = () => {
55
+ const {onStartRecordClick} = this.props;
56
+ if (this.state.isVoice && !this.state.isRecording) {
57
+ this.startRecording();
58
+ onStartRecordClick()
59
+ } else {
60
+ this.stopRecording();
61
+ }
62
+ }
63
+
64
+ render() {
65
+ const {isShowVoiceButton, disabled, placeholder, onSendClick, inputRef, onSmartRobotClick = () => {} } = this.props;
66
+ return (
67
+ <div className={styles.m_tool_box}>
68
+ {/*智能体小人图形固定显示位置*/}
69
+ <div className={styles.m_avatar}>
70
+ <img src={'https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/kai_yuan/common/smartBoy.png'} width={'60px'} style={{zIndex:10}} onClick={onSmartRobotClick}/>
71
+ </div>
72
+ {/*输入框*/}
73
+ <div className={styles.m_input_box} style={{display: 'flex', alignItems: 'center', width: '100%'}}>
74
+ {/*语音键盘切换按钮*/}
75
+ {
76
+ isShowVoiceButton &&
77
+ <div
78
+ onClick={() => this.setState({isVoice: !this.state.isVoice})}
79
+ className={styles.m_voiceToggle}
80
+ >
81
+ <img
82
+ src={this.state.isVoice ?
83
+ 'https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/kai_yuan/common/keyboard.png' :
84
+ 'https://trans-from-yuntu-resourse.oss-cn-beijing.aliyuncs.com/kai_yuan/common/voice.png'
85
+ }
86
+ width={'30px'}
87
+ height={'30px'}
88
+ />
89
+ </div>
90
+ }
91
+
92
+ {/*输入框 或 点击说话*/}
93
+ <div className={styles.m_center_box} style={{flex: 1, position: 'relative'}}>
94
+ {this.state.isVoice ? (
95
+ <div
96
+ className={styles.m_clickToTalk}
97
+ style={{background: this.state.isRecording ? '#1774FF' : '#f0f0f0'}}
98
+ onClick={this.handleRecordClick}
99
+ >
100
+ {this.state.isRecording ? <AudioWaveAnimation isActive={true}/> : '点击说话'}
101
+ </div>
102
+ ) : (
103
+ <Input
104
+ size={'large'}
105
+ ref={inputRef}
106
+ value={this.state.inputText}
107
+ disabled={disabled}
108
+ onChange={this.handleInputChange}
109
+ maxLength={500}
110
+ placeholder={placeholder}
111
+ className="m_inputBox-textarea"
112
+ style={{overflowY: 'hidden'}} // 默认隐藏滚动条
113
+ />
114
+ )}
115
+ </div>
116
+ {/*发送按钮*/}
117
+ {
118
+ this.state.isVoice && this.state.isRecording ? (
119
+ <div
120
+ className={styles.m_sendButton}
121
+ style={{backgroundColor: 'red'}}
122
+ onClick={this.stopRecording}
123
+ >
124
+ <span style={{color: 'white', fontSize: 14}}>结束</span>
125
+ </div>
126
+ ) : (
127
+ <div className={styles.m_sendButton} onClick={() => {
128
+ onSendClick();
129
+ this.setState({inputText: ''})
130
+ }}>
131
+ <span style={{color: 'white', fontSize: 14}}>{disabled ? '停止' : '开始'}</span>
132
+ </div>
133
+ )
134
+ }
135
+ </div>
136
+ </div>
137
+ )
138
+ }
139
+ }
140
+
141
+ class AudioWaveAnimation extends React.Component {
142
+ render() {
143
+ const { isActive } = this.props;
144
+ return (
145
+ <svg className={`${styles.audioWave} ${isActive ? styles.active : ''}`} width="160" height="20"
146
+ viewBox="0 0 60 20">
147
+ <rect x="1" y="9" width="4" height="2" rx="2" ry="2"/>
148
+ <rect x="7" y="7" width="4" height="4" rx="2" ry="2"/>
149
+ <rect x="13" y="5" width="4" height="6" rx="2" ry="2"/>
150
+ <rect x="19" y="7" width="4" height="4" rx="2" ry="2"/>
151
+ <rect x="25" y="9" width="4" height="2" rx="2" ry="2"/>
152
+ <rect x="31" y="7" width="4" height="4" rx="2" ry="2"/>
153
+ <rect x="37" y="5" width="4" height="6" rx="2" ry="2"/>
154
+ <rect x="43" y="7" width="4" height="4" rx="2" ry="2"/>
155
+ <rect x="49" y="9" width="4" height="2" rx="2" ry="2"/>
156
+ <rect x="1" y="9" width="4" height="2" rx="2" ry="2"/>
157
+ <rect x="7" y="7" width="4" height="4" rx="2" ry="2"/>
158
+ <rect x="13" y="5" width="4" height="6" rx="2" ry="2"/>
159
+ <rect x="19" y="7" width="4" height="4" rx="2" ry="2"/>
160
+ <rect x="25" y="9" width="4" height="2" rx="2" ry="2"/>
161
+ <rect x="31" y="7" width="4" height="4" rx="2" ry="2"/>
162
+ <rect x="37" y="5" width="4" height="6" rx="2" ry="2"/>
163
+ <rect x="43" y="7" width="4" height="4" rx="2" ry="2"/>
164
+ <rect x="49" y="9" width="4" height="2" rx="2" ry="2"/>
165
+ </svg>
166
+ );
167
+ }
168
+ }
169
+
170
+ export default InputMobile;
@@ -53,7 +53,10 @@ export default function ChatWidget({
53
53
  renderCustomDropDown,
54
54
  isTagsHidden,
55
55
  isEnableForV1 = true,
56
- questions
56
+ questions,
57
+ voice,
58
+ onSmartRobotClick,
59
+ isShowMobileInputArea,
57
60
  }: {
58
61
  is_enable_call:boolean,
59
62
  tags: [];
@@ -101,6 +104,10 @@ export default function ChatWidget({
101
104
  isTagsHidden: boolean;
102
105
  isEnableForV1: boolean;
103
106
  questions: any[];
107
+ isTagsHidden: boolean,
108
+ voice?: string;
109
+ onSmartRobotClick?: Function;
110
+ isShowMobileInputArea?: boolean;
104
111
  }) {
105
112
  const [open, setOpen] = useState(start_open);
106
113
  const [messages, setMessages] = useState<ChatMessageType[]>([]);
@@ -178,6 +185,9 @@ export default function ChatWidget({
178
185
  isTagsHidden={isTagsHidden}
179
186
  isEnableForV1={isEnableForV1}
180
187
  questions={questions}
188
+ voice={voice}
189
+ onSmartRobotClick={onSmartRobotClick}
190
+ isShowMobileInputArea={isShowMobileInputArea}
181
191
  />
182
192
  </div>
183
193
  );