@tencentcloud/ai-desk-customer-vue 1.5.1 → 1.5.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/README.md +316 -71
  3. package/components/CustomerServiceChat/emoji-config/index.ts +3 -2
  4. package/components/CustomerServiceChat/index-web.vue +17 -4
  5. package/components/CustomerServiceChat/message-input/index-web.vue +9 -2
  6. package/components/CustomerServiceChat/message-input/message-input-editor-web.vue +11 -0
  7. package/components/CustomerServiceChat/message-input/message-input-quote/index.vue +31 -6
  8. package/components/CustomerServiceChat/message-input-toolbar/file-upload/index.vue +14 -1
  9. package/components/CustomerServiceChat/message-input-toolbar/image-upload/index.vue +26 -2
  10. package/components/CustomerServiceChat/message-input-toolbar/video-upload/index.vue +14 -1
  11. package/components/CustomerServiceChat/message-list/bottom-quick-order/index.vue +201 -0
  12. package/components/CustomerServiceChat/message-list/index-web.vue +46 -0
  13. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/marked.ts +6 -1
  14. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-desk.vue +1 -1
  15. package/components/CustomerServiceChat/message-list/message-elements/message-desk/message-desk-elements/message-product-card.vue +140 -87
  16. package/components/CustomerServiceChat/message-list/message-elements/message-quote/index-web.vue +18 -9
  17. package/components/CustomerServiceChat/style/h5.scss +1 -0
  18. package/components/CustomerServiceChat/utils/sendMessage.ts +5 -0
  19. package/constant.ts +3 -3
  20. package/interface.ts +15 -1
  21. package/locales/en/TUIChat.ts +2 -1
  22. package/locales/en/aidesk.ts +4 -1
  23. package/locales/fil/TUIChat.ts +1 -0
  24. package/locales/fil/aidesk.ts +3 -0
  25. package/locales/id/TUIChat.ts +161 -160
  26. package/locales/id/aidesk.ts +3 -0
  27. package/locales/ja/TUIChat.ts +162 -161
  28. package/locales/ja/aidesk.ts +3 -0
  29. package/locales/ms/TUIChat.ts +162 -161
  30. package/locales/ms/aidesk.ts +3 -0
  31. package/locales/ru/TUIChat.ts +155 -154
  32. package/locales/ru/aidesk.ts +3 -0
  33. package/locales/th/TUIChat.ts +162 -161
  34. package/locales/th/aidesk.ts +3 -0
  35. package/locales/vi/TUIChat.ts +153 -152
  36. package/locales/vi/aidesk.ts +3 -0
  37. package/locales/zh_cn/TUIChat.ts +1 -0
  38. package/locales/zh_cn/aidesk.ts +3 -0
  39. package/locales/zh_tw/TUIChat.ts +1 -0
  40. package/locales/zh_tw/aidesk.ts +3 -0
  41. package/package.json +4 -6
  42. package/server.ts +42 -7
  43. package/utils/utils.ts +60 -0
@@ -2,38 +2,58 @@
2
2
  <div
3
3
  class="message-product-card"
4
4
  >
5
- <image
6
- v-if="isApp"
7
- class="product-img"
8
- :src="props.payload.content.pic"
9
- />
10
- <img
11
- v-else
12
- class="product-img"
13
- :src="props.payload.content.pic"
14
- >
15
- <div class="product-card-information">
16
- <div class="product-card-title">
17
- {{ props.payload.content.header }}
5
+ <div v-for="(item,index) in topCustomField" :key="index" class="product-card-custom-field">
6
+ <div class="custom-field-line">
7
+ <div v-if="item.name" class="order-name">
8
+ {{ item.name }}:
9
+ </div>
10
+ <div class="order-value">
11
+ {{ item.value }}
12
+ </div>
18
13
  </div>
19
- <div class="product-card-description-block">
14
+ </div>
15
+ <div class="product-card-main">
16
+ <img
17
+ v-if="props.payload.content.pic"
18
+ class="product-img"
19
+ :src="props.payload.content.pic"
20
+ >
21
+ <div class="product-card-information">
22
+ <div class="product-card-title">
23
+ {{ props.payload.content.header }}
24
+ </div>
25
+ <div class="product-card-description-block">
20
26
  <div class="product-card-description">
