@tencentcloud/ai-desk-customer-vue 1.5.11 → 1.6.2

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 (52) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/assets/arrow_down_icon.svg +1 -0
  3. package/assets/arrow_down_icon_white.svg +1 -0
  4. package/assets/audio-blue.svg +4 -0
  5. package/assets/audio_icon_1.svg +3 -0
  6. package/assets/audio_icon_2.svg +3 -0
  7. package/assets/audio_icon_3.svg +3 -0
  8. package/assets/keyboard-icon.svg +9 -0
  9. package/components/CustomerServiceChat/chat-header/index-web.vue +1 -5
  10. package/components/CustomerServiceChat/emoji-config/default-emoji.ts +11 -0
  11. package/components/CustomerServiceChat/emoji-config/index.ts +14 -1
  12. package/components/CustomerServiceChat/index-web.vue +10 -2
  13. package/components/CustomerServiceChat/message-input/index-web.vue +95 -10
  14. package/components/CustomerServiceChat/message-input/message-input-button.vue +5 -0
  15. package/components/CustomerServiceChat/message-input/message-input-editor-web.vue +346 -11
  16. package/components/CustomerServiceChat/message-input-toolbar/emoji-dialog-mobile/emoji-dialog-mobile.vue +26 -46
  17. package/components/CustomerServiceChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue +8 -38
  18. package/components/CustomerServiceChat/message-list/bottom-quick-order/index.vue +1 -0
  19. package/components/CustomerServiceChat/message-list/index-web.vue +13 -0
  20. package/components/CustomerServiceChat/message-list/message-elements/message-audio-web.vue +50 -78
  21. package/components/CustomerServiceChat/message-list/message-elements/message-bubble-web.vue +1 -1
  22. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/marked.ts +5 -3
  23. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-robot-welcome.vue +13 -6
  24. package/components/CustomerServiceChat/message-list/message-elements/message-text.vue +3 -2
  25. package/components/CustomerServiceChat/message-list/scroll-button/index.vue +17 -9
  26. package/components/common/Toast/index-web.ts +16 -3
  27. package/components/common/Toast/index-web.vue +16 -6
  28. package/constant.ts +1 -0
  29. package/locales/en/TUIChat.ts +7 -3
  30. package/locales/en/aidesk.ts +0 -1
  31. package/locales/fil/TUIChat.ts +7 -3
  32. package/locales/fil/aidesk.ts +0 -1
  33. package/locales/id/TUIChat.ts +76 -72
  34. package/locales/id/aidesk.ts +0 -1
  35. package/locales/ja/TUIChat.ts +76 -72
  36. package/locales/ja/aidesk.ts +0 -1
  37. package/locales/ms/TUIChat.ts +76 -72
  38. package/locales/ms/aidesk.ts +0 -1
  39. package/locales/ru/TUIChat.ts +7 -3
  40. package/locales/ru/aidesk.ts +0 -1
  41. package/locales/th/TUIChat.ts +76 -72
  42. package/locales/th/aidesk.ts +0 -1
  43. package/locales/vi/TUIChat.ts +76 -72
  44. package/locales/vi/aidesk.ts +0 -1
  45. package/locales/zh_cn/TUIChat.ts +6 -2
  46. package/locales/zh_cn/aidesk.ts +0 -1
  47. package/locales/zh_tw/TUIChat.ts +6 -2
  48. package/locales/zh_tw/aidesk.ts +0 -1
  49. package/package.json +2 -1
  50. package/server.ts +22 -3
  51. package/assets/double-arrow.svg +0 -1
  52. package/assets/keyboard_icon.png +0 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 1.6.2 @2025.10.30
