@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 +9 -0
- package/components/CustomerServiceChat/chat-header/index-web.vue +14 -7
- package/components/CustomerServiceChat/index-web.vue +8 -1
- package/components/CustomerServiceChat/message-list/index-web.vue +4 -3
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-multi-form/form-mobile.vue +29 -20
- package/components/CustomerServiceChat/message-list/message-elements/message-text.vue +26 -18
- package/components/CustomerServiceChat/message-toolbar-button/index.vue +7 -6
- package/components/CustomerServiceChat/message-toolbar-button/toolbar-button-end-human-service.vue +1 -1
- package/constant.ts +1 -2
- package/package.json +1 -1
- package/server.ts +1 -3
package/CHANGELOG.md
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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("
|
|
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%;
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
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
|
|
42
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
74
|
+
const onCanEndConversationUpdate = (data: {conversationID: string, value: boolean}) => {
|
|
75
75
|
if (data && data.conversationID === currentConversation.value.conversationID) {
|
|
76
|
-
|
|
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
|
}
|
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
|
-
|
|
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
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
|
}
|