@quidgest/chatbot 0.0.2 → 0.0.4
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/components/CBMessage.vue.d.ts +34 -0
- package/dist/components/ChatBot.vue.d.ts +4 -0
- package/dist/components/index.d.ts +3 -0
- package/dist/index.js +157 -105
- package/dist/index.mjs +157 -105
- package/dist/style.css +34 -40
- package/dist/types/message.type.d.ts +6 -2
- package/package.json +1 -1
- package/src/assets/styles/styles.scss +40 -46
- package/src/components/CBMessage.vue +81 -0
- package/src/components/ChatBot.vue +117 -109
- package/src/components/index.ts +7 -0
- package/src/types/message.type.ts +7 -2
|
@@ -16,25 +16,59 @@
|
|
|
16
16
|
flex: 1;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
.clear-btn {
|
|
20
|
+
color: red;
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
&__content {
|
|
20
24
|
background-color: white;
|
|
21
25
|
border: 1px solid #eaebec;
|
|
22
26
|
height: 100%;
|
|
23
27
|
width: 100%;
|
|
24
|
-
padding: 2rem 1rem;
|
|
25
28
|
display: flex;
|
|
26
29
|
flex-direction: column;
|
|
27
30
|
overflow: auto;
|
|
28
31
|
gap: 1.5rem;
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
&
|
|
34
|
+
&__messages-container {
|
|
35
|
+
display: flex;
|
|
36
|
+
flex-direction: column;
|
|
37
|
+
padding: 0 1rem 2rem 1rem;
|
|
38
|
+
gap: 1.5rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
&__messages-wrapper {
|
|
32
42
|
display: flex;
|
|
33
43
|
max-width: 100%;
|
|
34
44
|
gap: 0.2rem;
|
|
45
|
+
|
|
46
|
+
&_right {
|
|
47
|
+
justify-content: flex-end;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&__tools {
|
|
52
|
+
display: flex;
|
|
53
|
+
flex-direction: row;
|
|
54
|
+
justify-content: end;
|
|
55
|
+
max-width: 100%;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&__message-wrapper {
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-direction: column;
|
|
61
|
+
gap: 0.2rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&__message-container {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: row;
|
|
67
|
+
align-items: center;
|
|
68
|
+
gap: 0.5rem;
|
|
35
69
|
}
|
|
36
70
|
|
|
37
|
-
&
|
|
71
|
+
&__messages-wrapper_right {
|
|
38
72
|
justify-content: flex-end;
|
|
39
73
|
}
|
|
40
74
|
|
|
@@ -45,63 +79,23 @@
|
|
|
45
79
|
}
|
|
46
80
|
|
|
47
81
|
&__message {
|
|
48
|
-
|
|
49
|
-
margin: 0rem 0.5rem;
|
|
50
|
-
max-width: 75%;
|
|
82
|
+
min-width: 4rem;
|
|
51
83
|
display: flex;
|
|
52
84
|
align-items: center;
|
|
53
85
|
padding: 0.3rem 0.5rem;
|
|
54
86
|
background-color: #eaebec;
|
|
55
87
|
white-space: normal;
|
|
88
|
+
min-height: 2rem;
|
|
56
89
|
}
|
|
57
90
|
|
|
58
|
-
&
|
|
91
|
+
&__messages-wrapper_right .q-chatbot__message {
|
|
59
92
|
background-color: rgba(#018bd2, 20%);
|
|
60
93
|
}
|
|
61
94
|
|
|
62
95
|
&__sender {
|
|
63
96
|
white-space: nowrap;
|
|
64
|
-
position: absolute;
|
|
65
97
|
color: #7c858d;
|
|
66
98
|
font-size: 0.7rem;
|
|
67
|
-
left: 0;
|
|
68
|
-
top: -1rem;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
&__timestamp {
|
|
72
|
-
white-space: nowrap;
|
|
73
|
-
position: absolute;
|
|
74
|
-
color: #7c858d;
|
|
75
|
-
font-size: 0.7rem;
|
|
76
|
-
right: 0;
|
|
77
|
-
top: -1rem;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
&__message-loading {
|
|
81
|
-
display: flex;
|
|
82
|
-
justify-content: center;
|
|
83
|
-
align-items: center;
|
|
84
|
-
gap: 0.3rem;
|
|
85
|
-
|
|
86
|
-
div {
|
|
87
|
-
background-color: rgba(#018bd2, 20%);
|
|
88
|
-
border-radius: 50%;
|
|
89
|
-
width: 0.5rem;
|
|
90
|
-
height: 0.5rem;
|
|
91
|
-
animation: dotPulse 1.5s infinite;
|
|
92
|
-
|
|
93
|
-
&:nth-child(1) {
|
|
94
|
-
animation-delay: 0s;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
&:nth-child(2) {
|
|
98
|
-
animation-delay: 0.5s;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
&:nth-child(3) {
|
|
102
|
-
animation-delay: 1s;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
99
|
}
|
|
106
100
|
}
|
|
107
101
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import ChatBotIcon from '@/assets/chatbot.png'
|
|
4
|
+
import { QLineLoader } from '@quidgest/ui'
|
|
5
|
+
|
|
6
|
+
export interface CBMessageProps {
|
|
7
|
+
/*
|
|
8
|
+
* Sender of the message
|
|
9
|
+
*/
|
|
10
|
+
sender?: 'bot' | 'user'
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* Message to be displayed
|
|
14
|
+
*/
|
|
15
|
+
message?: string
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Date of when the message was sent
|
|
19
|
+
*/
|
|
20
|
+
date?: Date
|
|
21
|
+
|
|
22
|
+
/*
|
|
23
|
+
* If the message is loading
|
|
24
|
+
*/
|
|
25
|
+
loading?: boolean
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const props = withDefaults(defineProps<CBMessageProps>(), {
|
|
29
|
+
sender: 'user',
|
|
30
|
+
date: () => new Date()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const senderName = computed(() => {
|
|
34
|
+
return props.sender === 'bot' ? 'GenioBot' : 'You'
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const getLocaleDate = computed(() => {
|
|
38
|
+
return props.date.toLocaleString()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
const messageHeader = computed(() => {
|
|
42
|
+
return `${senderName.value} ${getLocaleDate.value}`
|
|
43
|
+
})
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<div class="q-chatbot__message-container">
|
|
48
|
+
|
|
49
|
+
<!-- Chatbot Image -->
|
|
50
|
+
<img
|
|
51
|
+
v-if="props.sender === 'bot'"
|
|
52
|
+
:src="ChatBotIcon"
|
|
53
|
+
alt="Chatbot"
|
|
54
|
+
class="q-chatbot__profile" />
|
|
55
|
+
|
|
56
|
+
<div class="q-chatbot__message-wrapper">
|
|
57
|
+
<!-- Message header -->
|
|
58
|
+
<div
|
|
59
|
+
v-if="!loading"
|
|
60
|
+
class="q-chatbot__sender">
|
|
61
|
+
{{ messageHeader }}
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
<!-- Message body -->
|
|
65
|
+
<div class="q-chatbot__message">
|
|
66
|
+
<QLineLoader v-if="loading" />
|
|
67
|
+
<template v-else>
|
|
68
|
+
<div
|
|
69
|
+
class="q-chatbot__text"
|
|
70
|
+
v-if="props.sender == 'bot'"
|
|
71
|
+
v-html="props.message"></div>
|
|
72
|
+
<div
|
|
73
|
+
class="q-chatbot__text"
|
|
74
|
+
v-else>
|
|
75
|
+
{{ props.message }}
|
|
76
|
+
</div>
|
|
77
|
+
</template>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</template>
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import type { Ref } from 'vue'
|
|
4
4
|
import Axios from 'axios'
|
|
5
5
|
import type { AxiosResponse } from 'axios'
|
|
6
|
+
import { CBMessage } from '@/components'
|
|
7
|
+
|
|
6
8
|
import {
|
|
7
9
|
QButton,
|
|
8
10
|
QTextField,
|
|
@@ -11,13 +13,13 @@
|
|
|
11
13
|
} from '@quidgest/ui'
|
|
12
14
|
|
|
13
15
|
import type { ResourceStrings } from '@/types/texts.type'
|
|
14
|
-
import type { ChatBotMessage } from '@/types/message.type'
|
|
15
|
-
|
|
16
|
+
import type { ChatBotMessage, ChatBotMessageContent } from '@/types/message.type'
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
let messages: Ref<ChatBotMessage[]> = ref([])
|
|
18
20
|
let msgHistoryStack: string[] = []
|
|
19
21
|
let nextMessageId: number = 1
|
|
20
|
-
let userPrompt: Ref<string> = ref('')
|
|
22
|
+
let userPrompt: Ref<string> = ref('')
|
|
21
23
|
let isChatDisabled: boolean = false
|
|
22
24
|
let isLoading: boolean = false
|
|
23
25
|
|
|
@@ -40,6 +42,11 @@
|
|
|
40
42
|
* Genio username
|
|
41
43
|
*/
|
|
42
44
|
username: string
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Project aplication path
|
|
48
|
+
*/
|
|
49
|
+
projectPath: string
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
const props = withDefaults(defineProps<ChatBotProps>(), {
|
|
@@ -62,66 +69,74 @@
|
|
|
62
69
|
nextTick(scrollChatToBottom)
|
|
63
70
|
})
|
|
64
71
|
|
|
65
|
-
const getParsedEndpoint = computed(() => {
|
|
66
|
-
try {
|
|
67
|
-
let url = new URL(props.apiEndpoint).href
|
|
68
|
-
|
|
69
|
-
if (url.charAt(url.length - 1) == '/') {
|
|
70
|
-
url = url.slice(0, -1)
|
|
71
|
-
}
|
|
72
|
-
return url
|
|
73
|
-
} catch (ex) {
|
|
74
|
-
addChatMessage(props.texts.botIsSick)
|
|
75
|
-
|
|
76
|
-
isChatDisabled = true
|
|
77
|
-
throw Error('Could not parse Endpoint URL:\n' + ex)
|
|
78
|
-
}
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const sortedMessages = computed(() => {
|
|
82
|
-
return messages.value.toSorted((a: ChatBotMessage, b: ChatBotMessage) => {
|
|
83
|
-
const diff = new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
|
84
|
-
return diff !== 0 ? diff : a.sender === 'user' ? -1 : 1
|
|
85
|
-
})
|
|
86
|
-
})
|
|
87
|
-
|
|
88
72
|
const userMessages = computed(() => {
|
|
89
73
|
return messages.value.filter((m: ChatBotMessage) => m.sender === 'user')
|
|
90
74
|
})
|
|
75
|
+
|
|
76
|
+
function setDisabledState(state: boolean) {
|
|
77
|
+
isChatDisabled = state
|
|
78
|
+
}
|
|
91
79
|
|
|
92
80
|
function initChat() {
|
|
93
|
-
Axios.post(
|
|
81
|
+
Axios.post(props.apiEndpoint + '/auth/login', {
|
|
94
82
|
username: props.username,
|
|
95
83
|
password: 'test'
|
|
96
84
|
})
|
|
97
85
|
.then((response: AxiosResponse) => {
|
|
98
86
|
if (response.status != 200 || !response.data.success) {
|
|
99
|
-
|
|
87
|
+
setDisabledState(true)
|
|
100
88
|
addChatMessage(props.texts.loginError)
|
|
101
89
|
return console.log(
|
|
102
90
|
`Unsuccessful login, endpoint gave status ${response.status}`
|
|
103
91
|
)
|
|
104
92
|
}
|
|
105
93
|
|
|
106
|
-
|
|
94
|
+
loadChatData()
|
|
107
95
|
})
|
|
108
96
|
.catch((error: Error) => {
|
|
109
|
-
|
|
110
|
-
|
|
97
|
+
setDisabledState(true)
|
|
98
|
+
addChatMessage(props.texts.loginError)
|
|
99
|
+
console.log(
|
|
100
|
+
'The following error ocurred while trying to login: \n' +
|
|
101
|
+
error
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function loadChatData() {
|
|
107
|
+
Axios.post(props.apiEndpoint + '/prompt/load', {
|
|
108
|
+
username: props.username,
|
|
109
|
+
project: props.projectPath
|
|
110
|
+
})
|
|
111
|
+
.then((response: AxiosResponse) => {
|
|
112
|
+
if (response.status != 200 || !response.data.success) {
|
|
113
|
+
setDisabledState(true)
|
|
111
114
|
addChatMessage(props.texts.loginError)
|
|
112
|
-
console.log(
|
|
113
|
-
|
|
114
|
-
error
|
|
115
|
+
return console.log(
|
|
116
|
+
`Unsuccessful load, endpoint gave status ${response.status}`
|
|
115
117
|
)
|
|
116
118
|
}
|
|
119
|
+
|
|
120
|
+
sendInitialMessage()
|
|
121
|
+
response.data.history.forEach((message: ChatBotMessageContent) => {
|
|
122
|
+
addChatMessage(message.content, message.type === "ai" ? "bot" : "user")
|
|
123
|
+
})
|
|
124
|
+
})
|
|
125
|
+
.catch((error: Error) => {
|
|
126
|
+
setDisabledState(true)
|
|
127
|
+
addChatMessage(props.texts.loginError)
|
|
128
|
+
console.log(
|
|
129
|
+
'The following error ocurred while trying to login: \n' +
|
|
130
|
+
error
|
|
131
|
+
)
|
|
117
132
|
})
|
|
118
133
|
}
|
|
119
134
|
|
|
120
135
|
function addChatMessage(message: string, sender: 'bot' | 'user' = 'bot') {
|
|
121
136
|
messages.value.push({
|
|
122
137
|
id: nextMessageId++,
|
|
123
|
-
|
|
124
|
-
|
|
138
|
+
message,
|
|
139
|
+
date: new Date(),
|
|
125
140
|
sender: sender
|
|
126
141
|
})
|
|
127
142
|
}
|
|
@@ -141,9 +156,7 @@
|
|
|
141
156
|
msgHistoryStack = []
|
|
142
157
|
userPrompt.value = ''
|
|
143
158
|
isLoading = false
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
sendInitialMessage()
|
|
159
|
+
setDisabledState(false)
|
|
147
160
|
}
|
|
148
161
|
|
|
149
162
|
function scrollChatToBottom() {
|
|
@@ -177,13 +190,13 @@
|
|
|
177
190
|
|
|
178
191
|
//Save current prompt (even if modified) & update input
|
|
179
192
|
msgHistoryStack.push(userPrompt.value)
|
|
180
|
-
userPrompt.value = lastMsgObj.
|
|
193
|
+
userPrompt.value = lastMsgObj.message
|
|
181
194
|
|
|
182
195
|
//Set the cursor to the end of text
|
|
183
196
|
nextTick(() =>
|
|
184
197
|
setCursorPosition(
|
|
185
198
|
promptInput.value as HTMLInputElement,
|
|
186
|
-
lastMsgObj.
|
|
199
|
+
lastMsgObj.message.length
|
|
187
200
|
)
|
|
188
201
|
)
|
|
189
202
|
} else if (event.key == 'ArrowDown') {
|
|
@@ -222,13 +235,13 @@
|
|
|
222
235
|
//Send message request
|
|
223
236
|
let params = {
|
|
224
237
|
message: prompt,
|
|
225
|
-
project:
|
|
238
|
+
project: props.projectPath,
|
|
226
239
|
user: props.username
|
|
227
240
|
}
|
|
228
241
|
|
|
229
242
|
isLoading = true
|
|
230
243
|
Axios({
|
|
231
|
-
url:
|
|
244
|
+
url: props.apiEndpoint + '/prompt/message',
|
|
232
245
|
method: 'POST',
|
|
233
246
|
data: params,
|
|
234
247
|
onDownloadProgress: (progressEvent) => {
|
|
@@ -238,16 +251,16 @@
|
|
|
238
251
|
|
|
239
252
|
if (status != 200) return
|
|
240
253
|
|
|
241
|
-
if (msg) msg.
|
|
254
|
+
if (msg) msg.message = chunk
|
|
242
255
|
}
|
|
243
256
|
})
|
|
244
257
|
.then(({ data }) => {
|
|
245
|
-
if (msg) msg.
|
|
258
|
+
if (msg) msg.message = data
|
|
246
259
|
})
|
|
247
260
|
.catch((error) => {
|
|
248
261
|
addChatMessage(props.texts.botIsSick)
|
|
249
262
|
|
|
250
|
-
|
|
263
|
+
setDisabledState(true)
|
|
251
264
|
console.log(error)
|
|
252
265
|
})
|
|
253
266
|
.finally(() => {
|
|
@@ -255,21 +268,50 @@
|
|
|
255
268
|
})
|
|
256
269
|
}
|
|
257
270
|
|
|
258
|
-
function
|
|
259
|
-
|
|
271
|
+
function setCursorPosition(elem: HTMLInputElement, pos: number) {
|
|
272
|
+
elem.focus()
|
|
273
|
+
elem.setSelectionRange(pos, pos)
|
|
260
274
|
}
|
|
261
275
|
|
|
262
|
-
function
|
|
263
|
-
|
|
276
|
+
function clearChat() {
|
|
277
|
+
Axios.post(props.apiEndpoint + '/prompt/clear', {
|
|
278
|
+
username: props.username,
|
|
279
|
+
project: props.projectPath
|
|
280
|
+
})
|
|
281
|
+
.then((response: AxiosResponse) => {
|
|
282
|
+
if (response.status != 200 || !response.data.success) {
|
|
283
|
+
setDisabledState(true)
|
|
284
|
+
addChatMessage(props.texts.loginError)
|
|
285
|
+
return console.log(
|
|
286
|
+
`Unsuccessful login, endpoint gave status ${response.status}`
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
resetChat()
|
|
291
|
+
sendInitialMessage()
|
|
292
|
+
})
|
|
293
|
+
.catch((error: Error) => {
|
|
294
|
+
setDisabledState(true)
|
|
295
|
+
addChatMessage(props.texts.loginError)
|
|
296
|
+
console.log(
|
|
297
|
+
'The following error ocurred while trying to communicate with the endpoint: \n' +
|
|
298
|
+
error
|
|
299
|
+
)
|
|
300
|
+
})
|
|
264
301
|
}
|
|
265
302
|
|
|
266
|
-
function
|
|
267
|
-
|
|
268
|
-
|
|
303
|
+
function getMessageClasses(sender: 'user' | 'bot') {
|
|
304
|
+
const classes: string[] = ['q-chatbot__messages-wrapper']
|
|
305
|
+
|
|
306
|
+
if(sender == 'user')
|
|
307
|
+
classes.push('q-chatbot__messages-wrapper_right')
|
|
308
|
+
|
|
309
|
+
return classes
|
|
269
310
|
}
|
|
270
311
|
|
|
271
312
|
watch(() => props.apiEndpoint, () => {
|
|
272
313
|
resetChat()
|
|
314
|
+
initChat()
|
|
273
315
|
})
|
|
274
316
|
|
|
275
317
|
defineOptions({ name: 'ChatBot' })
|
|
@@ -277,63 +319,29 @@
|
|
|
277
319
|
|
|
278
320
|
<template>
|
|
279
321
|
<div class="q-chatbot">
|
|
280
|
-
<div class="c-sidebar__subtitle">
|
|
281
|
-
<span>{{ props.texts.chatbotTitle }}</span>
|
|
282
|
-
</div>
|
|
283
|
-
|
|
284
322
|
<div
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
<div></div>
|
|
308
|
-
<div></div>
|
|
309
|
-
<div></div>
|
|
310
|
-
</div>
|
|
311
|
-
|
|
312
|
-
<template v-else>
|
|
313
|
-
<div
|
|
314
|
-
class="q-chatbot__sender"
|
|
315
|
-
v-if="message.text && message.sender == 'bot'">
|
|
316
|
-
{{
|
|
317
|
-
getSenderName(message.sender) +
|
|
318
|
-
' ' +
|
|
319
|
-
getConvertedTime(message.timestamp)
|
|
320
|
-
}}
|
|
321
|
-
</div>
|
|
322
|
-
<div
|
|
323
|
-
class="q-chatbot__timestamp"
|
|
324
|
-
v-if="message.text && message.sender == 'user'">
|
|
325
|
-
{{ getConvertedTime(message.timestamp) }}
|
|
326
|
-
</div>
|
|
327
|
-
<div
|
|
328
|
-
class="q-chatbot__text"
|
|
329
|
-
v-if="message.sender == 'bot'"
|
|
330
|
-
v-html="message.text"></div>
|
|
331
|
-
<div
|
|
332
|
-
class="q-chatbot__text"
|
|
333
|
-
v-else>
|
|
334
|
-
{{ message.text }}
|
|
335
|
-
</div>
|
|
336
|
-
</template>
|
|
323
|
+
ref="messagesContainer"
|
|
324
|
+
class="q-chatbot__content">
|
|
325
|
+
|
|
326
|
+
<!-- Chat tools -->
|
|
327
|
+
<div class="q-chatbot__tools">
|
|
328
|
+
<q-button
|
|
329
|
+
:title="props.texts.qButtonTitle"
|
|
330
|
+
b-style="plain"
|
|
331
|
+
class="clear-btn"
|
|
332
|
+
:disabled="isChatDisabled"
|
|
333
|
+
borderless
|
|
334
|
+
@click="clearChat">
|
|
335
|
+
<q-icon icon="bin" />
|
|
336
|
+
</q-button>
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
<div class="q-chatbot__messages-container">
|
|
340
|
+
<div
|
|
341
|
+
v-for="message in messages"
|
|
342
|
+
:key="message.id"
|
|
343
|
+
:class="getMessageClasses">
|
|
344
|
+
<CBMessage v-bind="message" :loading="isLoading && !message.message" />
|
|
337
345
|
</div>
|
|
338
346
|
</div>
|
|
339
347
|
</div>
|