@tencentcloud/ai-desk-customer-vue 1.5.8 → 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.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 1.5.9 @2025.9.8
2
+
3
+ ### Features
4
+ - 支持识别文本消息中的 url 并可点击。
5
+ - 优化工具栏的【结束会话按钮】的显隐时机。
6
+
7
+ ### Fixed
8
+ - 切换账号,新账号的资料可能异常问题。
9
+
1
10
  ## 1.5.8 @2025.8.26
2
11
 
3
12
  ### Features
@@ -9,7 +9,7 @@
9
9
  <Icon :file="backSVG" />
10
10
  </div>
11
11
  <div class="chat-header-container">
12
- <Icon v-if="!isPC" width="32px" :file="customerAvatarMobile" />
12
+ <Icon v-if="!isPC" width="32px" height="23px" :file="customerAvatarMobile" />
13
13
  <div :class="['chat-header-content', !isPC && 'chat-header-h5-content']">
14
14
  {{ currentConversationName }}
15
15
  </div>
@@ -126,9 +126,20 @@ const closeChat = (conversationID: string | undefined) => {
126
126
  emits('closeChat', conversationID);
127
127
  };
128
128
 
129
+ function setCurrentConversationName() {
130
+ if (!currentConversation.value) {
131
+ return;
132
+ }
133
+ if (isPC) {
134
+ currentConversationName.value = currentConversation.value?.getShowName() || '';
135
+ } else {
136
+ currentConversationName.value = TUITranslateService.t('AIDesk.Hi,我是') + currentConversation.value.getShowName();
137
+ }
138
+ }
139
+
129
140
  function onCurrentConversationUpdated(conversation: IConversationModel) {
130
141
  currentConversation.value = conversation;
131
- currentConversationName.value = currentConversation.value?.getShowName();
142
+ setCurrentConversationName();
132
143
  }
133
144
 
134
145
  function onTypingStatusUpdated(data: {conversationID: string, value: boolean}) {
@@ -137,11 +148,7 @@ function onTypingStatusUpdated(data: {conversationID: string, value: boolean}) {
137
148
  if (isTyping.value && state.get('showTyping') === 1) {
138
149
  currentConversationName.value = TUITranslateService.t('TUIChat.对方正在输入');
139
150
  } else {
140
- if (isPC) {
141
- currentConversationName.value = currentConversation.value?.getShowName();
142
- } else {
143
- currentConversationName.value = TUITranslateService.t('AIDesk.Hi,我是') + currentConversation.value?.getShowName();
144
- }
151
+ setCurrentConversationName();
145
152
  }
146
153
  }
147
154
  }
@@ -30,7 +30,7 @@
30
30
  @dislike="onDislike"
31
31
  />
32
32
  <MessageToolbarButton
33
- v-if="!isH5 || (!Boolean(quoteMessage) && !showBottomQuickOrder)"
33
+ v-if="props.toolbarButtonList && (!isH5 || (!Boolean(quoteMessage) && !showBottomQuickOrder))"
34
34
  :toolbarButtonList="props.toolbarButtonList"
35
35
  />
36
36
  <MessageInputToolbar
@@ -146,6 +146,7 @@ interface IProps {
146
146
  langList?: Array<string>;
147
147
  enableFeedback?: number;
148
148
  enableAINote?: number;
149
+ enableURLDetection?: number;
149
150
  }
150
151
 
151
152
  const emits = defineEmits(['closeChat']);
@@ -188,6 +189,7 @@ const props = withDefaults(defineProps<IProps>(), {
188
189
  enableFeedback: 0,
189
190
  enableAINote: 1,
190
191
  langList: () => [],
192
+ enableURLDetection: 0,
191
193
  });
192
194
 