2
+
3
+ ### Features
4
+ - 优化发送语音的产品体验。
5
+ - 优化 scroll-button 等组件样式。
6
+ - emoji 表情适配海外。
7
+ - 完善 markdown 解析。
8
+
9
+ ## 1.6.0 @2025.10.21
10
+
11
+ ### Features
12
+ - 支持用户端转人工成功后发送语音消息。
13
+ - 优化 emoji 表情的交互体验。
14
+ - 开启 url 识别特性时,确保消息安全。
15
+
16
+ ### Fixed
17
+ - 部分场景下,消息已读状态异常问题。
18
+ - 富文本换行问题。
19
+ - 切换账号登录可能导致的用户资料异常问题。
20
+ - 部分手机浏览器软键盘遮挡输入框问题。
21
+
1
22
  ## 1.5.11 @2025.9.28
2
23
 
3
24
  ### Features
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#444444"><path d="M480-344 240-584l56-56 184 184 184-184 56 56-240 240Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ffffff"><path d="M480-344 240-584l56-56 184 184 184-184 56 56-240 240Z"/></svg>
@@ -0,0 +1,4 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M7 6C7 3.23858 9.23858 1 12 1C14.7614 1 17 3.23858 17 6V12.5C17 15.2614 14.7614 17.5 12 17.5C9.23858 17.5 7 15.2614 7 12.5V6ZM15 6V12.5C15 14.1569 13.6569 15.5 12 15.5C10.3431 15.5 9 14.1569 9 12.5V6C9 4.34315 10.3431 3 12 3C13.6569 3 15 4.34315 15 6Z" fill="#1C66E5"/>
3
+ <path d="M3 11V12.5C3 17.1326 6.50005 20.9476 11 21.4451V24H13V21.4451C17.5 20.9476 21 17.1326 21 12.5V11H19V12.5C19 16.366 15.866 19.5 12 19.5C8.13401 19.5 5 16.366 5 12.5V11H3Z" fill="#1C66E5"/>
4
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M11.4131 6.32129C11.0189 6.76465 10.7773 7.34741 10.7773 7.9873C10.7775 8.62651 11.0195 9.20745 11.4131 9.65039L13.2852 7.98633L11.4131 6.32129Z" fill="white" style="fill:white;fill-opacity:1;"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M8.59863 3.82031C7.6133 4.92851 7.0137 6.38793 7.01367 7.9873L7.02148 8.30957C7.09619 9.782 7.68002 11.1194 8.59863 12.1523L9.71973 11.1553C8.97051 10.3125 8.51389 9.20362 8.51367 7.9873C8.5137 6.77028 8.9699 5.65959 9.71973 4.81641L8.59863 3.82031ZM11.4131 6.32129C11.0189 6.76465 10.7773 7.34741 10.7773 7.9873C10.7775 8.62651 11.0195 9.20745 11.4131 9.65039L13.2852 7.98633L11.4131 6.32129Z" fill="white" style="fill:white;fill-opacity:1;"/>
3
+ </svg>
@@ -0,0 +1,3 @@
1
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <path d="M5.78613 1.32031C4.20943 3.09333 3.25002 5.42813 3.25 7.9873L3.2627 8.50391C3.38231 10.8598 4.31628 12.9998 5.78613 14.6523L6.90723 13.6562C5.56634 12.1486 4.75019 10.1637 4.75 7.9873C4.75002 5.81048 5.56603 3.82441 6.90723 2.31641L5.78613 1.32031ZM8.59863 3.82031C7.6133 4.92851 7.0137 6.38793 7.01367 7.9873L7.02148 8.30957C7.09619 9.782 7.68002 11.1194 8.59863 12.1523L9.71973 11.1553C8.97051 10.3125 8.51389 9.20362 8.51367 7.9873C8.5137 6.77028 8.9699 5.65959 9.71973 4.81641L8.59863 3.82031ZM11.4131 6.32129C11.0189 6.76465 10.7773 7.34741 10.7773 7.9873C10.7775 8.62651 11.0195 9.20745 11.4131 9.65039L13.2852 7.98633L11.4131 6.32129Z" fill="white" style="fill:white;fill-opacity:1;"/>
3
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <circle cx="12" cy="12" r="10" stroke="#1C66E5" stroke-width="2"/>
3
+ <circle cx="12" cy="9.5" r="1.5" fill="#1C66E5"/>
4
+ <circle cx="12" cy="14.5" r="1.5" fill="#1C66E5"/>
5
+ <circle cx="7.5" cy="9.5" r="1.5" fill="#1C66E5"/>
6
+ <circle cx="7.5" cy="14.5" r="1.5" fill="#1C66E5"/>
7
+ <circle cx="16.5" cy="9.5" r="1.5" fill="#1C66E5"/>
8
+ <circle cx="16.5" cy="14.5" r="1.5" fill="#1C66E5"/>
9
+ </svg>
@@ -160,11 +160,7 @@ function setCurrentConversationName() {
160
160
  if (props.headerConfig && props.headerConfig.title) {
161
161
  currentConversationName.value = props.headerConfig.title;
162
162
  } else {
163
- if (isH5) {
164
- currentConversationName.value = TUITranslateService.t('AIDesk.Hi,我是') + currentConversation.value.getShowName();
165
- } else {
166
- currentConversationName.value = currentConversation.value.getShowName() || '';
167
- }
163
+ currentConversationName.value = currentConversation.value.getShowName() || '';
168
164
  }
169
165
  }
