@quidgest/chatbot 0.0.9 → 0.1.0

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/dist/style.css CHANGED
@@ -1 +1 @@
1
- .q-chatbot{width:100%;padding:1rem;display:flex;flex-direction:column;height:100%}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;gap:.75rem;flex-direction:column;overflow:hidden}.q-chatbot__footer-container{padding:.8rem}.q-chatbot__send-container{padding-bottom:.25rem;display:flex;justify-content:flex-end}.q-chatbot__send-container .q-chatbot__send{border-radius:1rem}.q-chatbot__footer{position:sticky;padding:0 .5rem;border:1px solid #eaebec;border-radius:.25rem;bottom:0;width:100%;display:flex;flex-direction:column;gap:.25rem}.q-chatbot__footer-disabled{background-color:rgb(var(--q-theme-neutral-light-rgb)/.25);cursor:not-allowed}.q-chatbot__footer .q-chatbot__input{min-height:50px;max-height:100px;border-bottom:1px solid #eaebec;overflow-y:auto}.q-chatbot__footer .q-text-area{max-height:100%;overflow-y:auto}.q-chatbot__footer .q-text-area .q-field__control{border:none}.q-chatbot__messages-container{display:flex;flex-direction:column;flex-grow:1;padding:0 1rem 2rem;gap:1.5rem;overflow-y:auto}.q-chatbot__messages-wrapper{display:flex;max-width:100%;gap:.2rem}.q-chatbot__tools{display:flex;flex-direction:row;justify-content:end;max-width:100%}.q-chatbot__message-wrapper{display:flex;flex-direction:column;gap:.2rem}.q-chatbot__message-container{display:flex;flex-direction:column;gap:.25rem}.q-chatbot__messages-wrapper_right{justify-content:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-container{align-items:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-wrapper{display:flex;align-items:flex-end}.q-chatbot__profile.q-icon__img{border-radius:50%;height:2rem;width:2rem}.q-chatbot__message{display:flex;align-items:center;padding:.3rem .5rem;background-color:#eaebec;width:fit-content;white-space:normal;min-height:2rem;word-wrap:break-word;word-break:break-word;border-radius:0 .5rem .5rem}.q-chatbot__messages-wrapper_right .q-chatbot__message{background-color:#018bd233;border-radius:.5rem 0 .5rem .5rem}.q-chatbot__sender{white-space:nowrap;color:#7c858d;font-size:.7rem}.pulsing-dots{display:flex;align-items:center;justify-content:center;gap:.1rem}.dot{font-size:20px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}
1
+ .svg-icon[data-v-695796d5]{display:inline-flex;width:16px;height:14px}.svg-icon[data-v-695796d5] svg{width:100%;height:100%}.q-chatbot{width:100%;padding:1rem;display:flex;flex-direction:column;height:100%}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;gap:.75rem;flex-direction:column;overflow:hidden}.q-chatbot__footer-container{padding:.8rem}.q-chatbot__send-container{padding-bottom:.25rem;display:flex;justify-content:space-between;width:100%}.q-chatbot__send-container .q-chatbot__send,.q-chatbot__send-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__send-container .spacer{flex-grow:1}.q-chatbot__footer{position:sticky;padding:0 .5rem;border:1px solid #eaebec;border-radius:.25rem;bottom:0;width:100%;display:flex;flex-direction:column;gap:.25rem}.q-chatbot__footer-disabled{background-color:rgb(var(--q-theme-neutral-light-rgb)/.25);cursor:not-allowed}.q-chatbot__footer .q-chatbot__input{min-height:50px;max-height:100px;border-bottom:1px solid #eaebec;overflow-y:auto}.q-chatbot__footer .q-text-area{max-height:100%;overflow-y:auto}.q-chatbot__footer .q-text-area .q-field__control{border:none}.q-chatbot__upload-container{display:flex;justify-content:flex-start;padding:.25rem 0}.q-chatbot__upload-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__messages-container{display:flex;flex-direction:column;flex-grow:1;padding:0 1rem 2rem;gap:1.5rem;overflow-y:auto}.q-chatbot__messages-wrapper{display:flex;max-width:100%;gap:.2rem}.q-chatbot__tools{display:flex;flex-direction:row;justify-content:end;max-width:100%}.q-chatbot__message-wrapper{display:flex;flex-direction:column;gap:.2rem}.q-chatbot__message-container{display:flex;flex-direction:column;gap:.25rem}.q-chatbot__messages-wrapper_right{justify-content:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-container{align-items:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-wrapper{display:flex;align-items:flex-end}.q-chatbot__profile.q-icon__img{border-radius:50%;height:2rem;width:2rem}.q-chatbot__message{display:flex;align-items:center;padding:.3rem .5rem;background-color:#eaebec;width:fit-content;white-space:normal;min-height:2rem;word-wrap:break-word;word-break:break-word;border-radius:0 .5rem .5rem}.q-chatbot__messages-wrapper_right .q-chatbot__message{background-color:#018bd233;border-radius:.5rem 0 .5rem .5rem}.q-chatbot__sender{white-space:nowrap;color:#7c858d;font-size:.7rem}.q-chatbot__retry-button{align-items:center;display:flex}.q-chatbot__image-preview{position:relative;display:inline-block}.q-chatbot__image-preview img{max-height:100px;width:auto;border-radius:8px;margin:10px 0}.q-chatbot__remove-image{position:absolute;top:5px;right:5px;background-color:#00000080;color:#fff;border-radius:50%;padding:5px;cursor:pointer;font-size:12px;border:none}.hidden-input{display:none}.pulsing-dots{display:flex;align-items:center;justify-content:center;gap:.1rem}.dot{font-size:20px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}
@@ -2,6 +2,7 @@ import { ResourceStrings } from './texts.type';
2
2
 
3
3
  export type ChatBotProps = {
4
4
  apiEndpoint?: string;
5
+ controllerEndpoint?: string;
5
6
  texts?: ResourceStrings;
6
7
  username: string;
7
8
  projectPath: string;
@@ -3,9 +3,14 @@ export type ChatBotMessage = {
3
3
  message: string;
4
4
  date: Date;
5
5
  sender: ChatBotMessageSender;
6
+ sessionID: string;
7
+ imagePreviewUrl?: string;
8
+ isWelcomeMessage?: boolean;
6
9
  };
7
10
  export type ChatBotMessageContent = {
8
11
  content: string;
9
12
  type: string;
13
+ sessionID: string;
14
+ imageUrl?: string;
10
15
  };
11
16
  export type ChatBotMessageSender = 'bot' | 'user';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@quidgest/chatbot",
3
3
  "private": false,
4
- "version": "0.0.9",
4
+ "version": "0.1.0",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
7
7
  "main": "dist/index.cjs",
@@ -32,20 +32,21 @@
32
32
  "lint:fix": "eslint . --fix && prettier --write --list-different src"
33
33
  },
34
34
  "dependencies": {
35
- "axios": "^1.6.7"
35
+ "axios": "^1.6.7",
36
+ "uuid": "^11.0.5"
36
37
  },
37
38
  "devDependencies": {
38
- "@types/node": "^20.11.13",
39
- "@vitejs/plugin-vue": "^4.5.2",
40
- "eslint": "^9.8.0",
39
+ "@types/node": "^20.16.1",
40
+ "@vitejs/plugin-vue": "^4.6.2",
41
+ "eslint": "^9.9.0",
41
42
  "eslint-plugin-prettier": "^5.2.1",
42
43
  "prettier": "^3.3.3",
43
- "rimraf": "^5.0.5",
44
- "sass": "^1.70.0",
45
- "typescript": "^5.2.2",
46
- "vite": "^5.0.8",
47
- "vite-plugin-dts": "^3.7.3",
48
- "vue-tsc": "^1.8.25"
44
+ "rimraf": "^5.0.10",
45
+ "sass": "^1.77.8",
46
+ "typescript": "^5.5.4",
47
+ "vite": "^5.4.2",
48
+ "vite-plugin-dts": "^3.9.1",
49
+ "vue-tsc": "^1.8.27"
49
50
  },
50
51
  "peerDependencies": {
51
52
  "@quidgest/ui": "^0.14.19",
@@ -0,0 +1,7 @@
1
+ <svg
2
+ xmlns="http://www.w3.org/2000/svg"
3
+ viewBox="0 0 24 24">
4
+ <path
5
+ fill="currentColor"
6
+ d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z" />
7
+ </svg>
@@ -35,13 +35,22 @@
35
35
  &__send-container {
36
36
  padding-bottom: .25rem;
37
37
  display: flex;
38
- justify-content: flex-end;
38
+ justify-content: space-between;
39
+ width: 100%;
39
40
 
40
41
  & .q-chatbot__send {
41
42
  border-radius: 1rem;
42
43
  }
44
+
45
+ & .q-chatbot__upload {
46
+ border-radius: 1rem;
47
+ }
48
+
49
+ & .spacer {
50
+ flex-grow: 1;
51
+ }
43
52
  }
44
-
53
+
45
54
  &__footer {
46
55
  position: sticky;
47
56
  padding: 0 .5rem;
@@ -57,23 +66,34 @@
57
66
  background-color: rgb(var(--q-theme-neutral-light-rgb) / 0.25);
58
67
  cursor: not-allowed;
59
68
  }
60
-
69
+
61
70
  & .q-chatbot__input {
62
- min-height: 50px;
63
- max-height: 100px;
64
- border-bottom: 1px solid #eaebec;
65
- overflow-y: auto;
71
+ min-height: 50px;
72
+ max-height: 100px;
73
+ border-bottom: 1px solid #eaebec;
74
+ overflow-y: auto;
66
75
  }
67
-
76
+
68
77
  & .q-text-area {
69
78
  max-height: 100%;
70
79
  overflow-y: auto;
71
-
80
+
72
81
  & .q-field__control {
73
82
  border: none;
74
83
  }
75
84
  }
76
- }
85
+ }
86
+
87
+ &__upload-container {
88
+ display: flex;
89
+ justify-content: flex-start;
90
+ padding: .25rem 0;
91
+
92
+ & .q-chatbot__upload {
93
+ border-radius: 1rem;
94
+ }
95
+ }
96
+
77
97
  &__messages-container {
78
98
  display: flex;
79
99
  flex-direction: column;
@@ -141,12 +161,12 @@
141
161
  min-height: 2rem;
142
162
  word-wrap: break-word;
143
163
  word-break: break-word;
144
- border-radius: 0 0.5rem 0.5rem .5rem;
164
+ border-radius: 0 0.5rem 0.5rem 0.5rem;
145
165
  }
