@tencentcloud/ai-desk-customer-vue 1.5.6 → 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.
Files changed (59) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/assets/feedback_dislike_after.svg +1 -1
  3. package/assets/feedback_dislike_before.svg +2 -9
  4. package/assets/feedback_dislike_hover.svg +2 -9
  5. package/assets/feedback_like_after.svg +2 -13
  6. package/assets/feedback_like_before.svg +2 -9
  7. package/assets/feedback_like_hover.svg +2 -9
  8. package/components/CustomerServiceChat/chat-header/index-web.vue +19 -12
  9. package/components/CustomerServiceChat/feedback-modal/index.vue +391 -0
  10. package/components/CustomerServiceChat/index-web.vue +34 -1
  11. package/components/CustomerServiceChat/message-input/message-input-editor-web.vue +0 -1
  12. package/components/CustomerServiceChat/message-input/message-input-quote/index.vue +0 -3
  13. package/components/CustomerServiceChat/message-input-toolbar/index-web.vue +4 -2
  14. package/components/CustomerServiceChat/message-list/bottom-quick-order/index.vue +0 -1
  15. package/components/CustomerServiceChat/message-list/index-web.vue +92 -62
  16. package/components/CustomerServiceChat/message-list/message-elements/feedback-button.vue +96 -251
  17. package/components/CustomerServiceChat/message-list/message-elements/message-bubble-web.vue +51 -16
  18. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-concurrency-limit.vue +0 -2
  19. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-ivr-form/form-branch.vue +0 -1
  20. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-multi-branch/branch-pc.vue +0 -2
  21. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-multi-form/form-mobile.vue +34 -29
  22. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-multi-form/form-pc.vue +0 -1
  23. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-order.vue +0 -1
  24. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-product-card.vue +0 -1
  25. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-rating/message-rating-star.vue +0 -1
  26. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-stream.vue +0 -1
  27. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-transfer-with-desc.vue +0 -1
  28. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/styles/common.scss +0 -2
  29. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-plugin-layout-web.vue +16 -0
  30. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-plugin-web.vue +18 -0
  31. package/components/CustomerServiceChat/message-list/message-elements/message-quote/index-web.vue +0 -3
  32. package/components/CustomerServiceChat/message-list/message-elements/message-text.vue +26 -19
  33. package/components/CustomerServiceChat/message-list/message-elements/message-timestamp.vue +0 -1
  34. package/components/CustomerServiceChat/message-list/message-elements/read-status/index.vue +4 -2
  35. package/components/CustomerServiceChat/message-list/scroll-button/index.vue +8 -4
  36. package/components/CustomerServiceChat/message-list/style/web.scss +0 -2
  37. package/components/CustomerServiceChat/message-toolbar-button/index.vue +13 -9
  38. package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-end-human-service.vue +1 -1
  39. package/components/CustomerServiceChat/style/common.scss +4 -0
  40. package/components/CustomerServiceChat/style/web.scss +2 -1
  41. package/components/common/BottomPopup/style/h5.scss +0 -1
  42. package/components/common/Dialog/style/color.scss +0 -1
  43. package/components/common/Toast/index-web.vue +1 -1
  44. package/constant.ts +3 -2
  45. package/locales/en/aidesk.ts +18 -15
  46. package/locales/fil/aidesk.ts +17 -15
  47. package/locales/id/aidesk.ts +17 -15
  48. package/locales/ja/aidesk.ts +17 -15
  49. package/locales/ms/aidesk.ts +17 -15
  50. package/locales/ru/aidesk.ts +17 -15
  51. package/locales/th/aidesk.ts +17 -15
  52. package/locales/vi/aidesk.ts +17 -15
  53. package/locales/zh_cn/aidesk.ts +17 -14
  54. package/locales/zh_tw/aidesk.ts +17 -15
  55. package/package.json +1 -1
  56. package/server.ts +5 -4
  57. package/utils/index.ts +6 -0
  58. package/utils/utils.ts +42 -0
  59. package/assets/customer_avatar.png +0 -0