21
- {{ props.payload.content.desc }}
27
+ {{ props.payload.content.desc }}
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+
33
+ <div v-for="(item,index) in bottomCustomField" :key="index" class="product-card-custom-field">
34
+ <div class="custom-field-line">
35
+ <div v-if="item.name" class="order-name">
36
+ {{ item.name }}:
37
+ </div>
38
+ <div class="order-value">
39
+ {{ item.value }}
22
40
  </div>
23
- <div class="product-card-link" @click="jumpProductCard">
24
- {{TUITranslateService.t("AIDesk.跳转")}}
25
- </div>
26
41
  </div>
27
42
  </div>
43
+ <div class="product-card-link" @click="jumpProductCard">
44
+ {{ TUITranslateService.t("AIDesk.跳转") }}
45
+ </div>
28
46
  </div>
29
47
  </template>
30
48
 
31
49
  <script lang="ts">
50
+ import vue from '../../../../../../adapter-vue';
32
51
  import { customerServicePayloadType } from '../../../../../../interface';
33
52
  import { isApp } from '../../../../../../utils/env';
34
- import {TUITranslateService} from '@tencentcloud/chat-uikit-engine';
35
- // eslint-disable-next-line
36
- declare var uni: any;
53
+ import { openSafeUrl } from '../../../../../../utils/utils';
54
+ import { TUITranslateService } from '@tencentcloud/chat-uikit-engine';
55
+ import Log from '../../../../../../utils/logger';
56
+ const { computed } = vue;
37
57
 
