@tencentcloud/ai-desk-customer-vue 1.6.0 → 1.6.3
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 +19 -0
- package/assets/arrow_down_icon.svg +1 -0
- package/assets/arrow_down_icon_white.svg +1 -0
- package/components/CustomerServiceChat/chat-header/index-web.vue +1 -5
- package/components/CustomerServiceChat/customer-queue-page/index.vue +138 -0
- package/components/CustomerServiceChat/emoji-config/default-emoji.ts +11 -0
- package/components/CustomerServiceChat/emoji-config/index.ts +14 -1
- package/components/CustomerServiceChat/index-web.vue +29 -7
- package/components/CustomerServiceChat/message-input/index-web.vue +38 -0
- package/components/CustomerServiceChat/message-input/message-input-button.vue +5 -0
- package/components/CustomerServiceChat/message-input/message-input-editor-web.vue +13 -9
- package/components/CustomerServiceChat/message-input-toolbar/emoji-dialog-mobile/emoji-dialog-mobile.vue +26 -46
- package/components/CustomerServiceChat/message-input-toolbar/emoji-picker/emoji-picker-dialog.vue +8 -38
- package/components/CustomerServiceChat/message-list/bottom-quick-order/index.vue +1 -0
- package/components/CustomerServiceChat/message-list/index-web.vue +7 -1
- package/components/CustomerServiceChat/message-list/message-elements/message-bubble-web.vue +33 -15
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/marked.ts +7 -6
- package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-stream.vue +1 -0
- package/components/CustomerServiceChat/message-list/scroll-button/index.vue +17 -9
- package/locales/en/TUIChat.ts +1 -1
- package/locales/en/aidesk.ts +4 -1
- package/locales/fil/TUIChat.ts +1 -1
- package/locales/fil/aidesk.ts +4 -1
- package/locales/id/TUIChat.ts +1 -1
- package/locales/id/aidesk.ts +4 -1
- package/locales/ja/TUIChat.ts +1 -1
- package/locales/ja/aidesk.ts +4 -1
- package/locales/ms/TUIChat.ts +1 -1
- package/locales/ms/aidesk.ts +4 -1
- package/locales/ru/TUIChat.ts +1 -1
- package/locales/ru/aidesk.ts +4 -1
- package/locales/th/TUIChat.ts +1 -1
- package/locales/th/aidesk.ts +4 -1
- package/locales/vi/TUIChat.ts +1 -1
- package/locales/vi/aidesk.ts +4 -1
- package/locales/zh_cn/aidesk.ts +4 -1
- package/locales/zh_tw/aidesk.ts +4 -1
- package/package.json +1 -1
- package/utils/utils.ts +1 -1
- package/assets/double-arrow.svg +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## 1.6.3 @2025.11.13
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
- 转人工排队阶段支持展示排队信息,提升用户体验。
|
|
5
|
+
- 转人工成功后支持展示人工客服的昵称和头像。
|
|
6
|
+
- 优化 markdown 图片尺寸。
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
- 没有消息时显示查看更多。
|
|
10
|
+
- 流式消息的字体跟主字体不一致的问题。
|
|
11
|
+
|
|
12
|
+
## 1.6.2 @2025.10.30
|
|
13
|
+
|
|
14
|
+
### Features
|
|
15
|
+
- 优化发送语音的产品体验。
|
|
16
|
+
- 优化 scroll-button 等组件样式。
|
|
17
|
+
- emoji 表情适配海外。
|
|
18
|
+
- 完善 markdown 解析。
|
|
19
|
+
|
|
1
20
|
## 1.6.0 @2025.10.21
|
|
2
21
|
|
|
3
22
|
### Features
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#444444"><path d="M480-344 240-584l56-56 184 184 184-184 56 56-240 240Z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#ffffff"><path d="M480-344 240-584l56-56 184 184 184-184 56 56-240 240Z"/></svg>
|
|
@@ -160,11 +160,7 @@ function setCurrentConversationName() {
|
|
|
160
160
|
if (props.headerConfig && props.headerConfig.title) {
|
|
161
161
|
currentConversationName.value = props.headerConfig.title;
|
|
162
162
|
} else {
|
|
163
|
-
|
|
164
|
-
currentConversationName.value = TUITranslateService.t('AIDesk.Hi,我是') + currentConversation.value.getShowName();
|
|
165
|
-
} else {
|
|
166
|
-
currentConversationName.value = currentConversation.value.getShowName() || '';
|
|
167
|
-
}
|
|
163
|
+
currentConversationName.value = currentConversation.value.getShowName() || '';
|
|
168
164
|
}
|
|
169
165
|
}
|
|
170
166
|
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="customer-queue">
|
|
3
|
+
<div class="customer-queue-header">
|
|
4
|
+
{{ TUITranslateService.t("AIDesk.排队等待中") }}...
|
|
5
|
+
</div>
|
|
6
|
+
<div class="customer-queue-info">
|
|
7
|
+
{{ TUITranslateService.t("AIDesk.排队等待话术") }}
|
|
8
|
+
</div>
|
|
9
|
+
<div class="customer-queue-container">
|
|
10
|
+
<div class="customer-queue-content-title">
|
|
11
|
+
{{ TUITranslateService.t("AIDesk.当前前方排队人数") }}
|
|
12
|
+
</div>
|
|
13
|
+
<div class="customer-queue-content-number">
|
|
14
|
+
{{ props.queueNumber }}
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
<div class="customer-queue-end-button">
|
|
18
|
+
<div :class="['customer-queue-end-button-content', isClicked ? 'button-clicked' : '']" @click="endQueuing">
|
|
19
|
+
{{ TUITranslateService.t("AIDesk.结束排队") }}
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
<script lang="ts" setup>
|
|
25
|
+
import vue from '../../../adapter-vue';
|
|
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';
|
|
30
|
+
const currentConversation = ref<IConversationModel>();
|
|
31
|
+
const isClicked = ref(false);
|
|
32
|
+
const props = defineProps({
|
|
33
|
+
queueNumber: {
|
|
34
|
+
type: Number,
|
|
35
|
+
default: 0
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
onMounted(() => {
|
|
39
|
+
TUIStore.watch(StoreName.CONV, {
|
|
40
|
+
currentConversation: onCurrentConversationUpdate,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
onUnmounted(() => {
|
|
45
|
+
TUIStore.unwatch(StoreName.CONV, {
|
|
46
|
+
currentConversation: onCurrentConversationUpdate,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const onCurrentConversationUpdate = (conversation: IConversationModel) => {
|
|
51
|
+
currentConversation.value = conversation;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const endQueuing = () => {
|
|
55
|
+
if (isClicked.value) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
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 });
|
|
70
|
+
}
|
|
71
|
+
</script>
|
|
72
|
+
<style lang="scss">
|
|
73
|
+
.customer-queue {
|
|
74
|
+
width: 100%;
|
|
75
|
+
height: 100%;
|
|
76
|
+
max-width: 100%;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
box-sizing: border-box;
|
|
79
|
+
display: flex;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
}
|
|
82
|
+
.customer-queue-header {
|
|
83
|
+
height: 52px;
|
|
84
|
+
background-color: #1C66E5;
|
|
85
|
+
color: white;
|
|
86
|
+
line-height: 52px;
|
|
87
|
+
padding-left: 20px;
|
|
88
|
+
}
|
|
89
|
+
.customer-queue-info {
|
|
90
|
+
padding: 20px;
|
|
91
|
+
color: #666;
|
|
92
|
+
}
|
|
93
|
+
.customer-queue-container {
|
|
94
|
+
flex: 1;
|
|
95
|
+
display: flex;
|
|
96
|
+
flex-direction: column;
|
|
97
|
+
align-items: center;
|
|
98
|
+
justify-content: center;
|
|
99
|
+
color: #1C66E5;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.customer-queue-content-title {
|
|
103
|
+
font-size: 20px;
|
|
104
|
+
margin-bottom: 10px;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.customer-queue-content-number {
|
|
108
|
+
font-size: 40px;
|
|
109
|
+
font-weight: bold;
|
|
110
|
+
}
|
|
111
|
+
.customer-queue-end-button {
|
|
112
|
+
color: white;
|
|
113
|
+
padding: 8px 23px;
|
|
114
|
+
border-radius: 10px;
|
|
115
|
+
}
|
|
116
|
+
.customer-queue-end-button {
|
|
117
|
+
display: flex;
|
|
118
|
+
align-items: center;
|
|
119
|
+
justify-content: center;
|
|
120
|
+
margin-bottom: 20px;
|
|
121
|
+
&-content {
|
|
122
|
+
background: #1c66e5;
|
|
123
|
+
color: white;
|
|
124
|
+
padding: 8px 23px;
|
|
125
|
+
border-radius: 10px;
|
|
126
|
+
width: fit-content;
|
|
127
|
+
min-width: 70px;
|
|
128
|
+
cursor: pointer;
|
|
129
|
+
display: flex;
|
|
130
|
+
justify-content: center;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
.button-clicked {
|
|
134
|
+
cursor: default;
|
|
135
|
+
background: #cccccc;
|
|
136
|
+
color: #666666;
|
|
137
|
+
}
|
|
138
|
+
</style>
|
|
@@ -80,6 +80,17 @@ export const DEFAULT_BASIC_EMOJI_URL_MAPPING: Record<string, string> = {
|
|
|
80
80
|
'[TUIEmoji_Like]': 'emoji_61@2x.png',
|
|
81
81
|
};
|
|
82
82
|
|
|
83
|
+
export const EXCLUDED_EMOJI_LIST = [
|
|
84
|
+
'[TUIEmoji_Pig]',
|
|
85
|
+
'[TUIEmoji_Rich]',
|
|
86
|
+
'[TUIEmoji_Fortune]',
|
|
87
|
+
'[TUIEmoji_857]',
|
|
88
|
+
'[TUIEmoji_666]',
|
|
89
|
+
'[TUIEmoji_Prohibit]',
|
|
90
|
+
'[TUIEmoji_Convinced]',
|
|
91
|
+
'[TUIEmoji_Knife]',
|
|
92
|
+
];
|
|
93
|
+
|
|
83
94
|
export const BIG_EMOJI_GROUP_LIST: IEmojiGroupList = [
|
|
84
95
|
{
|
|
85
96
|
emojiGroupID: 1,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
|
|
2
2
|
import { CUSTOM_BASIC_EMOJI_URL, CUSTOM_BIG_EMOJI_URL, CUSTOM_BASIC_EMOJI_URL_MAPPING, CUSTOM_BIG_EMOJI_GROUP_LIST } from './custom-emoji';
|
|
3
|
-
import { DEFAULT_BASIC_EMOJI_URL, BIG_EMOJI_GROUP_LIST, DEFAULT_BASIC_EMOJI_URL_MAPPING, BASIC_EMOJI_NAME_TO_KEY_MAPPING, DEFAULT_BIG_EMOJI_URL } from './default-emoji';
|
|
3
|
+
import { DEFAULT_BASIC_EMOJI_URL, BIG_EMOJI_GROUP_LIST, DEFAULT_BASIC_EMOJI_URL_MAPPING, BASIC_EMOJI_NAME_TO_KEY_MAPPING, DEFAULT_BIG_EMOJI_URL, EXCLUDED_EMOJI_LIST } from './default-emoji';
|
|
4
4
|
import { default as emojiCNLocales } from './locales/zh_cn';
|
|
5
5
|
import { default as emojiENLocales } from './locales/en';
|
|
6
6
|
import { IEmojiGroupList } from '../../../interface';
|
|
@@ -126,6 +126,17 @@ const parseTextToRenderArray = (text: string): Array<{ type: 'text' | 'image'; c
|
|
|
126
126
|
return result;
|
|
127
127
|
};
|
|
128
128
|
|
|
129
|
+
const filterEmojisInNonChineseEnv = (list: string[]) => {
|
|
130
|
+
const language = state.get('currentLanguage') || 'zh';
|
|
131
|
+
if (!(language === 'zh' || language === 'zh_tw')) {
|
|
132
|
+
return list.filter((item) => {
|
|
133
|
+
return !EXCLUDED_EMOJI_LIST.includes(item);
|
|
134
|
+
});
|
|
135
|
+
} else {
|
|
136
|
+
return list;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
129
140
|
export {
|
|
130
141
|
EMOJI_GROUP_LIST,
|
|
131
142
|
CUSTOM_BIG_EMOJI_URL,
|
|
@@ -138,4 +149,6 @@ export {
|
|
|
138
149
|
transformTextWithKeysToEmojiNames,
|
|
139
150
|
transformTextWithEmojiNamesToKeys,
|
|
140
151
|
emojiConfig,
|
|
152
|
+
BASIC_EMOJI_URL,
|
|
153
|
+
filterEmojisInNonChineseEnv,
|
|
141
154
|
};
|
|
@@ -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"
|
|
4
|
+
v-if="currentConversationID && (!props.showQueuePage || queueNumber < 0 || queueNumber === undefined)"
|
|
5
5
|
:class="['tui-chat', !isPC && 'tui-chat-h5']"
|
|
6
6
|
>
|
|
7
7
|
<ChatHeader
|
|
@@ -94,6 +94,9 @@
|
|
|
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"/>
|
|
99
|
+
</div>
|
|
97
100
|
</div>
|
|
98
101
|
</template>
|
|
99
102
|
<script lang="ts" setup>
|
|
@@ -122,6 +125,7 @@ import state from '../../utils/state.js';
|
|
|
122
125
|
import { switchReadStatus,isNonEmptyObject } from '../../utils/utils';
|
|
123
126
|
import { getCountryForTimezone } from 'countries-and-timezones';
|
|
124
127
|
import FeedbackModal from './feedback-modal/index.vue';
|
|
128
|
+
import CustomerQueuePage from './customer-queue-page/index.vue';
|
|
125
129
|
const { ref, onMounted, onUnmounted, computed } = vue;
|
|
126
130
|
|
|
127
131
|
interface IProps {
|
|
@@ -134,12 +138,13 @@ interface IProps {
|
|
|
134
138
|
toolbarButtonList?: ToolbarButtonModel[];
|
|
135
139
|
showAvatar?: number;
|
|
136
140
|
robotAvatar?: string;
|
|
137
|
-
|
|
141
|
+
memberAvatar?: string;
|
|
138
142
|
userAvatar?: string;
|
|
139
143
|
showNickName?: number;
|
|
140
144
|
robotNickName?: string;
|
|
141
|
-
|
|
145
|
+
memberNickName?: string;
|
|
142
146
|
userNickName?: string;
|
|
147
|
+
enableUnifiedMemberProfile?: number;
|
|
143
148
|
inputToolbarList?: InputToolbarModel[];
|
|
144
149
|
showReadStatus?: number;
|
|
145
150
|
showTyping?: number;
|
|
@@ -151,6 +156,7 @@ interface IProps {
|
|
|
151
156
|
enableURLDetection?: number;
|
|
152
157
|
headerConfig?: IHeaderConfig;
|
|
153
158
|
enableSendingAudio?: number;
|
|
159
|
+
showQueuePage?: number;
|
|
154
160
|
}
|
|
155
161
|
|
|
156
162
|
const emits = defineEmits(['closeChat']);
|
|
@@ -170,6 +176,7 @@ const currentLanguage = ref('');
|
|
|
170
176
|
const languageForShowList = ref<Array<string>>([]);
|
|
171
177
|
const feedbackModalRef = ref();
|
|
172
178
|
const showFeedbackModal = ref(false);
|
|
179
|
+
const queueNumber = ref(-1);
|
|
173
180
|
let timezone = '';
|
|
174
181
|
let countryID = '';
|
|
175
182
|
const props = withDefaults(defineProps<IProps>(), {
|
|
@@ -182,12 +189,13 @@ const props = withDefaults(defineProps<IProps>(), {
|
|
|
182
189
|
showReadStatus: 1,
|
|
183
190
|
showAvatar: 1,
|
|
184
191
|
robotAvatar: '',
|
|
185
|
-
|
|
192
|
+
memberAvatar: '',
|
|
186
193
|
userAvatar: '',
|
|
187
194
|
showNickName: 0,
|
|
188
195
|
robotNickName: '',
|
|
189
|
-
|
|
196
|
+
memberNickName: '',
|
|
190
197
|
userNickName: '',
|
|
198
|
+
enableUnifiedMemberProfile: 1,
|
|
191
199
|
showTyping: 0,
|
|
192
200
|
enableMultilingual: 0,
|
|
193
201
|
enableFeedback: 0,
|
|
@@ -195,6 +203,7 @@ const props = withDefaults(defineProps<IProps>(), {
|
|
|
195
203
|
langList: () => [],
|
|
196
204
|
enableURLDetection: 0,
|
|
197
205
|
enableSendingAudio: 0,
|
|
206
|
+
showQueuePage: 0,
|
|
198
207
|
});
|
|
199
208
|
|
|
200
209
|
const loginCustomerUIKit = () => {
|
|
@@ -290,11 +299,12 @@ const setAvatarNickName = () => {
|
|
|
290
299
|
showAvatar: props.showAvatar,
|
|
291
300
|
showNickName: props.showNickName,
|
|
292
301
|
userAvatar: props.userAvatar,
|
|
293
|
-
|
|
302
|
+
memberAvatar: props.memberAvatar,
|
|
294
303
|
robotAvatar: props.robotAvatar,
|
|
295
304
|
userNickName: props.userNickName,
|
|
296
|
-
|
|
305
|
+
memberNickName: props.memberNickName,
|
|
297
306
|
robotNickName: props.robotNickName,
|
|
307
|
+
enableUnifiedMemberProfile: props.enableUnifiedMemberProfile,
|
|
298
308
|
});
|
|
299
309
|
}
|
|
300
310
|
|
|
@@ -346,6 +356,9 @@ onMounted(() => {
|
|
|
346
356
|
TUIStore.watch(StoreName.CHAT, {
|
|
347
357
|
quoteMessage: onQuoteMessageUpdated,
|
|
348
358
|
});
|
|
359
|
+
TUIStore.watch(StoreName.CUSTOM, {
|
|
360
|
+
isQueuing: onIsQueuingUpdate,
|
|
361
|
+
});
|
|
349
362
|
});
|
|
350
363
|
|
|
351
364
|
onUnmounted(() => {
|
|
@@ -358,6 +371,9 @@ onUnmounted(() => {
|
|
|
358
371
|
TUIStore.unwatch(StoreName.CHAT, {
|
|
359
372
|
quoteMessage: onQuoteMessageUpdated,
|
|
360
373
|
});
|
|
374
|
+
TUIStore.unwatch(StoreName.CUSTOM, {
|
|
375
|
+
isQueuing: onIsQueuingUpdate,
|
|
376
|
+
});
|
|
361
377
|
});
|
|
362
378
|
|
|
363
379
|
const isInputToolbarShow = computed<boolean>(() => {
|
|
@@ -501,6 +517,12 @@ function onDislike(messageInfo: Object) {
|
|
|
501
517
|
showFeedbackModal.value = true;
|
|
502
518
|
feedbackModalRef.value.onDislike(messageInfo);
|
|
503
519
|
}
|
|
520
|
+
|
|
521
|
+
function onIsQueuingUpdate(data: {conversationID: string, value: number}) {
|
|
522
|
+
if (data && data.conversationID === currentConversationID.value) {
|
|
523
|
+
queueNumber.value = data.value;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
504
526
|
</script>
|
|
505
527
|
|
|
506
528
|
<style scoped lang="scss" src="./style/index.scss">
|
|
@@ -69,6 +69,7 @@ import audioIcon from '../../../assets/audio-blue.svg';
|
|
|
69
69
|
import keyboardIcon from '../../../assets/keyboard-icon.svg';
|
|
70
70
|
import { INPUT_TOOLBAR_TYPE } from '../../../constant';
|
|
71
71
|
import { onUnmounted } from 'vue';
|
|
72
|
+
import Log from '../../../utils/logger';
|
|
72
73
|
const { ref, onMounted, onBeforeUnmount, computed } = vue;
|
|
73
74
|
|
|
74
75
|
const props = defineProps({
|
|
@@ -111,23 +112,33 @@ const showSendButton = ref(false);
|
|
|
111
112
|
const isInHumanService = ref(false);
|
|
112
113
|
let quoteMessageCloudCustomData:string = '';
|
|
113
114
|
const isInAudioMode = ref(false);
|
|
115
|
+
let hasAudioStream = false;
|
|
116
|
+
let audioStream = null;
|
|
114
117
|
|
|
115
118
|
onMounted(() => {
|
|
116
119
|
// document.addEventListener('click', handleClick);
|
|
117
120
|
TUIStore.watch(StoreName.CUSTOM, {
|
|
118
121
|
isInHumanService: onInHumanServiceUpdate,
|
|
119
122
|
});
|
|
123
|
+
document.addEventListener('visibilitychange', handleVisibilityChange);
|
|
120
124
|
});
|
|
121
125
|
|
|
122
126
|
onUnmounted(() => {
|
|
123
127
|
TUIStore.unwatch(StoreName.CUSTOM, {
|
|
124
128
|
isInHumanService: onInHumanServiceUpdate,
|
|
125
129
|
});
|
|
130
|
+
stopAudioStream();
|
|
131
|
+
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
|
126
132
|
});
|
|
127
133
|
|
|
128
134
|
const onInHumanServiceUpdate = (data: {conversationID: string, value: boolean}) => {
|
|
129
135
|
if (data && data.conversationID === currentConversation.value.conversationID) {
|
|
130
136
|
isInHumanService.value = data.value;
|
|
137
|
+
if (!isInHumanService.value) {
|
|
138
|
+
isInAudioMode.value = false;
|
|
139
|
+
placeholder.value = TUITranslateService.t('TUIChat.请输入消息');
|
|
140
|
+
stopAudioStream();
|
|
141
|
+
}
|
|
131
142
|
}
|
|
132
143
|
};
|
|
133
144
|
|
|
@@ -203,9 +214,28 @@ const toolShow = () => {
|
|
|
203
214
|
const blurToolAndEmojiH5 = () =>{
|
|
204
215
|
emit('blurToolAndEmojiH5');
|
|
205
216
|
}
|
|
217
|
+
const getAudioStream = async () => {
|
|
218
|
+
try {
|
|
219
|
+
stopAudioStream();
|
|
220
|
+
audioStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
221
|
+
hasAudioStream = true;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
Log.e('Error accessing audio stream:', err);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const stopAudioStream = () => {
|
|
227
|
+
if (audioStream) {
|
|
228
|
+
audioStream.getTracks().forEach(track => track.stop());
|
|
229
|
+
audioStream = null;
|
|
230
|
+
hasAudioStream = false;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
206
233
|
|
|
207
234
|
const audioShow = () => {
|
|
208
235
|
isInAudioMode.value = !isInAudioMode.value;
|
|
236
|
+
if (isInAudioMode.value && !hasAudioStream) {
|
|
237
|
+
getAudioStream();
|
|
238
|
+
}
|
|
209
239
|
placeholder.value = isInAudioMode.value ? TUITranslateService.t('TUIChat.按住说话') : TUITranslateService.t('TUIChat.请输入消息');
|
|
210
240
|
emit('blurToolAndEmojiH5');
|
|
211
241
|
}
|
|
@@ -228,6 +258,14 @@ const shouldShowAudio = computed(() => {
|
|
|
228
258
|
return isH5 && isInHumanService.value && (props.enableSendingAudio === 1 || (props.inputToolbarList !== undefined && props.inputToolbarList.some(item => item.presetId === INPUT_TOOLBAR_TYPE.AUDIO && item.isEnabled === 1)));
|
|
229
259
|
});
|
|
230
260
|
|
|
261
|
+
const handleVisibilityChange = () => {
|
|
262
|
+
if (document.visibilityState === 'hidden') {
|
|
263
|
+
stopAudioStream();
|
|
264
|
+
} else if (document.visibilityState === 'visible' && isInAudioMode.value) {
|
|
265
|
+
getAudioStream();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
231
269
|
defineExpose({
|
|
232
270
|
insertEmoji,
|
|
233
271
|
reEdit,
|
|
@@ -155,6 +155,7 @@ let recordTimer: any = null;
|
|
|
155
155
|
const audioWaveBars = ref(Array(24).fill({ height: 6 }));
|
|
156
156
|
let audioWaveAnimation = -1;
|
|
157
157
|
let pressStartTime: any = null;
|
|
158
|
+
const hasMicPermission = ref(false);
|
|
158
159
|
|
|
159
160
|
function onCurrentConversationIDUpdated(conversationID: string) {
|
|
160
161
|
if (currentConversationID.value !== conversationID) {
|
|
@@ -795,9 +796,11 @@ watch(
|
|
|
795
796
|
);
|
|
796
797
|
async function initRecorder() {
|
|
797
798
|
clearInterval(recordTimer);
|
|
798
|
-
recordTimer =
|
|
799
|
+
recordTimer = null;
|
|
799
800
|
recordTime.value = 0;
|
|
800
801
|
await recorder.value.stop();
|
|
802
|
+
isRecording.value = false;
|
|
803
|
+
recordTip.value = TUITranslateService.t("TUIChat.松开发送");
|
|
801
804
|
cancelAnimationFrame(audioWaveAnimation);
|
|
802
805
|
}
|
|
803
806
|
|
|
@@ -826,14 +829,19 @@ const startRecording = async(e) => {
|
|
|
826
829
|
isRecording.value = true;
|
|
827
830
|
pressStartTime = new Date();
|
|
828
831
|
recordCancel.value = false;
|
|
829
|
-
Recorder.getPermission().then(
|
|
832
|
+
Recorder.getPermission().then(() => {
|
|
833
|
+
hasMicPermission.value = true;
|
|
830
834
|
if (!isRecording.value) {
|
|
831
835
|
return;
|
|
832
836
|
}
|
|
833
|
-
if (recordTimer !==
|
|
837
|
+
if (recordTimer !== null) {
|
|
834
838
|
initRecorder();
|
|
835
839
|
}
|
|
840
|
+
// 提前获取音频流,避免开始录音时启动过长
|
|
836
841
|
recorder.value.start().then(() => {
|
|
842
|
+
if (!isRecording.value) {
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
837
845
|
updateRecordWave();
|
|
838
846
|
startRecordY = getRecordY(e);
|
|
839
847
|
recordTimer = setInterval(() => {
|
|
@@ -841,7 +849,6 @@ const startRecording = async(e) => {
|
|
|
841
849
|
}, 1000);
|
|
842
850
|
}, (error) => {
|
|
843
851
|
Log.e(`Error in recording ${error.name} : ${error.message}`);
|
|
844
|
-
isRecording.value = false;
|
|
845
852
|
let toastMessage = `${error.name} : ${error.message}`;
|
|
846
853
|
Toast({
|
|
847
854
|
message:toastMessage,
|
|
@@ -851,7 +858,6 @@ const startRecording = async(e) => {
|
|
|
851
858
|
initRecorder();
|
|
852
859
|
});
|
|
853
860
|
}, (error) => {
|
|
854
|
-
isRecording.value = false;
|
|
855
861
|
initRecorder();
|
|
856
862
|
Toast({
|
|
857
863
|
message:TUITranslateService.t("TUIChat.请检查麦克风访问权限"),
|
|
@@ -864,11 +870,10 @@ const startRecording = async(e) => {
|
|
|
864
870
|
};
|
|
865
871
|
|
|
866
872
|
const stopRecording = async () => {
|
|
867
|
-
isRecording.value = false;
|
|
868
873
|
const duration = recordTime.value;
|
|
869
874
|
initRecorder();
|
|
870
875
|
const pressDuration = Date.now() - pressStartTime;
|
|
871
|
-
if (pressDuration < 1000) {
|
|
876
|
+
if (pressDuration < 1000 && hasMicPermission.value) {
|
|
872
877
|
// 按压时间过短,还没开始录音就结束
|
|
873
878
|
Toast({
|
|
874
879
|
message: TUITranslateService.t("TUIChat.按压时间过短,请按压超过1秒"),
|
|
@@ -1179,8 +1184,7 @@ defineExpose({
|
|
|
1179
1184
|
overflow: hidden;
|
|
1180
1185
|
right: 0;
|
|
1181
1186
|
bottom: 0;
|
|
1182
|
-
background: linear-gradient(180deg,
|
|
1183
|
-
background: linear-gradient(180deg, color(display-p3 1 1 1 / 0.00) 0%, color(display-p3 1 1 1) 64.35%);
|
|
1187
|
+
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 64.35%);
|
|
1184
1188
|
display: flex;
|
|
1185
1189
|
flex-direction: column;
|
|
1186
1190
|
justify-content: flex-end;
|
|
@@ -1,60 +1,54 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
{{ TUITranslateService.t("发送") }}
|
|
21
|
-
</div>
|
|
22
|
-
</div>
|
|
2
|
+
<div class="emojiDialog">
|
|
3
|
+
<ul ref="emojiPickerListRef" class="emojiDialogList">
|
|
4
|
+
<li
|
|
5
|
+
v-for="(childrenItem, childrenIndex) in currentEmojiList"
|
|
6
|
+
:key="childrenIndex"
|
|
7
|
+
class="emojiDialogList-item"
|
|
8
|
+
@click="select(childrenItem, childrenIndex)"
|
|
9
|
+
>
|
|
10
|
+
<img
|
|
11
|
+
class="emoji"
|
|
12
|
+
:src="BASIC_EMOJI_URL + BASIC_EMOJI_URL_MAPPING[childrenItem]"
|
|
13
|
+
>
|
|
14
|
+
</li>
|
|
15
|
+
</ul>
|
|
16
|
+
<div class="sendButtonContainer">
|
|
17
|
+
<div class="sendButton" @click="sendMessage">
|
|
18
|
+
{{ TUITranslateService.t("发送") }}
|
|
19
|
+
</div>
|
|
23
20
|
</div>
|
|
24
|
-
|
|
21
|
+
</div>
|
|
25
22
|
</template>
|
|
26
23
|
<script lang="ts" setup>
|
|
27
24
|
import vue from '../../../../adapter-vue';
|
|
28
|
-
import { IEmojiGroupList
|
|
25
|
+
import { IEmojiGroupList } from '../../../../interface';
|
|
29
26
|
const { ref, onMounted, onUnmounted } = vue;
|
|
30
27
|
import {
|
|
31
|
-
TUIChatService,
|
|
32
28
|
TUIStore,
|
|
33
29
|
StoreName,
|
|
34
30
|
IConversationModel,
|
|
35
31
|
TUITranslateService,
|
|
36
|
-
SendMessageParams,
|
|
37
32
|
} from '@tencentcloud/chat-uikit-engine';
|
|
38
33
|
import {
|
|
39
34
|
EMOJI_GROUP_LIST,
|
|
40
35
|
BASIC_EMOJI_URL_MAPPING,
|
|
41
36
|
convertKeyToEmojiName,
|
|
37
|
+
BASIC_EMOJI_URL,
|
|
38
|
+
filterEmojisInNonChineseEnv,
|
|
42
39
|
} from '../../emoji-config';
|
|
43
|
-
import { EMOJI_TYPE } from '.././../../../constant';
|
|
44
|
-
import { isPC, } from '../../../../utils/env';
|
|
45
40
|
|
|
46
41
|
const emits = defineEmits(['insertEmoji', 'onClose', 'sendMessage']);
|
|
47
42
|
const list = ref<IEmojiGroupList>(initEmojiList());
|
|
48
43
|
const currentConversation = ref();
|
|
49
44
|
const emojiPickerListRef = ref();
|
|
50
|
-
const currentTabIndex = ref<number>(0);
|
|
51
45
|
const currentEmojiList = ref<string[]>(list?.value[0]?.list);
|
|
52
|
-
const currentTabItem = ref<IEmojiGroup>(list?.value[0]);
|
|
53
46
|
|
|
54
47
|
onMounted(() => {
|
|
55
48
|
TUIStore.watch(StoreName.CONV, {
|
|
56
49
|
currentConversation: onCurrentConversationUpdate,
|
|
57
50
|
});
|
|
51
|
+
currentEmojiList.value = filterEmojisInNonChineseEnv(list.value[0].list);
|
|
58
52
|
});
|
|
59
53
|
|
|
60
54
|
onUnmounted(() => {
|
|
@@ -66,24 +60,10 @@ onUnmounted(() => {
|
|
|
66
60
|
const select = (item: any, index: number) => {
|
|
67
61
|
const options: any = {
|
|
68
62
|
emoji: { key: item, name: convertKeyToEmojiName(item) },
|
|
69
|
-
type:
|
|
63
|
+
type: 'basic',
|
|
64
|
+
url : BASIC_EMOJI_URL + BASIC_EMOJI_URL_MAPPING[item],
|
|
70
65
|
};
|
|
71
|
-
|
|
72
|
-
case EMOJI_TYPE.BASIC:
|
|
73
|
-
options.url = currentTabItem?.value?.url + BASIC_EMOJI_URL_MAPPING[item];
|
|
74
|
-
emits('insertEmoji', options);
|
|
75
|
-
|
|
76
|
-
break;
|
|
77
|
-
case EMOJI_TYPE.BIG:
|
|
78
|
-
// sendFaceMessage(index, currentTabItem.value);
|
|
79
|
-
break;
|
|
80
|
-
case EMOJI_TYPE.CUSTOM:
|
|
81
|
-
// sendFaceMessage(index, currentTabItem.value);
|
|
82
|
-
break;
|
|
83
|
-
default:
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
isPC && emits('onClose');
|
|
66
|
+
emits('insertEmoji', options);
|
|
87
67
|
};
|
|
88
68
|
|
|
89
69
|
function sendMessage(){
|