@@ -24,9 +24,13 @@
24
24
  :bottomQuickOrder="props.bottomQuickOrder"
25
25
  :showBottomQuickOrder="showBottomQuickOrder"
26
26
  @closeBottomQuickOrder="closeBottomQuickOrder"
27
+ :enableFeedback="props.enableFeedback"
28
+ :enableAINote="props.enableAINote"
29
+ @like="onLike"
30
+ @dislike="onDislike"
27
31
  />
28
32
  <MessageToolbarButton
29
- v-if="!isH5 || (!Boolean(quoteMessage) && !showBottomQuickOrder)"
33
+ v-if="props.toolbarButtonList && (!isH5 || (!Boolean(quoteMessage) && !showBottomQuickOrder))"
30
34
  :toolbarButtonList="props.toolbarButtonList"
31
35
  />
32
36
  <MessageInputToolbar
@@ -82,6 +86,11 @@
82
86
  />
83
87
  </div>
84
88
  </div>
89
+ <FeedbackModal
90
+ ref="feedbackModalRef"
91
+ v-show="showFeedbackModal"
92
+ @close="() => { showFeedbackModal = false }"
93
+ />
85
94
  </div>
86
95
  </div>
87
96
  </template>
@@ -110,6 +119,7 @@ import { Toast, TOAST_TYPE } from '../common/Toast/index-web';
110
119
  import state from '../../utils/state.js';
111
120
  import { switchReadStatus,isNonEmptyObject } from '../../utils/utils';
112
121
  import { getCountryForTimezone } from 'countries-and-timezones';
122
+ import FeedbackModal from './feedback-modal/index.vue';
113
123
  const { ref, onMounted, onUnmounted, computed } = vue;
114
124
 