146
166
 
147
167
  &__messages-wrapper_right .q-chatbot__message {
148
168
  background-color: rgba(#018bd2, 20%);
149
- border-radius: 0.5rem 0 0.5rem .5rem;
169
+ border-radius: 0.5rem 0 0.5rem 0.5rem;
150
170
  }
151
171
 
152
172
  &__sender {
@@ -154,20 +174,54 @@
154
174
  color: #7c858d;
155
175
  font-size: 0.7rem;
156
176
  }
177
+
178
+ &__retry-button {
179
+ align-items: center;
180
+ display: flex;
181
+ }
182
+
183
+ &__image-preview {
184
+ position: relative;
185
+ display: inline-block;
186
+
187
+ img {
188
+ max-height: 100px;
189
+ width: auto;
190
+ border-radius: 8px;
191
+ margin: 10px 0;
192
+ }
193
+ }
194
+
195
+ &__remove-image {
196
+ position: absolute;
197
+ top: 5px;
198
+ right: 5px;
199
+ background-color: rgba(0, 0, 0, 0.5);
200
+ color: white;
201
+ border-radius: 50%;
202
+ padding: 5px;
203
+ cursor: pointer;
204
+ font-size: 12px;
205
+ border: none;
206
+ }
207
+ }
208
+
209
+ .hidden-input {
210
+ display: none;
157
211
  }