170
166
 
@@ -80,6 +80,17 @@ export const DEFAULT_BASIC_EMOJI_URL_MAPPING: Record<string, string> = {
80
80
  '[TUIEmoji_Like]': 'emoji_61@2x.png',
81
81
  };
82
82
 
83
+ export const EXCLUDED_EMOJI_LIST = [
84
+ '[TUIEmoji_Pig]',
85
+ '[TUIEmoji_Rich]',
86
+ '[TUIEmoji_Fortune]',
87
+ '[TUIEmoji_857]',
88
+ '[TUIEmoji_666]',
89
+ '[TUIEmoji_Prohibit]',
90
+ '[TUIEmoji_Convinced]',
91
+ '[TUIEmoji_Knife]',
92
+ ];
93
+
83
94
  export const BIG_EMOJI_GROUP_LIST: IEmojiGroupList = [
84
95
  {
85
96
  emojiGroupID: 1,
@@ -1,6 +1,6 @@
1
1
  import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
2
2
  import { CUSTOM_BASIC_EMOJI_URL, CUSTOM_BIG_EMOJI_URL, CUSTOM_BASIC_EMOJI_URL_MAPPING, CUSTOM_BIG_EMOJI_GROUP_LIST } from './custom-emoji';
3
- import { DEFAULT_BASIC_EMOJI_URL, BIG_EMOJI_GROUP_LIST, DEFAULT_BASIC_EMOJI_URL_MAPPING, BASIC_EMOJI_NAME_TO_KEY_MAPPING, DEFAULT_BIG_EMOJI_URL } from './default-emoji';
3
+ import { DEFAULT_BASIC_EMOJI_URL, BIG_EMOJI_GROUP_LIST, DEFAULT_BASIC_EMOJI_URL_MAPPING, BASIC_EMOJI_NAME_TO_KEY_MAPPING, DEFAULT_BIG_EMOJI_URL, EXCLUDED_EMOJI_LIST } from './default-emoji';
4
4
  import { default as emojiCNLocales } from './locales/zh_cn';
5
5
  import { default as emojiENLocales } from './locales/en';
6
6
  import { IEmojiGroupList } from '../../../interface';
@@ -126,6 +126,17 @@ const parseTextToRenderArray = (text: string): Array<{ type: 'text' | 'image'; c
126
126
  return result;
127
127
  };
128
128
 
129
+ const filterEmojisInNonChineseEnv = (list: string[]) => {
130
+ const language = state.get('currentLanguage') || 'zh';
131
+ if (!(language === 'zh' || language === 'zh_tw')) {
132
+ return list.filter((item) => {
133
+ return !EXCLUDED_EMOJI_LIST.includes(item);
134
+ });
135
+ } else {
136
+ return list;
137
+ }
138
+ }
139
+
129
140
  export {
130
141
  EMOJI_GROUP_LIST,
131
142
  CUSTOM_BIG_EMOJI_URL,
@@ -138,4 +149,6 @@ export {
138
149
  transformTextWithKeysToEmojiNames,
139
150
  transformTextWithEmojiNamesToKeys,
140
151
  emojiConfig,
152
+ BASIC_EMOJI_URL,
153
+ filterEmojisInNonChineseEnv,
141
154
  };
@@ -57,6 +57,7 @@
57
57
  :placeholder="TUITranslateService.t('TUIChat.请输入消息')"
58
58
  :inputToolbarDisplayType="inputToolbarDisplayType"
59
59
  :inputToolbarList="props.inputToolbarList"
60
+ :enableSendingAudio="props.enableSendingAudio"
60
61
  @changeToolbarDisplayType="changeToolbarDisplayType"
61
62
  @emojiShow="emojiShow"
62
63
  @toolShow="toolShow"
@@ -149,6 +150,7 @@ interface IProps {
149
150
  enableAINote?: number;
150
151
  enableURLDetection?: number;
151
152
  headerConfig?: IHeaderConfig;
153
+ enableSendingAudio?: number;
152
154
  }
153
155
 
154
156
  const emits = defineEmits(['closeChat']);
@@ -192,6 +194,7 @@ const props = withDefaults(defineProps<IProps>(), {
192
194
  enableAINote: 1,
193
195
  langList: () => [],
194
196
  enableURLDetection: 0,
197
+ enableSendingAudio: 0,
195
198
  });
196
199
 
197
200
  const loginCustomerUIKit = () => {
@@ -329,6 +332,11 @@ try {
329
332
  }
330
333
 
331
334
  onMounted(() => {
335
+ // 如果组件挂载在 sdk login 之后,需再次设置 displayMessageReadReceipt,确保状态准确
336
+ if (TUIChatEngine.isReady()) {
337
+ switchReadStatus(state.get('showReadStatus'));
338
+ }
339
+
332
340
  TUIStore.watch(StoreName.CONV, {
333
341
  currentConversationID: onCurrentConversationIDUpdate,
334
342
  });
@@ -436,8 +444,8 @@ function toolShow(){
436
444
  toolShowH5.value = !toolShowH5.value;
437
445
  }
438
446
 
439
- function blurToolAndEmojiH5(){
440
- if(emojiOpen.value === true){
447
+ function blurToolAndEmojiH5() {
448
+ if (emojiOpen.value === true) {
441
449
  emojiOpen.value = false;
442
450
  }
443
451
  toolShowH5.value = false;
@@ -9,16 +9,21 @@
9
9
  !isPC && 'message-input-container-h5',
10
10
  ]"
11
11
  >
12
+ <div v-if="shouldShowAudio" class="audio-icon" @click="audioShow">
13
+ <Icon :file="isInAudioMode ? keyboardIcon : audioIcon" width="24px" height="24px"/>
14
+ </div>
12
15
  <MessageInputEditor
13
16
  ref="editor"
14
- :placeholder="props.placeholder"
17
+ :placeholder="placeholder"
15
18
  :isMuted="props.isMuted"
16
19
  :muteText="props.muteText"
17
20
  :enableInput="props.enableInput"
18
21
  :enableTyping="props.enableTyping"
19
22
  :enableDragUpload="props.enableDragUpload"
20
- :hasInputTool="shouldShowToolbar"
21
- :hasEmojiTool="shouldShowEmoji"
23
+ :isInAudioMode="isInAudioMode"
24
+ :shouldShowToolbar="shouldShowToolbar"
25
+ :shouldShowEmoji="shouldShowEmoji"
26
+ :shouldShowAudio="shouldShowAudio"
22
27
  @sendMessage="sendMessage"
23
28
  @onTyping="onTyping"
24
29
  @blurToolAndEmojiH5="blurToolAndEmojiH5"
@@ -47,6 +52,7 @@ import {
47
52
  TUIStore,
48
53
  StoreName,
49
54
  IConversationModel,
55
+ TUITranslateService
50
56
  } from '@tencentcloud/chat-uikit-engine';
51
57
  import vue from '../../../adapter-vue';
52
58
  import MessageInputEditor from './message-input-editor-web.vue';
@@ -59,14 +65,14 @@ import Icon from '../../common/Icon.vue';
59
65
  import emojiIcon from '../../../assets/emoji.png';
60
66
  import toolIcon from '../../../assets/more_tools.png';
61
67
  import sendButtonIcon from '../../../assets/send_button_h5.svg';
68
+ import audioIcon from '../../../assets/audio-blue.svg';
69
+ import keyboardIcon from '../../../assets/keyboard-icon.svg';
62
70
  import { INPUT_TOOLBAR_TYPE } from '../../../constant';
71
+ import { onUnmounted } from 'vue';
72
+ import Log from '../../../utils/logger';
63
73
  const { ref, onMounted, onBeforeUnmount, computed } = vue;
64
74
 
65
75
  const props = defineProps({
66
- placeholder: {
67
- type: String,
68
- default: 'this is placeholder',
69
- },
70
76
  isMuted: {
71
77
  type: Boolean,
72
78
  default: true,
@@ -91,18 +97,50 @@ const props = defineProps({
91
97
  type: Array,
92
98
  default: undefined,
93
99
  },
100
+ enableSendingAudio: {
101
+ type: Number,
102
+ default: 0,
103
+ },
94
104
  });
95
105
 
96
106
  const emit = defineEmits(['messageSent', 'resetReplyOrReference', 'onTyping','scrollToLatestMessage','changeToolbarDisplayType','insertEmoji','emojiShow','toolShow','blurToolAndEmojiH5']);
107
+ const placeholder = ref(TUITranslateService.t('TUIChat.请输入消息'));
97
108
  const editor = ref<InstanceType<typeof MessageInputEditor>>();
98
109
  const currentConversation = ref<IConversationModel>();
99
110
  const h5Dialog = ref<HTMLElement>();
100
111
  const showSendButton = ref(false);
112
+ const isInHumanService = ref(false);
101
113
  let quoteMessageCloudCustomData:string = '';
114
+ const isInAudioMode = ref(false);
115
+ let hasAudioStream = false;
116
+ let audioStream = null;
102
117
 
103
118
  onMounted(() => {
104
119
  // document.addEventListener('click', handleClick);
105
- })
120
+ TUIStore.watch(StoreName.CUSTOM, {
121
+ isInHumanService: onInHumanServiceUpdate,
122
+ });
123
+ document.addEventListener('visibilitychange', handleVisibilityChange);
124
+ });
125
+
126
+ onUnmounted(() => {
127
+ TUIStore.unwatch(StoreName.CUSTOM, {
128
+ isInHumanService: onInHumanServiceUpdate,
129
+ });
130
+ stopAudioStream();
131
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
132
+ });
133
+
134
+ const onInHumanServiceUpdate = (data: {conversationID: string, value: boolean}) => {
135
+ if (data && data.conversationID === currentConversation.value.conversationID) {
136
+ isInHumanService.value = data.value;
137
+ if (!isInHumanService.value) {
138
+ isInAudioMode.value = false;
139
+ placeholder.value = TUITranslateService.t('TUIChat.请输入消息');
140
+ stopAudioStream();
141
+ }
142
+ }
143
+ };
106
144
 
107
145
  onBeforeUnmount(() => {
108
146
  // document.removeEventListener('click', handleClick);
@@ -176,21 +214,58 @@ const toolShow = () => {
176
214
  const blurToolAndEmojiH5 = () =>{
177
215
  emit('blurToolAndEmojiH5');
178
216
  }
217
+ const getAudioStream = async () => {
218
+ try {
219
+ stopAudioStream();
220
+ audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
221
+ hasAudioStream = true;
222
+ } catch (err) {
223
+ Log.e('Error accessing audio stream:', err);
224
+ }
225
+ }
226
+ const stopAudioStream = () => {
227
+ if (audioStream) {
228
+ audioStream.getTracks().forEach(track => track.stop());
229
+ audioStream = null;
230
+ hasAudioStream = false;
231
+ }
232
+ }
233
+
234
+ const audioShow = () => {
235
+ isInAudioMode.value = !isInAudioMode.value;
236
+ if (isInAudioMode.value && !hasAudioStream) {
237
+ getAudioStream();
238
+ }
239
+ placeholder.value = isInAudioMode.value ? TUITranslateService.t('TUIChat.按住说话') : TUITranslateService.t('TUIChat.请输入消息');
240
+ emit('blurToolAndEmojiH5');
241
+ }
179
242
 
180
243
  const shouldShowToolbar = computed(() => {
181
244
  if (props.inputToolbarList !== undefined && props.inputToolbarList.length === 0) {
182
245
  return false;
183
246
  }
184
- if (props.inputToolbarList !== undefined && props.inputToolbarList.length === 1 && props.inputToolbarList[0]?.presetId === INPUT_TOOLBAR_TYPE.EMOJI) {
247
+ if (props.inputToolbarList !== undefined && props.inputToolbarList.length === 1 && (props.inputToolbarList[0]?.presetId === INPUT_TOOLBAR_TYPE.EMOJI || props.inputToolbarList[0]?.presetId === INPUT_TOOLBAR_TYPE.AUDIO)) {
185
248
  return false;
186
249
  }
187
250
  return true;
188
251
  });
189
252
 
190
253
  const shouldShowEmoji = computed(() => {
191
- return props.inputToolbarList === undefined || props.inputToolbarList.some(item => item.presetId === INPUT_TOOLBAR_TYPE.EMOJI && item.isEnabled === 1);
254
+ return (props.inputToolbarList === undefined || props.inputToolbarList.some(item => item.presetId === INPUT_TOOLBAR_TYPE.EMOJI && item.isEnabled === 1)) && !isInAudioMode.value;
192
255
  });
193
256
 
257
+ const shouldShowAudio = computed(() => {
258
+ return isH5 && isInHumanService.value && (props.enableSendingAudio === 1 || (props.inputToolbarList !== undefined && props.inputToolbarList.some(item => item.presetId === INPUT_TOOLBAR_TYPE.AUDIO && item.isEnabled === 1)));
259
+ });
260
+
261
+ const handleVisibilityChange = () => {
262
+ if (document.visibilityState === 'hidden') {
263
+ stopAudioStream();
264
+ } else if (document.visibilityState === 'visible' && isInAudioMode.value) {
265
+ getAudioStream();
266
+ }
267
+ }
268
+
194
269
  defineExpose({
195
270
  insertEmoji,
196
271
  reEdit,
@@ -246,6 +321,16 @@ defineExpose({
246
321
  align-items: center;
247
322
  }
248
323
 
324
+ .audio-icon {
325
+ background: #fff;
326
+ margin-left: 10px;
327
+ border-radius: 10px;
328
+ padding: 5px;
329
+ display: flex;
330
+ justify-content: center;
331
+ align-items: center;
332
+ }
333
+
249
334
  .input-tool-dialog{
250
335
  height: fit-content;
251
336
  background: #fff;
@@ -89,6 +89,11 @@ const sendMessage = () => {
89
89
  &:hover {
90
90
  .message-input-button-hover {
91
91
  display: flex;
92
+ width: max-content;
93
+ max-width: 60vw;
94
+ overflow: hidden;
95
+ word-break: break-word;
96
+ white-space: normal;
92
97
  }
93
98
  }
94
99
  }