@tencentcloud/ai-desk-customer-vue 1.6.6 → 1.6.7

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/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.6.7 @2025.12.30
2
+
3
+ ### Features
4
+ - initWithProfile 接口 Options 新增参数 clientCustomData,支持用户登录时传入自定义字段。
5
+
6
+ ### Fixed
7
+ - 优化关闭排队页面的用户体验。
8
+ - H5 端已知问题。
9
+
1
10
  ## 1.6.6 @2025.12.22
2
11
 
3
12
  ### Features
@@ -15,7 +15,7 @@
15
15
  </div>
16
16
  </div>
17
17
  <div class="customer-queue-end-button">
18
- <div :class="['customer-queue-end-button-content', isClicked ? 'button-clicked' : '']" @click="endQueuing">
18
+ <div :class="['customer-queue-end-button-content', isClicked ? 'button-clicked' : '']" @click="leaveQueue">
19
19
  {{ TUITranslateService.t("AIDesk.结束排队") }}
20
20
  </div>
21
21
  </div>
@@ -24,9 +24,8 @@
24
24
  <script lang="ts" setup>
25
25
  import vue from '../../../adapter-vue';
26
26
  const { ref, onMounted, onUnmounted } = vue;
27
- import { IConversationModel, StoreName, TUIChatService, TUIStore, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
28
- import { CUSTOM_MESSAGE_SRC } from '../../../constant';
29
- import { getTo, isEnabledMessageReadReceiptGlobal } from '../../../utils/utils';
27
+ import { IConversationModel, StoreName, TUIStore, TUITranslateService } from '@tencentcloud/chat-uikit-engine';
28
+ import { getTo, leaveQueuing } from '../../../utils/utils';
30
29
  const currentConversation = ref<IConversationModel>();
31
30
  const isClicked = ref(false);
32
31
  const props = defineProps({
@@ -35,6 +34,7 @@ const props = defineProps({
35
34
  default: 0
36
35
  },
37
36
  });
37
+ const emits = defineEmits(['closeQueuePage']);
38
38
  onMounted(() => {
39
39
  TUIStore.watch(StoreName.CONV, {
40
40
  currentConversation: onCurrentConversationUpdate,
@@ -51,22 +51,13 @@ const onCurrentConversationUpdate = (conversation: IConversationModel) => {
51
51
  currentConversation.value = conversation;
52
52
  };
53
53
 
54
- const endQueuing = () => {
54
+ const leaveQueue = () => {
55
55
  if (isClicked.value) {
56
56
  return;
57
57
  }
58
58
  isClicked.value = true;
59
- TUIChatService.sendCustomMessage({
60
- to: getTo(currentConversation.value),
61
- conversationType: currentConversation.value.type,
62
- payload: {
63
- data: JSON.stringify({
64
- customerServicePlugin: 0,
65
- src: CUSTOM_MESSAGE_SRC.USER_END_CONVERSATION,
66
- }),
67
- },
68
- needReadReceipt: isEnabledMessageReadReceiptGlobal(),
69
- },{ onlineUserOnly:true });
59
+ leaveQueuing(getTo(currentConversation.value));
60
+ emits('closeQueuePage');
70
61
  }
71
62
  </script>
72
63
  <style lang="scss">
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <div :class="['tui-chat', !isPC && 'tui-chat-h5']" :key="currentLanguage">
3
3
  <div
4
- v-if="currentConversationID && (!props.showQueuePage || queueNumber < 0 || queueNumber === undefined)"
4
+ v-if="currentConversationID && (!props.showQueuePage || queueNumber < 0 || queueNumber === undefined || hasLeftQueue)"
5
5
  :class="['tui-chat', !isPC && 'tui-chat-h5']"
6
6
  >
7
7
  <ChatHeader
@@ -94,8 +94,8 @@
94
94
  @close="() => { showFeedbackModal = false }"
95
95
  />
96
96
  </div>
97
- <div v-if="props.showQueuePage && queueNumber >= 0" :class="['tui-chat', !isPC && 'tui-chat-h5']">
98
- <CustomerQueuePage :queueNumber="queueNumber"/>
97
+ <div v-if="props.showQueuePage && queueNumber >= 0 && !hasLeftQueue" :class="['tui-chat', !isPC && 'tui-chat-h5']">
98
+ <CustomerQueuePage :queueNumber="queueNumber" @closeQueuePage="closeQueuePage"/>
99
99
  </div>
100
100
  </div>
101
101
  </template>
@@ -178,6 +178,7 @@ const languageForShowList = ref<Array<string>>([]);
178
178
  const feedbackModalRef = ref();
179
179
  const showFeedbackModal = ref(false);
180
180
  const queueNumber = ref(-1);
181
+ const hasLeftQueue = ref(false);
181
182
  let timezone = '';
182
183
  let countryID = '';
183
184
  const props = withDefaults(defineProps<IProps>(), {
@@ -528,8 +529,13 @@ function onDislike(messageInfo: Object) {
528
529
  function onIsQueuingUpdate(data: {conversationID: string, value: number}) {
529
530
  if (data && data.conversationID === currentConversationID.value) {
530
531
  queueNumber.value = data.value;
532
+ hasLeftQueue.value = false;
531
533
  }
532
534
  }
535
+
536
+ function closeQueuePage() {
537
+ hasLeftQueue.value = true;
538
+ }
533
539
  </script>
534
540
 
535
541
  <style scoped lang="scss" src="./style/index.scss">
@@ -361,7 +361,7 @@ function handlePasteText(e: ClipboardEvent) {
361
361
  const text = e.clipboardData?.getData('text/plain') || '';
362
362
  // if paste html in pc, paste by tiptap editor default
363
363
  // if paste text in pc or mobile, parse text to html to render emoji
364
- if (!html) {
364
+ if (isH5 || !html) {
365
365
  const renderArray = parseTextToRenderArray(text);
366
366
  insertEditorContent(renderArray);
367
367
  }
@@ -24,7 +24,7 @@ import ratingToolIcon from '../../../../assets/rating_tool_icon.svg'
24
24
  import ratingToolIconH5 from '../../../../assets/rating_tool_icon_h5.svg';
25
25
  import { getTo } from '../../../../utils/utils';
26
26
  import { CUSTOM_MESSAGE_SRC } from '../../../../constant';
27
- const { ref, onMounted, onUnmounted } = vue;
27
+ const { ref } = vue;
28
28
 
29
29
  const props = defineProps({
30
30
  isH5ToolShow:{
@@ -208,7 +208,6 @@ import {
208
208
  getBoundingClientRect,
209
209
  getScrollInfo,
210
210
  } from '@tencentcloud/universal-api';
211
- import { throttle } from 'lodash';
212
211
  import MessageText from './message-elements/message-text.vue';
213
212
  import MessageImage from './message-elements/message-image-web.vue';
214
213
  import MessageAudio from './message-elements/message-audio-web.vue';
@@ -237,6 +236,7 @@ import {
237
236
  deepCopy,
238
237
  isNonEmptyObject,
239
238
  updateCustomStore,
239
+ throttle
240
240
  } from '../../../utils/utils';
241
241
  import { isMessageInvisible, isThinkingMessage, isThinkingMessageOverTime, JSONToObject, isTransferMessageWithoutDesc } from '../../../utils/index';
242
242
  import { isCustomerConversation } from '../../../index';
@@ -85,6 +85,9 @@ watch(() => props.payload, (newValue: string, oldValue: string) => {
85
85
  }
86
86
 
87
87
  const _payloadObject = JSONToObject(props.payload);
88
+ if (!_payloadObject.chunks) {
89
+ return;
90
+ }
88
91
  chunks.value = Array.isArray(_payloadObject.chunks) ? _payloadObject.chunks.join('') : _payloadObject.chunks;
89
92
  isFinished.value = _payloadObject.isFinished === 1;
90
93
 
@@ -11,7 +11,7 @@
11
11
  import vue from '../../../../adapter-vue';
12
12
  import Icon from '../../../common/Icon.vue';
13
13
  import loading_message from '../../../../assets/loading_message.svg';
14
- const { ref, watchEffect,onMounted,onUnmounted} = vue;
14
+ const { ref, onMounted, onUnmounted} = vue;
15
15
  export default {
16
16
  components:{
17
17
  Icon
@@ -158,7 +158,6 @@ import TUIChatEngine, {
158
158
  TUITranslateService,
159
159
  } from '@tencentcloud/chat-uikit-engine';
160
160
  import addIcon from '../../../../../assets/back.svg';
161
- import playIcon from '../../../../../assets/video-play.png';
162
161
  import Icon from '../../../../common/Icon.vue';
163
162
  import MessageContainer from './message-container.vue';
164
163
  import MessageRecord from '../message-record/index.vue';
@@ -44,7 +44,7 @@ import { isH5 } from '../../../utils/env';
44
44
  import { ToolbarButtonModel } from '../../../interface';
45
45
  import Icon from '../../common/Icon.vue';
46
46
  import { TOOLBAR_BUTTON_TYPE } from '../../../constant';
47
- import { isEnabledMessageReadReceiptGlobal, openSafeUrl, getTo, isNonEmptyObject, transferToTaskFlow, transferToHuman, debounce, endQueuing } from '../../../utils/utils';
47
+ import { isEnabledMessageReadReceiptGlobal, openSafeUrl, getTo, isNonEmptyObject, transferToTaskFlow, transferToHuman, debounce, leaveQueuing } from '../../../utils/utils';
48
48
  import ToolbarButtonHumanService from './toolbar-button-human-service.vue';
49
49
  import ToolbarButtonServiceRating from './toolbar-button-service-rating.vue';
50
50
  import ToolbarButtonEndHumanService from './toolbar-button-end-human-service.vue';
@@ -190,7 +190,7 @@ const onClick = debounce((item:ToolbarButtonModel, index: number) => {
190
190
  } else if (item.type === 4 && isNonEmptyObject(item.content)) {
191
191
  transferToHuman(getTo(currentConversation.value), item.content.groupID, item.content.specificMemberList, item.content.description);
192
192
  } else if (item.type === 5 && queueNumber.value >= 0) {
193
- endQueuing(getTo(currentConversation.value));
193
+ leaveQueuing(getTo(currentConversation.value));
194
194
  } else if (props.toolbarButtonList !== undefined && typeof props.toolbarButtonList[index].clickEvent === 'function') {
195
195
  props.toolbarButtonList[index].clickEvent();
196
196
  }
@@ -38,6 +38,6 @@ const AIDesk = {
38
38
  "排队等待中": "In Queue",
39
39
  "排队等待话术": "We apologize for the wait. All agents are currently assisting other customers. You will be connected shortly. Thank you for your patience.",
40
40
  "当前前方排队人数": "Queue Position",
41
- "结束排队": "Exit Queue"
41
+ "结束排队": "Leave Queue"
42
42
  }
43
43
  export default AIDesk;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencentcloud/ai-desk-customer-vue",
3
- "version": "1.6.6",
3
+ "version": "1.6.7",
4
4
  "description": "Vue2/Vue3 UIKit for AI Desk",
5
5
  "main": "index",
6
6
  "keywords": [
@@ -31,11 +31,9 @@
31
31
  "@tiptap/extension-text": "2.0.0-beta.220",
32
32
  "@tiptap/pm": "2.0.0-beta.220",
33
33
  "@tiptap/suggestion": "2.0.0-beta.220",
34
- "@types/lodash": "^4.14.202",
35
34
  "countries-and-timezones": "^3.8.0",
36
35
  "dayjs": "^1.11.10",
37
36
  "js-audio-recorder": "^1.0.7",
38
- "lodash": "^4.17.21",
39
37
  "marked": "^6.0.0",
40
38
  "mp-html": "^2.5.0",
41
39
  "vue-clipboard3": "2.0.0"
package/server.ts CHANGED
@@ -29,6 +29,7 @@ interface IInitWithProfile {
29
29
  nickName?: string,
30
30
  avatar?: string,
31
31
  customerServiceID?: string,
32
+ clientCustomData?: string,
32
33
  }
33
34
 
34
35
  interface IProfile {
@@ -45,6 +46,7 @@ export default class TUICustomerServer {
45
46
  private myProfile: IProfile;
46
47
  private paramsForActiveAgain: any;
47
48
  private loginFailToasts: any[];
49
+ private clientCustomData: string;
48
50
  constructor() {
49
51
  TUICore.registerService(TUIConstants.TUICustomerServicePlugin.SERVICE.NAME, this);
50
52
  TUICore.registerExtension(TUIConstants.TUIContact.EXTENSION.CONTACT_LIST.EXT_ID, this);
@@ -54,6 +56,7 @@ export default class TUICustomerServer {
54
56
  this.isLoggedIn = false;
55
57
  this.loggedInUserID = '';
56
58
  this.myProfile = {};
59
+ this.clientCustomData = '';
57
60
  }
58
61
 
59
62
  static getInstance(): TUICustomerServer {
@@ -138,7 +141,7 @@ export default class TUICustomerServer {
138
141
  }
139
142
 
140
143
  public async initWithProfile(options: IInitWithProfile) {
141
- const { SDKAppID, userID, userSig, nickName, avatar, customerServiceID } = options;
144
+ const { SDKAppID, userID, userSig, nickName, avatar, customerServiceID, clientCustomData } = options;
142
145
  Log.l(`TUICustomerServer.initWithProfile version:${version}`);
143
146
  if (nickName) {
144
147
  // chat 个人资料的昵称是 nick
@@ -155,6 +158,12 @@ export default class TUICustomerServer {
155
158
  this.customerServiceIDList.push(this.currentCustomerServiceID);
156
159
  }
157
160
  }
161
+
162
+ if (clientCustomData) {
163
+ this.clientCustomData = clientCustomData;
164
+ } else {
165
+ this.clientCustomData = '';
166
+ }
158
167
  return this.init(SDKAppID, userID, userSig);
159
168
  }
160
169
 
@@ -276,7 +285,7 @@ export default class TUICustomerServer {
276
285
 
277
286
  // 激活会话服务流
278
287
  private activeServiceFlow(params: any) {
279
- Log.i(`TUICustomerServer.activeServiceFlow params: language:${params.robotLang} country:${params.country} timezone:${params.timezone}`);
288
+ Log.i(`TUICustomerServer.activeServiceFlow params: language:${params.robotLang} country:${params.country} timezone:${params.timezone} clientCustomData:${this.clientCustomData}`);
280
289
  this.paramsForActiveAgain = { ...params };
281
290
  TUIChatService.sendCustomMessage({
282
291
  to: params.conversationID.slice(3),
@@ -289,6 +298,7 @@ export default class TUICustomerServer {
289
298
  language: params.robotLang,
290
299
  country: params.country,
291
300
  timezone: params.timezone,
301
+ clientCustomData: this.clientCustomData,
292
302
  }
293
303
  }),
294
304
  },
package/utils/utils.ts CHANGED
@@ -378,7 +378,7 @@ export function debounce(func, delay) {
378
378
  }
379
379
  }
380
380
 
381
- export function endQueuing(conversationID: string) {
381
+ export function leaveQueuing(conversationID: string) {
382
382
  TUIChatService.sendCustomMessage({
383
383
  to: conversationID,
384
384
  conversationType: TUIChatEngine.TYPES.CONV_C2C,
@@ -390,4 +390,32 @@ export function endQueuing(conversationID: string) {
390
390
  },
391
391
  needReadReceipt: isEnabledMessageReadReceiptGlobal(),
392
392
  },{ onlineUserOnly: true });
393
- };
393
+ };
394
+
395
+ export function throttle(
396
+ func,
397
+ wait = 200,
398
+ options?: { leading?: boolean;}
399
+ ) {
400
+ let last = 0;
401
+ let timeout;
402
+ const leading = options?.leading !== undefined ? options.leading : true;
403
+
404
+ return function (this: any, ...args: any[]) {
405
+ const now = Date.now();
406
+ if (!last && !leading) {
407
+ last = now;
408
+ }
409
+
410
+ const remaining = wait - (now - last);
411
+
412
+ if (remaining <= 0) {
413
+ if (timeout) {
414
+ clearTimeout(timeout);
415
+ timeout = null;
416
+ }
417
+ last = now;
418
+ func.apply(this, args);
419
+ }
420
+ };
421
+ }
Binary file
@@ -1,59 +0,0 @@
1
- <template>
2
- <div class="dialog-video">
3
- <video
4
- v-if="isShow"
5
- id="videoEle"
6
- class="video-box"
7
- :src="videoData"
8
- controls
9
- autoplay
10
- />
11
- </div>
12
- </template>
13
-
14
- <script lang="ts" setup>
15
- import vue from '../../TencentCloudCustomer/tui-customer-service-plugin-test/adapter-vue';
16
- import { TUIGlobal } from '@tencentcloud/universal-api';
17
- import { onLoad, onReady } from '@dcloudio/uni-app';
18
- const { ref } = vue;
19
-
20
- const videoData = ref();
21
- const isShow = ref(false);
22
- const videoContext = ref();
23
- onLoad((option: any) => {
24
- const decodedUrl = decodeURIComponent(option?.videoUrl);
25
- videoData.value = decodedUrl;
26
- isShow.value = true;
27
- });
28
-
29
- onReady(() => {
30
- isShow.value = true;
31
- videoContext.value = TUIGlobal.createVideoContext('videoEle');
32
- });
33
- </script>
34
- <style lang="scss" scoped>
35
- .dialog-video {
36
- position: fixed;
37
- z-index: 999;
38
- width: 100vw;
39
- height: 100vh;
40
- background: rgba(#000, 0.6);
41
- top: 0;
42
- left: 0;
43
- right: 0;
44
- bottom: 0;
45
- display: flex;
46
- justify-content: center;
47
- align-items: center;
48
-
49
- .video-box {
50
- position: absolute;
51
- width: 100vw;
52
- height: 100vh;
53
- top: 0;
54
- left: 0;
55
- right: 0;
56
- bottom: 0;
57
- }
58
- }
59
- </style>