38
58
  interface Props {
39
59
  payload: customerServicePayloadType;
@@ -49,92 +69,125 @@ export default {
49
69
  emits: ['sendMessage'],
50
70
  setup(props: Props) {
51
71
  const jumpProductCard = () => {
52
- if (window) {
53
- window.open(props.payload.content.url, '_blank');
72
+ const { url } = props.payload.content;
73
+ if (url) {
74
+ openSafeUrl(props.payload.content.url);
54
75
  } else {
55
- // uni && uni.navigateTo({ url: `/TUIKit/components/TUIChat/web-view?url=${props.payload.content.url}` });
56
- // #ifdef APP-PLUS
57
- // @ts-ignore
58
- plus.runtime.openURL(props.payload.content.url);
59
- // #endif
60
- // #ifdef H5
61
- // @ts-ignore
62
- window.open(props.payload.content.url);
63
- // #endif
76
+ Log.w('Missing required url');
64
77
  }
65
78
  };
79
+ const customField = computed(() => {
80
+ return Array.isArray(props.payload.content.customField) ? props.payload.content.customField : [];
81
+ });
82
+
83
+ const topCustomField = computed(() => {
84
+ return customField.value.filter((item) => item.position === 'top');
85
+ });
86
+
87
+ const bottomCustomField = computed(() => {
88
+ return customField.value.filter((item) => item.position !== 'top');
89
+ });
66
90
  return {
67
91
  props,
68
92
  isApp,
69
93
  jumpProductCard,
70
- TUITranslateService
94
+ TUITranslateService,
95
+ topCustomField,
96
+ bottomCustomField,
71
97
  };
72
98
  },
73
99
  };
74
100
  </script>
75
101
  <style lang="scss" scoped>
76
102
  .message-product-card {
77
- min-width: 200px;
103
+ min-width: 100%;
78
104
  max-width: 400px;
105
+ margin-top: 2px;
106
+ font-family: PingFangSC-Regular;
79
107
  display: flex;
80
-
81
- .product-img {
82
- width: 75px;
83
- height: 75px;
84
- border-radius: 10px;
85
- flex-shrink: 0;
86
- object-fit: cover;
87
- }
88
-
89
- .product-card-information {
90
- width:100%;
91
- margin-left: 15px;
92
- margin-right:5px;
108
+ flex-direction: column;
109
+ .product-card-main {
93
110
  display: flex;
94
- flex-direction: column;
95
- justify-content: space-between;
111
+ margin: 10px 0 10px 0;
112
+ }
113
+ }
114
+ .product-img {
115
+ width: 60px;
116
+ height: 60px;
117
+ border-radius: 10px;
118
+ flex-shrink: 0;
119
+ object-fit: cover;
120
+ margin-right: 15px;
121
+ align-self: center;
122
+ }
96
123
 
97
- .product-card-title {
98
- max-width: 200px;
99
- min-width: 100px;
100
- color: #000000;
101
- font-size: 14px;
102
- display: -webkit-box;
103
- overflow: hidden;
104
- text-overflow: ellipsis;
105
- -webkit-line-clamp: 2;
106
- -webkit-box-orient: vertical;
107
- overflow-wrap: break-word;
108
- word-break: normal;
109
- }
124
+ .product-card-information {
125
+ width:100%;
126
+ margin-right: 5px;
127
+ display: flex;
128
+ flex-direction: column;
129
+ justify-content: space-between;
110
130
 
111
- .product-card-description-block {
112
- display:flex;
113
- justify-content: space-between;
114
- align-items: center;
115
- gap: 5px;
116
- }
131
+ .product-card-title {
132
+ color: #000000;
133
+ font-size: 12px;
134
+ display: -webkit-box;
135
+ overflow: hidden;
136
+ text-overflow: ellipsis;
137
+ -webkit-line-clamp: 2;
138
+ -webkit-box-orient: vertical;
139
+ word-break: break-word;
140
+ overflow-wrap: anywhere;
141
+ }
117
142
 
118
- .product-card-description {
119
- font-size: 12px;
120
- max-width: 60px;
121
- color: #1c66e5;
122
- overflow: hidden;
123
- text-overflow: ellipsis;
124
- white-space: nowrap;
125
- font-weight: 600;
126
- flex: 0 1 60px;
127
- }
143
+ .product-card-description-block {
144
+ display:flex;
145
+ justify-content: space-between;
146
+ align-items: center;
147
+ gap: 5px;
148
+ }
128
149
 
129
- .product-card-link {
130
- cursor: pointer;
131
- background-color: #1c66e5;
132
- color:#ffffff;
133
- padding:2px 12px;
134
- border-radius: 12px;
135
- flex: 0 0 auto;
136
- margin-left: auto;
137
- }
150
+ .product-card-description {
151
+ font-size: 14px;
152
+ max-width: 100px;
153
+ color: #1C66E5;
154
+ overflow: hidden;
155
+ text-overflow: ellipsis;
156
+ white-space: nowrap;
157
+ font-weight: 600;
158
+ flex: 0 1 100px;
138
159
  }
139
160
  }
161
+ .product-card-custom-field {
162
+ color: #000;
163
+ margin: 0 0 5px 0;
164
+ }
165
+ .custom-field-line {
166
+ display: flex;
167
+ font-size: 12px;
168
+ gap: 5px;
169
+ overflow: hidden;
170
+ .order-name {
171
+ color: #878787;
172
+ margin-right: 3px;
173
+ white-space: nowrap;
174
+ flex-shrink: 0;
175
+ }
176
+ .order-value {
177
+ overflow: hidden;
178
+ text-overflow: ellipsis;
179
+ white-space: nowrap;
180
+ }
181
+ }
182
+ .product-card-link {
183
+ cursor: pointer;
184
+ background-color: #1c66e5;
185
+ color: #ffffff;
186
+ padding: 2px 18px;
187
+ border-radius: 23px;
188
+ font-size: 12px;
189
+ line-height: 22px;
190
+ flex: 0 0 auto;
191
+ margin-left: auto;
192
+ }
140
193
  </style>
@@ -14,18 +14,18 @@
14
14
 
15
15
  <div
16
16
  v-if="isMessageRevoked"
17
- class="revoked-text"
17
+ :class="isPC ? 'revoked-text' : ''"
18
18
  >
19
19
  {{ TUITranslateService.t("TUIChat.引用内容已撤回") }}
20
20
  </div>
21
21
  <div v-else>
22
22
  <div v-if="isH5" class="mobile-quote-sender">
23
- {{ messageQuoteContent.messageSender }}
23
+ {{ quoteMessageSender }}
24
24
  </div>
25
25
  <div
26
26
  class="max-double-line"
27
27
  >
28
- {{ isPC ? messageQuoteContent.messageSender + ": "+ transformTextWithKeysToEmojiNames(messageQuoteText) : transformTextWithKeysToEmojiNames(messageQuoteText)}}
28
+ {{ isPC ? quoteMessageSender + ": "+ transformTextWithKeysToEmojiNames(messageQuoteText) : transformTextWithKeysToEmojiNames(messageQuoteText)}}
29
29
  </div>
30
30
  </div>
31
31
 
@@ -77,6 +77,7 @@ let selfAddValue = 0;
77
77
  const messageQuoteText = ref<string>('');
78
78
  const hasQuoteContent = ref(false);
79
79
  const messageQuoteContent = ref<IQuoteContent>({} as IQuoteContent);
80
+ const quoteMessageSender = ref<string>('');
80
81
 
81
82
  const isMessageRevoked = computed<boolean>(() => {
82
83
  try {
@@ -110,6 +111,7 @@ onMounted(() => {
110
111
  function performQuoteContent(params: IQuoteContent) {
111
112
  let messageKey: string = '';
112
113
  let quoteContent: string = '';
114
+ const quoteMessage = getQuoteMessage(params.messageID);
113
115
  switch (params.messageType) {
114
116
  case MessageQuoteTypeEnum.TYPE_TEXT:
115
117
  messageKey = '[文本]';
@@ -145,18 +147,25 @@ function performQuoteContent(params: IQuoteContent) {
145
147
  messageKey = '[消息]';
146
148
  break;
147
149
  }
148
- if (
149
- [MessageQuoteTypeEnum.TYPE_TEXT, MessageQuoteTypeEnum.TYPE_MERGER].includes(
150
- params.messageType,
151
- )
152
- ) {
150
+ if (params.messageType === MessageQuoteTypeEnum.TYPE_CUSTOM ||
151
+ params.messageType === MessageQuoteTypeEnum.TYPE_TEXT ||
152
+ params.messageType === MessageQuoteTypeEnum.TYPE_MERGER) {
153
153
  quoteContent = params.messageAbstract;
154
154
  }
155
+ quoteMessageSender.value = !quoteMessage ? messageQuoteContent.value.messageSender : (quoteMessage.nick || quoteMessage.from);
155
156
  return quoteContent
156
157
  ? quoteContent
157
158
  : TUITranslateService.t(`TUIChat.${messageKey}`);
158
159
  }
159
160
 
161
+ function getQuoteMessage(messageID: string) {
162
+ const currentMessageList = TUIStore.getData(StoreName.CHAT, 'messageList');
163
+ let quoteMessage = currentMessageList.find(
164
+ message => message.ID === messageID,
165
+ );
166
+ return quoteMessage;
167
+ }
168
+
160
169
  async function scrollToOriginalMessage() {
161
170
  if (isMessageRevoked.value) {
162
171
  return;
@@ -164,7 +173,7 @@ async function scrollToOriginalMessage() {
164
173
  const originMessageID = messageQuoteContent.value?.messageID;
165
174
  const currentMessageList = TUIStore.getData(StoreName.CHAT, 'messageList');
166
175
  const isOriginalMessageInScreen = currentMessageList.some(
167
- msg => msg.ID === originMessageID,
176
+ message => message.ID === originMessageID,
168
177
  );
169
178
  if (originMessageID && isOriginalMessageInScreen) {
170
179
  try {
@@ -12,6 +12,7 @@
12
12
  flex: 1;
13
13
  overflow: hidden;
14
14
  display: flex;
15
+ background-image: none;
15
16
  }
16
17
 
17
18
  &-message-input {
@@ -53,6 +53,7 @@ export const createOfflinePushInfo = (conversation: IConversationModel) => {
53
53
  export const sendMessages = async (
54
54
  messageList: ITipTapEditorContent[],
55
55
  currentConversation: IConversationModel,
56
+ quoteMessageCloudCustomData?: string,
56
57
  ) => {
57
58
  // In case of messageJumping, the sent message is automatically cleared and returns to the bottom
58
59
  if (TUIStore.getData(StoreName.CHAT, 'messageSource')) {
@@ -71,6 +72,10 @@ export const sendMessages = async (
71
72
  const sendMessageOptions = {
72
73
  offlinePushInfo: {},
73
74
  };
75
+ // handle quote message
76
+ if (quoteMessageCloudCustomData) {
77
+ options.cloudCustomData = quoteMessageCloudCustomData;
78
+ }
74
79
  switch (content?.type) {
75
80
  case 'text':
76
81
  textMessageContent = JSON.parse(JSON.stringify(content.payload?.text));
package/constant.ts CHANGED
@@ -148,14 +148,14 @@ export const TOOLBAR_BUTTON_TYPE = {
148
148
  HUMAN_SERVICE: 'humanService',
149
149
  SERVICE_RATING: 'serviceRating',
150
150
  END_HUMAN_SERVICE: 'endHumanService',
151
- }
151
+ };
152
152
  export const INPUT_TOOLBAR_TYPE = {
153
153
  EMOJI: 'emoji',
154
154
  IMAGE: 'image',
155
155
  FILE: 'file',
156
156
  VIDEO: 'video',
157
157
  RATING: 'rating',
158
- }
158
+ };
159
159
 
160
160
  export const USER_DEFAULT_AVATAR = 'https://web.sdk.qcloud.com/im/desk/assets/user_default_avatar.png';
161
161
 
@@ -165,4 +165,4 @@ export enum ReadState {
165
165
  AllRead,
166
166
  NotShow,
167
167
  PartiallyRead,
168
- }
168
+ };
package/interface.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { TOOLBAR_BUTTON_TYPE,INPUT_TOOLBAR_TYPE } from './constant';
1
+ import { TOOLBAR_BUTTON_TYPE, INPUT_TOOLBAR_TYPE } from './constant';
2
2
  export interface customerServicePayloadType {
3
3
  chatbotPlugin?: number | string;
4
4
  customerServicePlugin?: number | string;
@@ -211,4 +211,18 @@ export interface InputToolbarModel {
211
211
  isEnabled?: number, // 是否显示
212
212
  renderCondition?: () => {},// [UIKit] 是否显示
213
213
  clickEvent?: () => void,// [UIKit] 点击事件
214
+ }
215
+
216
+ export interface QuickOrderCustomFieldModel {
217
+ name?: string,
218
+ value: string,
219
+ position?: string,
220
+ }
221
+
222
+ export interface QuickOrderModel {
223
+ header: string,
224
+ desc: string,
225
+ pic?: string,
226
+ url: string,
227
+ customField?: QuickOrderCustomFieldModel[],
214
228
  }
@@ -88,7 +88,7 @@ const TUIChat = {
88
88
  '已接听': 'Answered',
89
89
  '已拒绝': 'Rejected',
90
90
  '已同意': 'Approved',
91
- '引用': 'Reference',
91
+ '引用': 'Quote',
92
92
  '引用失败': 'quote failed',
93
93
  '语音': '[Voice]',
94
94
  '语音通话': 'Voice Call',
@@ -153,6 +153,7 @@ const TUIChat = {
153
153
  '您已被禁止聊天': 'you have been forbidden to speak',
154
154
  '账号被强制下线': 'Account forcibly logged out',
155
155
  '登录失败': 'Login failed',
156
+ '链接':'[Link]',
156
157
  };
157
158
 
158
159
  export default TUIChat;
@@ -1,7 +1,7 @@
1
1
  const AIDesk = {
2
2
  "结束人工会话": "End human service",
3
3
  "转人工服务": "Human service",
4
- "跳转": "Goto",
4
+ "跳转": "Open",
5
5
  "立即填写": "Fill now",
6
6
  "已提交": "Submitted",
7
7
  "提交": "Submit",
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Context misunderstanding",
28
28
  "格式不规范":"Improper format",
29
29
  "内容不完整":"Incomplete content",
30
+ "file 大小超过 100MB,无法发送":"Unable to send the file as it exceeds 100 MB",
31
+ "image 大小超过 20MB,无法发送":"Unable to send the image as it exceeds 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Unable to send the video as it exceeds 100 MB",
30
33
  }
31
34
  export default AIDesk;
@@ -155,6 +155,7 @@ const TUIChat = {
155
155
  '按Enter发送,Ctrl+Enter换行': 'Pindutin ang Enter upang ipadala, Ctrl+Enter upang pumunta ng bara',
156
156
  '账号被强制下线': 'Ang account ay pina-force log out',
157
157
  '登录失败': 'Hindi makapag-login',
158
+ '链接':'[Link]',
158
159
  };
159
160
 
160
161
  export default TUIChat;
@@ -27,5 +27,8 @@ const AIDesk = {
27
27
  "上下文理解错误":"Maling pag-unawa sa konteksto",
28
28
  "格式不规范":"Hindi tamang pormat",
29
29
  "内容不完整":"Hindi kumpletong nilalaman",
30
+ "file 大小超过 100MB,无法发送":"Lampas sa 100MB ang file, hindi maipadala",
31
+ "image 大小超过 20MB,无法发送":"Hindi maipadala ang larawan dahil lampas ito sa 20 MB",
32
+ "video 大小超过 100MB,无法发送":"Hindi maipadala ang video dahil lampas ito sa 100 MB",
30
33
  }
31
34
  export default AIDesk;