193
195
  const loginCustomerUIKit = () => {
@@ -299,6 +301,10 @@ const setShowTyping = () => {
299
301
  state.set('showTyping', props.showTyping);
300
302
  }
301
303
 
304
+ const setEnableURLDetection = () => {
305
+ state.set('enableURLDetection', props.enableURLDetection);
306
+ }
307
+
302
308
  try {
303
309
  const userContext = TUILogin.getContext();
304
310
  if (userContext.userID == '' && props.SDKAppID !==0 && props.userID !=='' && props.userSig !==''){
@@ -311,6 +317,7 @@ try {
311
317
  setAvatarNickName();
312
318
  setShowReadStatus();
313
319
  setShowTyping();
320
+ setEnableURLDetection();
314
321
  getTimeZoneAndCountry();
315
322
  if (isNonEmptyObject(props.bottomQuickOrder)) {
316
323
  showBottomQuickOrder.value = true;
@@ -82,6 +82,7 @@
82
82
  <MessageText
83
83
  v-else-if="item.type === TYPES.MSG_TEXT"
84
84
  :content="item.getMessageContent()"
85
+ :flow="item.flow"
85
86
  />
86
87
  <ProgressMessage
87
88
  v-else-if="item.type === TYPES.MSG_IMAGE"
@@ -374,10 +375,12 @@ function onNewMessageList(list: IMessageModel[]) {
374
375
  const conversationID = message.conversationID;
375
376
  if (data) {
376
377
  if (data.src === CUSTOM_MESSAGE_SRC.BOT_STATUS) {
378
+ updateCustomStore("canEndConversation", { conversationID, value: true });
377
379
  if (data.content.content === 'inBot') {
378
380
  updateCustomStore("isInHumanService", { conversationID, value: false });
379
381
  }
380
382
  } else if (data.src === CUSTOM_MESSAGE_SRC.SEAT_STATUS) {
383
+ updateCustomStore("canEndConversation", { conversationID, value: true });
381
384
  if (data.content.command === "updateSeatStatus") {
382
385
  if (data.content.content === 'inSeat') {
383
386
  updateCustomStore("isInHumanService", { conversationID, value: true });
@@ -392,9 +395,7 @@ function onNewMessageList(list: IMessageModel[]) {
392
395
  updateCustomStore("isTyping", { conversationID, value: false });
393
396
  }
394
397
  } else if (data.src === CUSTOM_MESSAGE_SRC.NO_SEAT_ONLINE || data.src === CUSTOM_MESSAGE_SRC.TIMEOUT || data.src === CUSTOM_MESSAGE_SRC.END) {
395
- updateCustomStore("isInSession", { conversationID, value: false });
396
- } else if (data.src === CUSTOM_MESSAGE_SRC.SESSION_RESTARTED) {
397
- updateCustomStore("isInSession", { conversationID, value: true });
398
+ updateCustomStore("canEndConversation", { conversationID, value: false });
398
399
  } else if (data.src === CUSTOM_MESSAGE_SRC.GET_FEEDBACK_MENU) {
399
400
  TUIStore.update(StoreName.CUSTOM, "feedbackTags", data.content.menu);
400
401
  }
@@ -15,7 +15,7 @@
15
15
  @onClose="closeDialog"
16
16
  title=""
17
17
  >
18
- <div style="height:100%;overflow-y: auto;">
18
+ <div style="height:100%;">
19
19
  <div class="dialog-title">
20
20
  <div class="dialog-title-tip">
21
21
  {{ props.payload.content.tip }}
@@ -24,28 +24,30 @@
24
24
  <Icon :src="iconClose" width="16px" height="16px"/>
25
25
  </div>
26
26
  </div>
27
- <div
28
- v-for="(item, index) in props.payload.content.inputVariables"
29
- :key="index"
30
- >
31
- <div v-if="item.formType == 0 && props.payload.nodeStatus == 0">
32
- <InputMobile :placeholder="item.placeholder" :variableValue="item.variableValue" :name="item.name" :isRequired="item.isRequired" @input-change="handleInputChange" :validator="item.isRequired == 1 && isValid(item.name)"/>
33
- </div>
34
- <div v-else-if="item.formType == 1 && props.payload.nodeStatus == 0">
35
- <RadioMobile :chooseItemList="item.chooseItemList" :name="item.name" :isRequired="item.isRequired" @input-change="handleInputChange" :validator="item.isRequired == 1 && isValid(item.name)"/>
36
- </div>
37
- <div v-else class="variable-value-container-mobile">
38
- <div class="variable-value-label-mobile">
39
- {{ item.name }}
27
+ <div class="dialog-show-content">
28
+ <div
29
+ v-for="(item, index) in props.payload.content.inputVariables"
30
+ :key="index"
31
+ >
32
+ <div v-if="item.formType == 0 && props.payload.nodeStatus == 0">
33
+ <InputMobile :placeholder="item.placeholder" :variableValue="item.variableValue" :name="item.name" :isRequired="item.isRequired" @input-change="handleInputChange" :validator="item.isRequired == 1 && isValid(item.name)"/>
40
34
  </div>
41
- <div class="variable-value">
42
- {{ item.variableValue == '' || item.variableValue == null ? mapValue[item.name] : item.variableValue}}
35
+ <div v-else-if="item.formType == 1 && props.payload.nodeStatus == 0">
36
+ <RadioMobile :chooseItemList="item.chooseItemList" :name="item.name" :isRequired="item.isRequired" @input-change="handleInputChange" :validator="item.isRequired == 1 && isValid(item.name)"/>
37
+ </div>
38
+ <div v-else class="variable-value-container-mobile">
39
+ <div class="variable-value-label-mobile">
40
+ {{ item.name }}
41
+ </div>
42
+ <div class="variable-value">
43
+ {{ item.variableValue == '' || item.variableValue == null ? mapValue[item.name] : item.variableValue}}
44
+ </div>
43
45
  </div>
44
46
  </div>
45
- </div>
46
- <div class="button-container" v-if="props.payload.nodeStatus === 0">
47
- <div class="button" @click="handleSendForm">
48
- {{ TUITranslateService.t("AIDesk.提交") }}
47
+ <div class="button-container" v-if="props.payload.nodeStatus === 0">
48
+ <div class="button" @click="handleSendForm">
49
+ {{ TUITranslateService.t("AIDesk.提交") }}
50
+ </div>
49
51
  </div>
50
52
  </div>
51
53
  </div>
@@ -163,6 +165,7 @@ export default {
163
165
  };
164
166
  emit('sendMessage', submitData);
165
167
  isSubmit.value = false;
168
+ closeDialog();
166
169
  };
167
170
  const handleInputChange = ({name,value}) => {
168
171
  mapValue.value[name] = value;
@@ -369,6 +372,12 @@ export default {
369
372
  color: rgba(153,153,153,1);
370
373
  }
371
374
  }
375
+
376
+ .dialog-show-content {
377
+ overflow-y: auto;
378
+ max-height: 68vh;
379
+ }
380
+
372
381
  .variable-value-container-mobile {
373
382
  padding: 16px;
374
383
  display: flex;
@@ -1,11 +1,15 @@
1
1
  <template>
2
2
  <div :class="['message-text-container', isPC && 'text-select']">
3
3
  <span
4
- v-for="(item, index) in data.text"
4
+ v-for="(item, index) in textMessageData.text"
5
5
  :key="index"
6
- >
6
+ >
7
+ <span v-if="item.name === 'text' && enableURLDetection === 1"
8
+ class="text"
9
+ v-html="item.text"
10
+ ></span>
7
11
  <span
8
- v-if="item.name === 'text'"
12
+ v-else-if="item.name === 'text'"
9
13
  class="text"
10
14
  >{{ item.text }}</span>
11
15
  <img
@@ -25,17 +29,23 @@ import {
25
29
  CUSTOM_BASIC_EMOJI_URL_MAPPING,
26
30
  } from '../../emoji-config';
27
31
  import { isPC } from '../../../../utils/env';
28
- const { watchEffect, ref } = vue;
32
+ import state from '../../../../utils/state.js';
33
+ const {ref, computed } = vue;
29
34
  interface IProps {
30
35
  content: Record<string, any>;
36
+ flow: string;
31
37
  }
32
38
  const props = withDefaults(defineProps<IProps>(), {
33
39
  content: () => ({}),
40
+ flow: 'in',
41
+ });
42
+ const enableURLDetection = ref(state.get('enableURLDetection'));
43
+ const linkColor = computed(() => {
44
+ return props.flow === 'out' && isPC ? '#fff' : '#0052d9';
34
45
  });
35
- const data = ref();
36
- watchEffect(() => {
37
- data.value = props.content;
38
- data.value.text?.forEach(
46
+ const textMessageData = computed(() => {
47
+ const contentCopy = JSON.parse(JSON.stringify(props.content));
48
+ contentCopy.text?.forEach(
39
49
  (item: {
40
50
  name: string;
41
51
  text?: string;
@@ -45,24 +55,22 @@ watchEffect(() => {
45
55
  }) => {
46
56
  if (item.name === 'img' && item?.type === 'custom') {
47
57
  if (!CUSTOM_BASIC_EMOJI_URL) {
48
- console.warn(
49
- 'CUSTOM_BASIC_EMOJI_URL is required for custom emoji, please check your CUSTOM_BASIC_EMOJI_URL.',
50
- );
51
- } else if (
52
- !item.emojiKey
53
- || !CUSTOM_BASIC_EMOJI_URL_MAPPING[item.emojiKey]
54
- ) {
55
- console.warn(
56
- 'emojiKey is required for custom emoji, please check your emojiKey.',
57
- );
58
+ console.warn('CUSTOM_BASIC_EMOJI_URL is required for custom emoji, please check your CUSTOM_BASIC_EMOJI_URL.');
59
+ } else if (!item.emojiKey || !CUSTOM_BASIC_EMOJI_URL_MAPPING[item.emojiKey]) {
60
+ console.warn('emojiKey is required for custom emoji, please check your emojiKey.');
58
61
  } else {
59
62
  item.src
60
63
  = CUSTOM_BASIC_EMOJI_URL
61
64
  + CUSTOM_BASIC_EMOJI_URL_MAPPING[item.emojiKey];
62
65
  }
66
+ } else if (item.name === 'text' && enableURLDetection.value) {
67
+ item.text = item.text.replace(/https?:\/\/[\w\-./?=&:#]+(?=[^\w\-./?=&:#]|$)/g, (url) => {
68
+ return `<a href="${url}" target="_blank" rel="noopener noreferrer" class="message-text-link" style="color: ${linkColor.value}; text-decoration: underline;">${url}</a>`;
69
+ }) || '';
63
70
  }
64
71
  },
65
72
  );
73
+ return contentCopy;
66
74
  });
67
75
  </script>
68
76
  <style lang="scss" scoped>
@@ -3,7 +3,7 @@
3
3
  <template v-for="(item, index) in props.toolbarButtonList">
4
4
  <ToolbarButtonHumanService v-if="item.presetId === TOOLBAR_BUTTON_TYPE.HUMAN_SERVICE && shouldRender(item) && !isInHumanService" :title="item.title" :icon="item.icon"/>
5
5
  <ToolbarButtonServiceRating v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.SERVICE_RATING && shouldRender(item) && isInHumanService" :title="item.title" :icon="item.icon"/>
6
- <ToolbarButtonEndHumanService v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.END_HUMAN_SERVICE && shouldRender(item) && ((item.displayFlag === 1 && isInSession) || isInHumanService)" :title="item.title" :icon="item.icon"/>
6
+ <ToolbarButtonEndHumanService v-else-if="item.presetId === TOOLBAR_BUTTON_TYPE.END_HUMAN_SERVICE && shouldRender(item) && ((item.displayFlag === 1 && canEndConversation) || isInHumanService)" :title="item.title" :icon="item.icon"/>
7
7
  <div v-else-if="shouldRender(item) && !item.presetId" :key="index"
8
8
  :class="['toolbar-button', isH5 ? 'toolbar-button-h5' : '']" @click="onClick(item, index)">
9
9
  <Icon v-if="item.icon" class="toolbar-button-icon" :file="item.icon" width="18px" height="18px"/>
@@ -39,7 +39,7 @@ const props = withDefaults(defineProps<IProps>(), {});
39
39
 
40
40
  const isInHumanService = ref(false);
41
41
  const currentConversation = ref<IConversationModel>();
42
- const isInSession = ref(true);
42
+ const canEndConversation = ref(false);
43
43
 
44
44
  onMounted(() => {
45
45
  TUIStore.watch(StoreName.CONV, {
@@ -47,7 +47,7 @@ onMounted(() => {
47
47
  });
48
48
  TUIStore.watch(StoreName.CUSTOM, {
49
49
  isInHumanService: onInHumanServiceUpdate,
50
- isInSession: onInSessionUpdate
50
+ canEndConversation: onCanEndConversationUpdate
51
51
  });
52
52
  });
53
53
 
@@ -57,7 +57,7 @@ onUnmounted(() => {
57
57
  });
58
58
  TUIStore.unwatch(StoreName.CUSTOM, {
59
59
  isInHumanService: onInHumanServiceUpdate,
60
- isInSession: onInSessionUpdate
60
+ canEndConversation: onCanEndConversationUpdate
61
61
  });
62
62
  });
63
63
 
@@ -71,9 +71,9 @@ const onInHumanServiceUpdate = (data: {conversationID: string, value: boolean})
71
71
  }
72
72
  };
73
73
 
74
- const onInSessionUpdate = (data: {conversationID: string, value: boolean}) => {
74
+ const onCanEndConversationUpdate = (data: {conversationID: string, value: boolean}) => {
75
75
  if (data && data.conversationID === currentConversation.value.conversationID) {
76
- isInSession.value = data.value;
76
+ canEndConversation.value = data.value;
77
77
  }
78
78
  }
79
79
 
@@ -119,6 +119,7 @@ function shouldRender(item: ToolbarButtonModel) {
119
119
  overflow-x: auto; /* 允许横向滚动 */
120
120
  scrollbar-width: none; /* Firefox 隐藏滚动条 */
121
121
  -ms-overflow-style: none; /* IE/Edge 隐藏滚动条 */
122
+ min-height: 28px;
122
123
  &::-webkit-scrollbar {
123
124
  display: none; /* Chrome 隐藏滚动条 */
124
125
  }
@@ -48,7 +48,7 @@ const onClick = () => {
48
48
  payload: {
49
49
  data: JSON.stringify({
50
50
  customerServicePlugin: 0,
51
- src: CUSTOM_MESSAGE_SRC.USER_END_SESSION,
51
+ src: CUSTOM_MESSAGE_SRC.USER_END_CONVERSATION,
52
52
  }),
53
53
  },
54
54
  needReadReceipt: isEnabledMessageReadReceiptGlobal(),
package/constant.ts CHANGED
@@ -25,7 +25,7 @@ export const CUSTOM_MESSAGE_SRC = {
25
25
  USER_SATISFACTION: '24',
26
26
  BOT_STATUS: '25',
27
27
  SEAT_STATUS: '26',
28
- USER_END_SESSION: '27',
28
+ USER_END_CONVERSATION: '27',
29
29
  ORDER:'28',
30
30
  ROBOT_MSG: '29',
31
31
  RICH_TEXT: '30',
@@ -37,7 +37,6 @@ export const CUSTOM_MESSAGE_SRC = {
37
37
  CONCURRENCY_LIMIT: '36',
38
38
  TIMEOUT_WARNING: '37',
39
39
  TRANSFER_TO_HUMAN: '39',
40
- SESSION_RESTARTED: '40',
41
40
  GET_FEEDBACK_MENU: '42',
42
41
  SEND_FEEDBACK: '43',
43
42
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tencentcloud/ai-desk-customer-vue",
3
- "version": "1.5.8",
3
+ "version": "1.5.9",
4
4
  "description": "Vue2/Vue3 UIKit for AI Desk",
5
5
  "main": "index",
6
6
  "keywords": [
package/server.ts CHANGED
@@ -13,8 +13,6 @@ import TUIChatEngine, {
13
13
  SendMessageParams,
14
14
  SendMessageOptions,
15
15
  TUIUserService,
16
- TUIStore,
17
- StoreName,
18
16
  } from '@tencentcloud/chat-uikit-engine';
19
17
  import Log from './utils/logger';
20
18
  import { version } from './package.json'
@@ -148,6 +146,7 @@ export default class TUICustomerServer {
148
146
 
149
147
  public async unInit() {
150
148
  this.isLoggedIn = false;
149
+ this.myProfile = { avatar: USER_DEFAULT_AVATAR };
151
150
  return TUIChatEngine.logout();
152
151
  }
153
152
 
@@ -280,7 +279,6 @@ export default class TUICustomerServer {
280
279
  }),
281
280
  },
282
281
  }, { onlineUserOnly: true });
283
- updateCustomStore("isInSession", { conversationID: params.conversationID, value: true });
284
282
  Log.w(`TUICustomerServer.activeServiceFlow src 7 sent`);
285
283
  }
286
284
  }