115
125
  interface IProps {
@@ -134,6 +144,9 @@ interface IProps {
134
144
  bottomQuickOrder?: QuickOrderModel;
135
145
  enableMultilingual?: number;
136
146
  langList?: Array<string>;
147
+ enableFeedback?: number;
148
+ enableAINote?: number;
149
+ enableURLDetection?: number;
137
150
  }
138
151
 
139
152
  const emits = defineEmits(['closeChat']);
@@ -151,6 +164,8 @@ const quoteMessage = ref<IMessageModel>();
151
164
  const showBottomQuickOrder = ref(false);
152
165
  const currentLanguage = ref('');
153
166
  const languageForShowList = ref<Array<string>>([]);
167
+ const feedbackModalRef = ref();
168
+ const showFeedbackModal = ref(false);
154
169
  let timezone = '';
155
170
  let countryID = '';
156
171
  const props = withDefaults(defineProps<IProps>(), {
@@ -171,7 +186,10 @@ const props = withDefaults(defineProps<IProps>(), {
171
186
  userNickName: '',
172
187
  showTyping: 0,
173
188
  enableMultilingual: 0,
189
+ enableFeedback: 0,
190
+ enableAINote: 1,
174
191
  langList: () => [],
192
+ enableURLDetection: 0,
175
193
  });
176
194
 
177
195
  const loginCustomerUIKit = () => {
@@ -283,6 +301,10 @@ const setShowTyping = () => {
283
301
  state.set('showTyping', props.showTyping);
284
302
  }
285
303
 
304
+ const setEnableURLDetection = () => {
305
+ state.set('enableURLDetection', props.enableURLDetection);
306
+ }
307
+
286
308
  try {
287
309
  const userContext = TUILogin.getContext();
288
310
  if (userContext.userID == '' && props.SDKAppID !==0 && props.userID !=='' && props.userSig !==''){
@@ -295,6 +317,7 @@ try {
295
317
  setAvatarNickName();
296
318
  setShowReadStatus();
297
319
  setShowTyping();
320
+ setEnableURLDetection();
298
321
  getTimeZoneAndCountry();
299
322
  if (isNonEmptyObject(props.bottomQuickOrder)) {
300
323
  showBottomQuickOrder.value = true;
@@ -437,6 +460,7 @@ function changeLanguage(languageCode: string) {
437
460
  Log.l(`multilingual: change language to ${languageCode}`);
438
461
  TUITranslateService.changeLanguage(languageCode).then(() => {
439
462
  currentLanguage.value = languageCode;
463
+ state.set('currentLanguage', languageCode);
440
464
  try {
441
465
  localStorage.setItem('AIDesk_language', languageCode);
442
466
  } catch {
@@ -458,6 +482,15 @@ function activeConversation() {
458
482
  },
459
483
  });
460
484
  }
485
+
486
+ function onLike(messageInfo: Object) {
487
+ feedbackModalRef.value.onLike(messageInfo);
488
+ }
489
+
490
+ function onDislike(messageInfo: Object) {
491
+ showFeedbackModal.value = true;
492
+ feedbackModalRef.value.onDislike(messageInfo);
493
+ }
461
494
  </script>
462
495
 
463
496
  <style scoped lang="scss" src="./style/index.scss">
@@ -743,7 +743,6 @@ defineExpose({
743
743
 
744
744
  &-area {
745
745
  box-sizing: border-box;
746
- font-family: PingFangSC-Regular;
747
746
  height: 100%;
748
747
  flex: 1;
749
748
  display: flex;
@@ -171,7 +171,6 @@ function onQuoteMessageUpdated(options?: {
171
171
  overflow: hidden;
172
172
  text-overflow: ellipsis;
173
173
  white-space: nowrap;
174
- font-family: PingFangSC-Regular;
175
174
  }
176
175
  }
177
176
  .input-quote-content-h5 {
@@ -198,13 +197,11 @@ function onQuoteMessageUpdated(options?: {
198
197
  overflow: hidden;
199
198
  text-overflow: ellipsis;
200
199
  white-space: nowrap;
201
- font-family: PingFangSC-Regular;
202
200
  }
203
201
 
204
202
  .input-quote-sender-h5 {
205
203
  font-size: 10px;
206
204
  color:#00000080;
207
- font-family: PingFangSC-Regular;
208
205
  }
209
206
  }
210
207
 
@@ -148,8 +148,10 @@ const onCurrentConversationUpdate = (conversation: IConversationModel) => {
148
148
  = currentConversation?.value?.type === TUIChatEngine.TYPES.CONV_GROUP;
149
149
  };
150
150
 
151
- const onInHumanServiceUpdate = (value: boolean) => {
152
- isInHumanService.value = value;
151
+ const onInHumanServiceUpdate = (data: {conversationID: string, value: boolean}) => {
152
+ if (data && data.conversationID === currentConversation.value.conversationID) {
153
+ isInHumanService.value = data.value;
154
+ }
153
155
  };
154
156
 
155
157
  const insertEmoji = (emojiObj: object) => {
@@ -100,7 +100,6 @@ const closeOrder = () => {
100
100
  border-radius: 8px;
101
101
  margin: 0 10px 0 10px;
102
102
  position: relative;
103
- font-family: PingFangSC-Regular;
104
103
  }
105
104
  .bottom-quick-order-container-pc {
106
105
  width: 300px;
@@ -49,6 +49,10 @@
49
49
  @handleH5LongPress="handleH5LongPress"
50
50
  @heightChanged="onHeightChanged"
51
51
  @messageSent="onMessageSent"
52
+ :enableFeedback="props.enableFeedback"
53
+ :enableAINote="props.enableAINote"
54
+ @like="onLike"
55
+ @dislike="onDislike"
52
56
  />
53
57
  <div
54
58
  v-else
@@ -66,14 +70,19 @@
66
70
  :isAudioPlayed="Boolean(audioPlayedMapping[item.ID])"
67
71
  :blinkMessageIDList="blinkMessageIDList"
68
72
  :messageItem="JSON.parse(JSON.stringify(item))"
73
+ :enableFeedback="props.enableFeedback"
74
+ :enableAINote="props.enableAINote"
69
75
  @blinkMessage="blinkMessage"
70
76
  @resendMessage="resendMessage(item)"
77
+ @like="onLike"
78
+ @dislike="onDislike"
71
79
  >
72
80
  <template #messageElement>
73
81
  <MessageThinking v-if="isThinkingMessage(item)"/>
74
82
  <MessageText
75
83
  v-else-if="item.type === TYPES.MSG_TEXT"
76
84
  :content="item.getMessageContent()"
85
+ :flow="item.flow"
77
86
  />
78
87
  <ProgressMessage
79
88
  v-else-if="item.type === TYPES.MSG_IMAGE"
@@ -151,6 +160,7 @@
151
160
  <ScrollButton
152
161
  ref="scrollButtonInstanceRef"
153
162
  @scrollToLatestMessage="scrollToLatestMessage"
163
+ @scrollNearToBottom="scrollNearToBottom"
154
164
  />
155
165
  <Dialog
156
166
  v-if="reSendDialogShow"
@@ -226,11 +236,13 @@ import {
226
236
  isEnabledMessageReadReceiptGlobal,
227
237
  deepCopy,
228
238
  isNonEmptyObject,
239
+ updateCustomStore,
229
240
  } from '../../../utils/utils';
230
241
  import { isMessageInvisible, isThinkingMessage, isThinkingMessageOverTime, JSONToObject, isTransferMessageWithoutDesc } from '../../../utils/index';
231
242
  import { isCustomerConversation } from '../../../index';
232
243
  import { CUSTOM_MESSAGE_SRC } from '../../../constant';
233
244
  import { QuickOrderModel } from '../../../interface';
245
+ import Log from '../../../utils/logger';
234
246
 
235
247
  interface ScrollConfig {
236
248
  scrollToMessage?: IMessageModel;
@@ -244,15 +256,21 @@ interface ScrollConfig {
244
256
  interface IProps {
245
257
  bottomQuickOrder?: QuickOrderModel;
246
258
  showBottomQuickOrder: boolean;
259
+ enableFeedback: number;
260
+ enableAINote: number;
247
261
  }
248
262
  const props = withDefaults(defineProps<IProps>(), {
249
263
  showBottomQuickOrder: false,
264
+ enableFeedback: 0,
265
+ enableAINote: 1,
250
266
  });
251
267
 
252
268
  interface IEmits {
253
269
  (key: 'closeInputToolBar'): void;
254
270
  (key: 'handleEditor', message: IMessageModel, type: string): void;
255
271
  (key: 'closeBottomQuickOrder'): void;
272
+ (key: 'like', messageInfo: Object): void;
273
+ (key: 'dislike', messageInfo: Object): void;
256
274
  }
257
275
 
258
276
  const emits = defineEmits<IEmits>();
@@ -354,29 +372,32 @@ function onNewMessageList(list: IMessageModel[]) {
354
372
  list.forEach((message:IMessageModel) => {
355
373
  if (message.type === TUIChatEngine.TYPES.MSG_CUSTOM) {
356
374
  const data = JSONToObject(message.payload.data);
375
+ const conversationID = message.conversationID;
357
376
  if (data) {
358
377
  if (data.src === CUSTOM_MESSAGE_SRC.BOT_STATUS) {
378
+ updateCustomStore("canEndConversation", { conversationID, value: true });
359
379
  if (data.content.content === 'inBot') {
360
- TUIStore.update(StoreName.CUSTOM, "isInHumanService", false);
380
+ updateCustomStore("isInHumanService", { conversationID, value: false });
361
381
  }
362
382
  } else if (data.src === CUSTOM_MESSAGE_SRC.SEAT_STATUS) {
383
+ updateCustomStore("canEndConversation", { conversationID, value: true });
363
384
  if (data.content.command === "updateSeatStatus") {
364
385
  if (data.content.content === 'inSeat') {
365
- TUIStore.update(StoreName.CUSTOM, "isInHumanService", true);
386
+ updateCustomStore("isInHumanService", { conversationID, value: true });
366
387
  } else if (data.content.content === 'outSeat') {
367
- TUIStore.update(StoreName.CUSTOM, "isInHumanService", false);
388
+ updateCustomStore("isInHumanService", { conversationID, value: false });
368
389
  }
369
390
  }
370
391
  } else if (data.src === CUSTOM_MESSAGE_SRC.TYPING_STATE) {
371
392
  if (data.typingStatus === 1) {
372
- TUIStore.update(StoreName.CUSTOM, 'isTyping', true);
393
+ updateCustomStore("isTyping", { conversationID, value: true });
373
394
  } else {
374
- TUIStore.update(StoreName.CUSTOM, 'isTyping', false);
395
+ updateCustomStore("isTyping", { conversationID, value: false });
375
396
  }
376
397
  } else if (data.src === CUSTOM_MESSAGE_SRC.NO_SEAT_ONLINE || data.src === CUSTOM_MESSAGE_SRC.TIMEOUT || data.src === CUSTOM_MESSAGE_SRC.END) {
377
- TUIStore.update(StoreName.CUSTOM, "isInSession", false);
378
- } else if (data.src === CUSTOM_MESSAGE_SRC.SESSION_RESTARTED) {
379
- TUIStore.update(StoreName.CUSTOM, "isInSession", true);
398
+ updateCustomStore("canEndConversation", { conversationID, value: false });
399
+ } else if (data.src === CUSTOM_MESSAGE_SRC.GET_FEEDBACK_MENU) {
400
+ TUIStore.update(StoreName.CUSTOM, "feedbackTags", data.content.menu);
380
401
  }
381
402
  }
382
403
  }
@@ -385,6 +406,7 @@ function onNewMessageList(list: IMessageModel[]) {
385
406
 
386
407
  async function onMessageListUpdated(list: IMessageModel[]) {
387
408
  if (!isCustomerConversation(currentConversationID.value)) {
409
+ Log.w(`Messages filtered as they are not customer service messages. currentConversationID: ${currentConversationID.value}`);
388
410
  return;
389
411
  }
390
412
  observer?.disconnect();
@@ -441,9 +463,7 @@ async function onMessageListUpdated(list: IMessageModel[]) {
441
463
  await scrollToPosition({ scrollToBottom: true });
442
464
  }
443
465
  currentLastMessage.value = Object.assign({}, newLastMessage);
444
- if (isEnabledMessageReadReceiptGlobal()) {
445
- nextTick(() => bindIntersectionObserver());
446
- }
466
+ nextTick(() => bindIntersectionObserver());
447
467
  }
448
468
 
449
469
  function isCurrentListInBottomPosition() {
@@ -682,6 +702,13 @@ async function scrollToLatestMessage() {
682
702
  }
683
703
  scrollButtonInstanceRef.value?.hideScrollButton();
684
704
  beforeHistoryGetScrollHeight.value = 0;
705
+ // 滚动到底部的时候,及时检查是否要上报已读回执
706
+ bindIntersectionObserver();
707
+ }
708
+
709
+ function scrollNearToBottom() {
710
+ // 滚动到底部的时候,及时检查是否要上报已读回执
711
+ bindIntersectionObserver();
685
712
  }
686
713
 
687
714
  const handelScrollListScroll = throttle(
@@ -693,67 +720,62 @@ const handelScrollListScroll = throttle(
693
720
  );
694
721
 
695
722
  async function bindIntersectionObserver() {
696
- if (
697
- !messageList.value
698
- || !messageListRef.value
699
- || messageList.value.length === 0
700
- ) {
723
+ if (!messageList.value || !messageListRef.value || messageList.value.length === 0) {
701
724
  return;
702
725
  }
703
726
 
704
- const mappingFromIDToMessage: Record<
705
- string,
706
- {
707
- msgDom: HTMLElement;
708
- msgModel: IMessageModel | undefined;
709
- }
710
- > = {};
727
+ if (!isEnabledMessageReadReceiptGlobal()) {
728
+ return;
729
+ }
711
730
 
712
- observer?.disconnect();
713
- observer = new IntersectionObserver(
714
- (entries) => {
715
- entries.forEach((entry) => {
716
- const { isIntersecting, target } = entry;
717
- if (isIntersecting) {
718
- const { msgDom, msgModel } = mappingFromIDToMessage[target.id];
719
- if (
720
- msgModel
721
- && !msgModel.readReceiptInfo?.isPeerRead
722
- && !sentReceiptMessageIDSet.has(msgModel.ID)
723
- ) {
731
+ const mappingFromIDToMessage: Record<string, { msgDom: HTMLElement; msgModel: IMessageModel | undefined; }> = {};
732
+
733
+ if (observer) {
734
+ observer.disconnect();
735
+ }
736
+ observer = new IntersectionObserver((entries) => {
737
+ entries.forEach((entry) => {
738
+ const { isIntersecting, target } = entry;
739
+ if (isIntersecting) {
740
+ const { msgDom, msgModel } = mappingFromIDToMessage[target.id];
741
+ if (msgModel) {
742
+ const { readReceiptInfo, ID, payload } = msgModel;
743
+ if (!readReceiptInfo.isPeerRead && !sentReceiptMessageIDSet.has(ID)) {
724
744
  TUIChatService.sendMessageReadReceipt([msgModel]);
725
- sentReceiptMessageIDSet.add(msgModel.ID);
726
- observer?.unobserve(msgDom);
745
+ sentReceiptMessageIDSet.add(ID);
746
+ Log.l(`Receipt sent. ID:${ID} payload:${JSON.stringify(payload)}`);
747
+ if (observer) {
748
+ observer.unobserve(msgDom);
749
+ }
727
750
  }
728
751
  }
729
- });
730
- },
731
- {
732
- root: messageListRef.value,
733
- threshold: 0.7,
734
- },
735
- );
752
+ }
753
+ });
754
+ }, {
755
+ root: messageListRef.value,
756
+ threshold: 0.3, // 超过1/4可见时触发,提高灵敏度
757
+ });
736
758
 
737
- const arrayOfMessageLi
738
- = messageListRef.value?.querySelectorAll('.message-li');
759
+ const arrayOfMessageLi = messageListRef.value?.querySelectorAll('.message-li');
739
760
  if (arrayOfMessageLi) {
740
- for (let i = 0; i < arrayOfMessageLi?.length; ++i) {
761
+ for (let i = 0, length = arrayOfMessageLi.length; i < length; i++) {
741
762
  const messageElement = arrayOfMessageLi[i] as HTMLElement;
742
- const matchingMessage = messageList.value.find(
743
- (message: IMessageModel) => {
744
- return messageElement.id.slice(4) === message.ID;
745
- },
746
- );
747
- if (
748
- matchingMessage
749
- && matchingMessage.needReadReceipt
750
- && matchingMessage.flow === 'in'
751
- ) {
752
- mappingFromIDToMessage[messageElement.id] = {
753
- msgDom: messageElement,
754
- msgModel: matchingMessage,
755
- };
756
- observer?.observe(messageElement);
763
+ const matchingMessage = messageList.value.find((message: IMessageModel) => {
764
+ // replace tui-
765
+ return messageElement.id.slice(4) === message.ID;
766
+ });
767
+ if (matchingMessage) {
768
+ const { needReadReceipt, readReceiptInfo, flow } = matchingMessage;
769
+ // 收到的消息需要已读回执,但我尚未发送
770
+ if (needReadReceipt && flow === 'in' && !readReceiptInfo.isPeerRead) {
771
+ mappingFromIDToMessage[messageElement.id] = {
772
+ msgDom: messageElement,
773
+ msgModel: matchingMessage,
774
+ };
775
+ if (observer) {
776
+ observer.observe(messageElement);
777
+ }
778
+ }
757
779
  }
758
780
  }
759
781
  }
@@ -794,6 +816,14 @@ function sendBottomQuickOrder() {
794
816
  closeBottomQuickOrder();
795
817
  }
796
818
 
819
+ function onLike(messageInfo: Object) {
820
+ emits('like', messageInfo);
821
+ };
822
+
823
+ function onDislike(messageInfo: Object) {
824
+ emits('dislike', messageInfo);
825
+ };
826
+
797
827
  defineExpose({
798
828
  scrollToLatestMessage,
799
829
  });