@tencentcloud/ai-desk-customer-vue 1.5.8 → 1.5.10
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 +16 -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/marked.ts +21 -2
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-multi-form/form-mobile.vue +31 -16
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-rich-text.vue +57 -5
- 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
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
## 1.5.9 @2025.9.18
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
- 支持展示工作台快捷回复的视频。
|
|
5
|
+
- 固定底部弹窗的提交按钮,不随内容滚动,优化产品体验。
|
|
6
|
+
- 优化组件内超链接 的 title 和字体颜色。
|
|
7
|
+
|
|
8
|
+
## 1.5.9 @2025.9.8
|
|
9
|
+
|
|
10
|
+
### Features
|
|
11
|
+
- 支持识别文本消息中的 url 并可点击。
|
|
12
|
+
- 优化工具栏的【结束会话按钮】的显隐时机。
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
- 切换账号,新账号的资料可能异常问题。
|
|
16
|
+
|
|
1
17
|
## 1.5.8 @2025.8.26
|
|
2
18
|
|
|
3
19
|
### 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
|
-
|
|
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
|
}
|
|
@@ -11,10 +11,23 @@ export const marked = new Marked(
|
|
|
11
11
|
class="image-container"
|
|
12
12
|
onclick="onMarkdownImageClicked('${safeHref}')"
|
|
13
13
|
style="cursor:pointer;" >
|
|
14
|
-
<img src="${href}" alt="${text}" onload="
|
|
14
|
+
<img src="${href}" alt="${text}" onload="onMarkdownMediaLoad()"/>
|
|
15
15
|
</div>
|
|
16
16
|
`;
|
|
17
17
|
},
|
|
18
|
+
paragraph(text: string) {
|
|
19
|
+
if (text.startsWith('<video')) {
|
|
20
|
+
const videoSrc = extractVideoSrc(text) || '';
|
|
21
|
+
if (videoSrc) {
|
|
22
|
+
return `<div class="message-video">
|
|
23
|
+
<div class="message-video-box">
|
|
24
|
+
<video class="message-img video-web" src="${videoSrc}" onloadeddata="onMarkdownMediaLoad()" preload="auto" controls/>
|
|
25
|
+
</div>
|
|
26
|
+
</div>`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return text
|
|
30
|
+
},
|
|
18
31
|
link(this: any, href: string | null, title: string | null, text: string) {
|
|
19
32
|
if (href) {
|
|
20
33
|
// 匹配以 http:// 或 https:// 开头,所有 URL 主体字符,遇到第一个非主体字符(如中文括号、空格、表情符号等)时停止
|
|
@@ -24,7 +37,7 @@ export const marked = new Marked(
|
|
|
24
37
|
// 如果 text 里包含 url,我们就用 matchedUrl 作为 a 标签的值;否则用 text 作为值
|
|
25
38
|
isURLInText = true;
|
|
26
39
|
}
|
|
27
|
-
return `<a target="_blank" rel="noreferrer noopenner" class="message-marked_link" href="${matchedUrl || ''}" title="${title}">${isURLInText ? matchedUrl : text}</a>`;
|
|
40
|
+
return `<a target="_blank" rel="noreferrer noopenner" class="message-marked_link" href="${matchedUrl || ''}" title="${title || ''}">${isURLInText ? matchedUrl : text}</a>`;
|
|
28
41
|
});
|
|
29
42
|
if (ret === href) {
|
|
30
43
|
Log.w(`Unable to extract url, href:${href}`);
|
|
@@ -37,6 +50,12 @@ export const marked = new Marked(
|
|
|
37
50
|
},
|
|
38
51
|
);
|
|
39
52
|
|
|
53
|
+
const extractVideoSrc = (text: string) => {
|
|
54
|
+
const videoSrcRegex = /<video[^>]*src=["']([^"']*)["'][^>]*>/gi;
|
|
55
|
+
const result = videoSrcRegex.exec(text);
|
|
56
|
+
return result ? result[1] : null;
|
|
57
|
+
}
|
|
58
|
+
|
|
40
59
|
export const parseMarkdown = (text: string) => {
|
|
41
60
|
let ret = marked.parse(text);
|
|
42
61
|
return typeof ret === 'string' ? ret : '';
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
@onClose="closeDialog"
|
|
16
16
|
title=""
|
|
17
17
|
>
|
|
18
|
-
<div
|
|
18
|
+
<div class="dialog-container">
|
|
19
19
|
<div class="dialog-title">
|
|
20
20
|
<div class="dialog-title-tip">
|
|
21
21
|
{{ props.payload.content.tip }}
|
|
@@ -24,22 +24,24 @@
|
|
|
24
24
|
<Icon :src="iconClose" width="16px" height="16px"/>
|
|
25
25
|
</div>
|
|
26
26
|
</div>
|
|
27
|
-
<div
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
<
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
<
|
|
36
|
-
|
|
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)"/>
|
|
34
|
+
</div>
|
|
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)"/>
|
|
40
37
|
</div>
|
|
41
|
-
<div class="variable-value">
|
|
42
|
-
|
|
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
47
|
</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;
|
|
@@ -353,6 +356,12 @@ export default {
|
|
|
353
356
|
}
|
|
354
357
|
}
|
|
355
358
|
|
|
359
|
+
.dialog-container {
|
|
360
|
+
height: 100%;
|
|
361
|
+
display: flex;
|
|
362
|
+
flex-direction: column;
|
|
363
|
+
}
|
|
364
|
+
|
|
356
365
|
.dialog-title {
|
|
357
366
|
display: flex;
|
|
358
367
|
flex-direction: row;
|
|
@@ -369,6 +378,12 @@ export default {
|
|
|
369
378
|
color: rgba(153,153,153,1);
|
|
370
379
|
}
|
|
371
380
|
}
|
|
381
|
+
|
|
382
|
+
.dialog-show-content {
|
|
383
|
+
overflow-y: auto;
|
|
384
|
+
max-height: 68vh;
|
|
385
|
+
}
|
|
386
|
+
|
|
372
387
|
.variable-value-container-mobile {
|
|
373
388
|
padding: 16px;
|
|
374
389
|
display: flex;
|
|
@@ -27,8 +27,7 @@ interface Props {
|
|
|
27
27
|
payload: customerServicePayloadType;
|
|
28
28
|
}
|
|
29
29
|
export default {
|
|
30
|
-
components: {
|
|
31
|
-
},
|
|
30
|
+
components: {},
|
|
32
31
|
props: {
|
|
33
32
|
payload: {
|
|
34
33
|
type: Object as () => customerServicePayloadType,
|
|
@@ -41,12 +40,12 @@ export default {
|
|
|
41
40
|
const imageList = [];
|
|
42
41
|
const parsedContent = computed(() => {
|
|
43
42
|
// @ts-ignore
|
|
44
|
-
window.onMarkdownImageClicked = function(href:string) {
|
|
43
|
+
window.onMarkdownImageClicked = function(href: string) {
|
|
45
44
|
image.value = !image.value;
|
|
46
45
|
imageSrc.value = decodeURIComponent(href);
|
|
47
46
|
}
|
|
48
47
|
// @ts-ignore
|
|
49
|
-
window.
|
|
48
|
+
window.onMarkdownMediaLoad = function() {
|
|
50
49
|
emit('heightChanged');
|
|
51
50
|
}
|
|
52
51
|
return parseMarkdown(props.payload.content);
|
|
@@ -63,7 +62,7 @@ export default {
|
|
|
63
62
|
image,
|
|
64
63
|
imageSrc,
|
|
65
64
|
closeImage,
|
|
66
|
-
imageList
|
|
65
|
+
imageList,
|
|
67
66
|
};
|
|
68
67
|
},
|
|
69
68
|
};
|
|
@@ -93,4 +92,57 @@ export default {
|
|
|
93
92
|
overflow: hidden;
|
|
94
93
|
pointer-events: auto;
|
|
95
94
|
}
|
|
95
|
+
.message-video {
|
|
96
|
+
position: relative;
|
|
97
|
+
display: flex;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
overflow: hidden;
|
|
100
|
+
|
|
101
|
+
&-box {
|
|
102
|
+
max-width: min(calc(100vw - 180px), 300px);
|
|
103
|
+
font-size: 0;
|
|
104
|
+
|
|
105
|
+
video {
|
|
106
|
+
max-width: min(calc(100vw - 180px), 300px);
|
|
107
|
+
max-height: min(calc(100vw - 180px), 300px);
|
|
108
|
+
width: inherit;
|
|
109
|
+
height: inherit;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
.dialog-video {
|
|
114
|
+
position: fixed;
|
|
115
|
+
z-index: 12;
|
|
116
|
+
width: 100vw;
|
|
117
|
+
height: 100vh;
|
|
118
|
+
top: 0;
|
|
119
|
+
left: 0;
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-direction: column;
|
|
122
|
+
align-items: center;
|
|
123
|
+
|
|
124
|
+
&-close {
|
|
125
|
+
display: flex;
|
|
126
|
+
justify-content: flex-end;
|
|
127
|
+
background: #000;
|
|
128
|
+
width: 100%;
|
|
129
|
+
box-sizing: border-box;
|
|
130
|
+
padding: 10px;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&-box {
|
|
134
|
+
display: flex;
|
|
135
|
+
flex: 1;
|
|
136
|
+
max-height: 100%;
|
|
137
|
+
padding: 6rem;
|
|
138
|
+
box-sizing: border-box;
|
|
139
|
+
justify-content: center;
|
|
140
|
+
align-items: center;
|
|
141
|
+
|
|
142
|
+
video {
|
|
143
|
+
max-width: 100%;
|
|
144
|
+
max-height: 100%;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
96
148
|
</style>
|
|
@@ -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' ? '#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
|
}
|