158
212
 
159
213
  .pulsing-dots {
160
- display: flex;
161
- align-items: center;
162
- justify-content: center;
163
- gap: 0.1rem;
214
+ display: flex;
215
+ align-items: center;
216
+ justify-content: center;
217
+ gap: 0.1rem;
164
218
  }
165
219
 
166
220
  .dot {
167
- font-size: 20px;
168
- line-height: 1;
169
- animation: pulse 1s infinite;
170
- color: var(--q-theme-primary);
221
+ font-size: 20px;
222
+ line-height: 1;
223
+ animation: pulse 1s infinite;
224
+ color: var(--q-theme-primary);
171
225
  }
172
226
 
173
227
  @keyframes pulse {
@@ -0,0 +1,9 @@
1
+ <svg
2
+ id="Layer_1"
3
+ class="q-icon q-icon__svg"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ viewBox="0 0 21.86 19.87">
6
+ <path
7
+ fill="currentColor"
8
+ d="M17.89,11.92h3.97V0h-3.97M13.91,0H4.97c-.82,0-1.53.5-1.83,1.21L.14,8.22c-.09.23-.14.47-.14.73v1.99c0,1.1.89,1.99,1.99,1.99h6.27l-.94,4.54c-.02.1-.03.2-.03.31,0,.42.17.78.44,1.05l1.05,1.05,6.54-6.55c.37-.36.59-.85.59-1.4V1.99c0-1.1-.89-1.99-1.99-1.99Z" />
9
+ </svg>
@@ -0,0 +1,9 @@
1
+ <svg
2
+ id="Layer_1"
3
+ class="q-icon q-icon__svg"
4
+ xmlns="http://www.w3.org/2000/svg"
5
+ viewBox="0 0 21.7 19.73">
6
+ <path
7
+ fill="currentColor"
8
+ d="M21.7,8.88c0-1.09-.89-1.97-1.97-1.97h-6.23l.95-4.51c.02-.1.03-.21.03-.32,0-.4-.17-.78-.43-1.05l-1.05-1.04-6.49,6.49c-.36.36-.58.86-.58,1.4v9.86c0,1.09.88,1.97,1.97,1.97h8.88c.82,0,1.52-.49,1.82-1.2l2.98-6.95c.09-.23.14-.46.14-.72v-1.97M0,19.73h3.95V7.89H0v11.84Z" />
9
+ </svg>
@@ -4,26 +4,78 @@
4
4
  <q-icon
5
5
  type="img"
6
6
  :icon="messageImage"
7
- alt="Sender Image"
7
+ :alt="props.texts.senderImage"
8
8
  class="q-chatbot__profile" />
9
9
 
10
- <div class="q-chatbot__message-wrapper">
10
+ <div class="q-chatbot__message-wrapper">
11
+ <div
12
+ v-if="props.imagePreviewUrl && props.imagePreviewUrl.length > 0"
13
+ class="q-chatbot__image-preview">
14
+ <img
15
+ :src="props.imagePreviewUrl"
16
+ :alt="props.texts.imagePreview" />
17
+ </div>
11
18
  <!-- Message body -->
12
19
  <div class="q-chatbot__message">
13
20
  <!-- When loading is true, show bouncing dots animation -->
14
- <pulse-dots v-if="loading"/>
21
+ <pulse-dots v-if="loading" />
15
22
  <template v-else>
16
23
  <div
17
- class="q-chatbot__text"
18
- v-if="props.sender === 'bot'"
19
- v-html="props.message"></div>
20
- <div class="q-chatbot__text" v-else>
24
+ class="q-chatbot__text"
25
+ v-if="props.sender === 'bot'"
26
+ v-html="props.message"></div>
27
+ <div
28
+ class="q-chatbot__text"
29
+ v-else>
21
30
  {{ props.message }}
22
31
  </div>
23
32
  </template>
24
33
  </div>
25
- <div
26
- class="q-chatbot__sender">
34
+ <q-dialog
35
+ v-model="showDialog"
36
+ :buttons="commentButtons"
37
+ :text="props.texts.commentDialogTitle">
38
+ <template #body.append>
39
+ <q-text-field
40
+ v-model="feedbackComment"
41
+ :maxLength="150"
42
+ size="large"
43
+ :placeholder="props.texts.commentPlaceholder" />
44
+ </template>
45
+ </q-dialog>
46
+
47
+ <!-- Thumbs up and down buttons, only for bot messages -->
48
+ <div
49
+ v-if="isBotMessageAndNotDefault"
50
+ class="q-chatbot__feedback-buttons">
51
+ <q-button-group>
52
+ <q-button
53
+ :title="props.texts.goodResponse"
54
+ borderless
55
+ :disabled="loading"
56
+ b-style="secondary"
57
+ @click="openFeedbackDialog(1)">
58
+ <span v-html="thumbUpSvg" class="svg-icon"></span>
59
+ </q-button>
60
+ <q-button
61
+ :title="props.texts.badResponse"
62
+ borderless
63
+ :disabled="loading"
64
+ b-style="secondary"
65
+ @click="openFeedbackDialog(0)">
66
+ <span v-html="thumbDownSvg" class="svg-icon"></span>
67
+ </q-button>
68
+ <q-button
69
+ :title="props.texts.copyResponse"
70
+ borderless
71
+ :disabled="loading"
72
+ b-style="secondary"
73
+ @click="copyResponse">
74
+ <span v-html="copySvg" class="svg-icon"></span>
75
+ </q-button>
76
+ </q-button-group>
77
+ </div>
78
+ <div class="q-chatbot__sender">
27
79
  {{ messageDate }}
28
80
  </div>
29
81
  </div>
@@ -31,10 +83,28 @@
31
83
  </template>
32
84
 
33
85
  <script setup lang="ts">
34
- import { computed } from 'vue'
35
- import { QIcon } from '@quidgest/ui/components'
86
+ import { computed, ref } from 'vue'
87
+ import {
88
+ QButton,
89
+ QIcon,
90
+ QDialog,
91
+ QTextField
92
+ } from '@quidgest/ui/components'
36
93
  import PulseDots from '@/components/PulseDots.vue'
37
94
  import type { ChatBotMessageSender } from '@/types/message.type'
95
+ import { ResourceStrings } from '@/types/texts.type'
96
+ import Axios from 'axios'
97
+
98
+ // Importando o conteúdo dos arquivos SVG como strings
99
+ import thumbUpSvgFile from '@/assets/thumbUp.svg?raw'
100
+ import thumbDownSvgFile from '@/assets/thumbDown.svg?raw'
101
+ import copySvgFile from '@/assets/copy.svg?raw'
102
+
103
+ // Armazenando o conteúdo SVG como strings
104
+ const thumbUpSvg = thumbUpSvgFile;
105
+ const thumbDownSvg = thumbDownSvgFile;
106
+ const copySvg = copySvgFile;
107
+
38
108
  export interface CBMessageProps {
39
109
  /*
40
110
  * Sender of the message
@@ -59,7 +129,27 @@
59
129
  /**
60
130
  * Project locale
61
131
  */
62
- dateFormat?: string,
132
+ dateFormat?: string
133
+
134
+ /**
135
+ * Image preview URL
136
+ */
137
+ imagePreviewUrl?: string
138
+
139
+ /**
140
+ * Default chatBot texts
141
+ */
142
+ texts?: ResourceStrings
143
+
144
+ /**
145
+ * Default api endpoint
146
+ */
147
+ apiEndpoint?: String
148
+
149
+ /**
150
+ * Session ID
151
+ */
152
+ sessionID?: String
63
153
 
64
154
  /**
65
155
  * User image
@@ -70,18 +160,61 @@
70
160
  * Chatbot image
71
161
  */
72
162
  chatbotImage: string
163
+
164
+ /**
165
+ * Flag to mark welcome messages
166
+ */
167
+ isWelcomeMessage?: boolean
73
168
  }
74
169
 
75
170
  const props = withDefaults(defineProps<CBMessageProps>(), {
76
171
  sender: 'user',
77
172
  userImage: undefined,
78
- date: () => new Date()
173
+ date: () => new Date(),
174
+ texts: () => ({
175
+ commentDialogTitle: 'Would you like to add a comment?',
176
+ commentPlaceholder: 'Type your comment here (optional)...',
177
+ goodResponse: 'Good response',
178
+ badResponse: 'Bad response',
179
+ copyResponse: 'Copy response',
180
+ submitButton: 'Submit',
181
+ cancelButton: 'Cancel',
182
+ senderImage: 'Sender Image',
183
+ imagePreview: 'Image preview'
184
+ })
79
185
  })
80
186
 
187
+ const showDialog = ref(false)
188
+ const feedbackComment = ref('')
189
+ const currentFeedback = ref<number | null>(null)
190
+ const commentButtons = [
191
+ {
192
+ id: 'confirm-btn',
193
+ action: submitFeedback,
194
+ props: {
195
+ label: props.texts.submitButton,
196
+ 'b-style': 'primary'
197
+ }
198
+ },
199
+ {
200
+ id: 'cancel-btn',
201
+ props: {
202
+ label: props.texts.cancelButton,
203
+ 'b-style': 'danger'
204
+ }
205
+ }
206
+ ]
207
+
81
208
  const senderName = computed(() => {
82
209
  return props.sender === 'bot' ? 'GenioBot' : 'You'
83
210
  })
84
211
 
212
+ const isBotMessageAndNotDefault = computed(() => {
213
+ return props.sender === 'bot' &&
214
+ !Object.values(props.texts || {}).includes(props.message || '') &&
215
+ !props.isWelcomeMessage
216
+ })
217
+
85
218
  const getLocaleDate = computed(() => {
86
219
  if (!props.dateFormat) return props.date.toLocaleString()
87
220
 
@@ -92,7 +225,9 @@
92
225
  return `${senderName.value} ${getLocaleDate.value}`
93
226
  })
94
227
 
95
- const messageImage = computed(() => props.sender === 'bot' ? props.chatbotImage : props.userImage);
228
+ const messageImage = computed(() =>
229
+ props.sender === 'bot' ? props.chatbotImage : props.userImage
230
+ )
96
231
 
97
232
  function formatDate(date: Date, format: string) {
98
233
  const day = date.getDate().toString().padStart(2, '0')
@@ -110,4 +245,55 @@
110
245
  .replace('mm', minutes)
111
246
  .replace('ss', seconds)
112
247
  }
248
+
249
+ function openFeedbackDialog(feedback: number) {
250
+ showDialog.value = true
251
+ feedbackComment.value = ''
252
+ currentFeedback.value = feedback
253
+ }
254
+
255
+ function submitFeedback() {
256
+ if (currentFeedback.value != null) {
257
+ handleFeedback(currentFeedback.value, feedbackComment.value)
258
+ }
259
+ }
260
+ function handleFeedback(feedback: number, comment: string) {
261
+ Axios.post(props.apiEndpoint + '/prompt/feedback', {
262
+ messageSessionID: props.sessionID,
263
+ feedbackValue: feedback,
264
+ feedbackComment: comment
265
+ }).catch((error) => {
266
+ console.error('Error sending message feedback: ', error)
267
+ })
268
+ }
269
+
270
+ function copyResponse() {
271
+ if (!props.message) return
272
+
273
+ navigator.clipboard
274
+ .writeText(props.message)
275
+ .then(() => {
276
+ console.log('Message copied to clipboard')
277
+ })
278
+ .catch((error) => {
279
+ console.error(
280
+ 'Failed to copy message to clipboard: ',
281
+ error
282
+ )
283
+ })
284
+ }
113
285
  </script>
286
+
287
+ <style scoped>
288
+
289
+ .svg-icon {
290
+ display: inline-flex;
291
+ width: 16px;
292
+ height: 14px;
293
+ }
294
+
295
+ .svg-icon :deep(svg) {
296
+ width: 100%;
297
+ height: 100%;
298
+ }
299
+ </style>