@quidgest/chatbot 0.0.8 → 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.
@@ -1,338 +1,443 @@
1
- <script setup lang="ts">
2
- import {
3
- onMounted,
4
- useTemplateRef,
5
- nextTick,
6
- ref,
7
- watch,
8
- defineOptions
9
- } from 'vue'
10
- import type { Ref } from 'vue'
11
- import Axios from 'axios'
12
- import type { AxiosResponse } from 'axios'
13
- import { CBMessage } from '@/components'
14
-
15
- import {
16
- QButton,
17
- QTextArea,
18
- QIcon,
19
- QLabel
20
- } from '@quidgest/ui/components'
21
-
22
- import type { ResourceStrings } from '@/types/texts.type'
23
- import type {
24
- ChatBotMessage,
25
- ChatBotMessageContent
26
- } from '@/types/message.type'
27
-
28
- let messages: Ref<ChatBotMessage[]> = ref([])
29
- let nextMessageId: number = 1
30
- let userPrompt: Ref<string> = ref('')
31
- let isLoading: Ref<boolean> = ref(false)
32
- let isChatDisabled: boolean = false
33
-
34
- // refs
35
- const scrollElement = useTemplateRef('scrollElement')
36
-
37
- export type ChatBotProps = {
38
- /**
39
- * API Enpoint URL
40
- */
41
- apiEndpoint?: string
42
-
43
- /**
44
- * Static resource texts used by ChatBot
45
- */
46
- texts?: ResourceStrings
47
-
48
- /**
49
- * Genio username
50
- */
51
- username: string
52
-
53
- /**
54
- * Project aplication path
55
- */
56
- projectPath: string
57
-
58
- /**
59
- * Project locale
60
- */
61
- dateFormat?: string
62
- }
63
-
64
- const props = withDefaults(defineProps<ChatBotProps>(), {
65
- apiEndpoint: 'http://localhost:3000',
66
- texts: () => ({
67
- chatbotTitle: 'ChatBot',
68
- qButtonTitle: 'Send message',
69
- inputLabel: 'What can I help with?',
70
- initialMessage:
71
- "Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
72
- loginError:
73
- 'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
74
- botIsSick:
75
- '*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!'
76
- })
77
- })
1
+ <template>
2
+ <div class="q-chatbot">
3
+ <div class="q-chatbot__content">
4
+ <!-- Chat tools -->
5
+ <div class="q-chatbot__tools">
6
+ <q-button
7
+ :title="props.texts.clearChat"
8
+ b-style="secondary"
9
+ :disabled="isChatDisabled"
10
+ borderless
11
+ @click="clearChat">
12
+ <QIcon icon="bin" />
13
+ </q-button>
14
+ </div>
15
+
16
+ <!-- Attach ref to messages container -->
17
+ <div
18
+ ref="messagesContainer"
19
+ class="q-chatbot__messages-container"
20
+ @scroll="handleScroll">
21
+ <div
22
+ v-for="message in messages"
23
+ :key="message.id"
24
+ :class="getMessageClasses(message.sender)">
25
+ <!-- Pass the streaming flag to CBMessage for animation -->
26
+ <c-b-message
27
+ v-bind="message"
28
+ :date-format="props.dateFormat"
29
+ :user-image="props.userImage"
30
+ :chatbot-image="props.chatbotImage"
31
+ :loading="isLoading && !message.message"
32
+ :imagePreviewUrl="message.imagePreviewUrl"
33
+ :apiEndpoint="props.apiEndpoint"
34
+ :sessionID="message.sessionID"/>
35
+ </div>
36
+ </div>
37
+ <!--show image preview if exists-->
38
+ <div
39
+ v-if="imagePreviewUrl"
40
+ class="q-chatbot__image-preview">
41
+ <img
42
+ :src="imagePreviewUrl"
43
+ alt="Image preview" />
44
+ <q-button
45
+ class="q-chatbot__remove-image"
46
+ b-style="secondary"
47
+ flat
48
+ round
49
+ @click="removeImage">
50
+ <q-icon icon="bin" />
51
+ </q-button>
52
+ </div>
53
+ </div>
54
+
55
+ <div class="q-chatbot__footer-container">
56
+ <q-label :label="props.texts.inputLabel"/>
57
+ <div
58
+ class="q-chatbot__footer"
59
+ :class="chatBotFooterClasses">
60
+ <div class="q-chatbot__input">
61
+ <q-text-area
62
+ v-model="userPrompt"
63
+ size="block"
64
+ autosize
65
+ resize="none"
66
+ :rows="2"
67
+ :disabled="isDisabled"
68
+ @keyup.enter="sendMessage" />
69
+ </div>
70
+ <div class="q-chatbot__send-container">
71
+ <!-- Upload button moved to the same container as send, but positioned to the left -->
72
+ <q-button
73
+ :title="props.texts.imageUpload"
74
+ b-style="primary"
75
+ class="q-chatbot__upload"
76
+ :disabled="isChatDisabled || isLoading || hasSelectedImage"
77
+ @click="triggerImageUpload">
78
+ <q-icon icon="upload" />
79
+ </q-button>
80
+
81
+ <!-- Hidden file input -->
82
+ <input
83
+ id="image-upload"
84
+ type="file"
85
+ ref="imageInput"
86
+ @change="handleImageUpload"
87
+ :accept="acceptedImageTypes"
88
+ class="hidden-input" style="display: none;" />
89
+
90
+ <div class="spacer"></div>
91
+
92
+ <q-button
93
+ :title="props.texts.sendMessage"
94
+ b-style="primary"
95
+ class="q-chatbot__send"
96
+ :disabled="isSendButtonDisabled"
97
+ :readonly="isDisabled"
98
+ :loading="isLoading"
99
+ @click="sendMessage">
100
+ <q-icon icon="send" />
101
+ </q-button>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </template>
107
+
78
108
 
79
- onMounted(() => {
80
- initChat()
109
+ <script setup lang="ts">
110
+ import { onMounted, nextTick, ref, watch, computed } from 'vue'
111
+ import type { Ref } from 'vue'
112
+ import Axios from 'axios'
113
+ import type { AxiosResponse } from 'axios'
114
+ import { CBMessage } from '@/components'
115
+
116
+ import { QButton, QTextArea, QIcon, QLabel } from '@quidgest/ui/components'
117
+ import type { ChatBotMessage, ChatBotMessageContent, ChatBotMessageSender } from '@/types/message.type'
118
+ import { v4 as uuidv4 } from 'uuid'
119
+
120
+ import ChatBotIcon from '@/assets/chatbot.png'
121
+ import UserIcon from '@/assets/user_avatar.png'
122
+ import { ChatBotProps } from '@/types/chatbot.type'
123
+
124
+ const messages: Ref<ChatBotMessage[]> = ref([])
125
+ const nextMessageId = ref(1)
126
+ const userPrompt = ref('')
127
+ const isLoading = ref(false)
128
+ const isChatDisabled = ref(false)
129
+
130
+ // Ref for the messages container
131
+ const messagesContainer = ref<HTMLElement | null>(null)
132
+ // Flag to control auto-scrolling
133
+ const autoScrollEnabled = ref(true)
134
+
135
+ const imageInput = ref<HTMLInputElement | null>(null)
136
+ const imagePreviewUrl = ref<string | null>(null)
137
+ const acceptedImageTypes = computed(() => '.png, .jpeg, .jpg, .svg, .webp')
138
+ const hasSelectedImage = ref<boolean>(false)
139
+
140
+ // Computed property to check if the send button should be enabled
141
+ const isSendButtonDisabled = computed(() => {
142
+ return userPrompt.value.trim().length === 0 || isLoading.value || isChatDisabled.value
81
143
  })
82
144
 
83
- function setDisabledState(state: boolean) {
84
- isChatDisabled = state
85
- }
86
-
87
- function initChat() {
88
- Axios.post(props.apiEndpoint + '/auth/login', {
89
- username: props.username,
90
- password: 'test'
91
- })
92
- .then((response: AxiosResponse) => {
93
- if (response.status != 200 || !response.data.success) {
94
- setDisabledState(true)
95
- addChatMessage(props.texts.loginError)
96
- return console.log(
97
- `Unsuccessful login, endpoint gave status ${response.status}`
98
- )
99
- }
100
-
101
- loadChatData()
102
- })
103
- .catch((error: Error) => {
104
- setDisabledState(true)
105
- addChatMessage(props.texts.loginError)
106
- console.log(
107
- 'The following error ocurred while trying to login: \n' +
108
- error
109
- )
110
- })
111
- }
112
-
113
- function loadChatData() {
114
- Axios.post(props.apiEndpoint + '/prompt/load', {
115
- username: props.username,
116
- project: props.projectPath
117
- })
118
- .then((response: AxiosResponse) => {
119
- if (response.status != 200 || !response.data.success) {
120
- setDisabledState(true)
121
- addChatMessage(props.texts.loginError)
122
- return console.log(
123
- `Unsuccessful load, endpoint gave status ${response.status}`
124
- )
125
- }
126
-
127
- sendInitialMessage()
128
- response.data.history.forEach(
129
- (message: ChatBotMessageContent) => {
130
- addChatMessage(
131
- message.content,
132
- message.type === 'ai' ? 'bot' : 'user'
133
- )
134
- }
135
- )
136
- })
137
- .catch((error: Error) => {
138
- setDisabledState(true)
139
- addChatMessage(props.texts.loginError)
140
- console.log(
141
- 'The following error ocurred while trying to login: \n' +
142
- error
143
- )
144
- })
145
- }
146
-
147
- function addChatMessage(message: string, sender: 'bot' | 'user' = 'bot') {
148
- messages.value.push({
149
- id: nextMessageId++,
150
- message,
151
- date: new Date(),
152
- sender: sender
153
- })
154
-
155
- nextTick(scrollToBottom)
156
- }
157
-
158
- function getLastMessage() {
159
- return messages.value.find(
160
- (m: ChatBotMessage) => m.id === nextMessageId - 1
161
- )
162
- }
163
-
164
- function sendInitialMessage() {
165
- addChatMessage(props.texts.initialMessage)
166
- }
167
-
168
- function resetChat() {
169
- messages.value = []
170
- userPrompt.value = ''
171
- isLoading.value = false
172
- setDisabledState(false)
173
- }
174
-
175
- function scrollToBottom() {
176
- scrollElement.value?.scrollIntoView({ behavior: 'smooth' })
177
- }
178
-
179
- function sendMessage() {
180
- if (
181
- userPrompt.value.trim().length == 0 ||
182
- isLoading.value ||
183
- isChatDisabled
184
- )
185
- return
186
-
187
- addChatMessage(userPrompt.value, 'user')
188
-
189
- setChatPrompt(userPrompt.value)
190
-
191
- userPrompt.value = '' //Clear user input
192
- }
193
-
194
- function setChatPrompt(prompt: string) {
195
- addChatMessage('') //Create empty bot message
196
- let msg = getLastMessage()
197
-
198
- //Send message request
199
- let params = {
200
- message: prompt,
201
- project: props.projectPath,
202
- user: props.username
203
- }
204
-
205
- isLoading.value = true
206
- Axios({
207
- url: props.apiEndpoint + '/prompt/message',
208
- method: 'POST',
209
- data: params,
210
- onDownloadProgress: (progressEvent) => {
211
- const chunk = progressEvent.event?.currentTarget.response
212
- const status = progressEvent.event?.currentTarget.status
213
-
214
- if (status != 200) return
215
-
216
- if (msg) msg.message = chunk
217
- scrollToBottom()
218
- }
219
- })
220
- .then(({ data }) => {
221
- if (msg) msg.message = data
222
- })
223
- .catch((error) => {
224
- addChatMessage(props.texts.botIsSick)
225
-
226
- setDisabledState(true)
227
- console.log(error)
228
- })
229
- .finally(() => {
230
- isLoading.value = false
231
- })
232
- }
233
-
234
- function clearChat() {
235
- Axios.post(props.apiEndpoint + '/prompt/clear', {
236
- username: props.username,
237
- project: props.projectPath
238
- })
239
- .then((response: AxiosResponse) => {
240
- if (response.status != 200 || !response.data.success) {
241
- setDisabledState(true)
242
- addChatMessage(props.texts.loginError)
243
- return console.log(
244
- `Unsuccessful login, endpoint gave status ${response.status}`
245
- )
246
- }
247
-
248
- resetChat()
249
- sendInitialMessage()
250
- })
251
- .catch((error: Error) => {
252
- setDisabledState(true)
253
- addChatMessage(props.texts.loginError)
254
- console.log(
255
- 'The following error ocurred while trying to communicate with the endpoint: \n' +
256
- error
257
- )
258
- })
259
- }
260
-
261
- function getMessageClasses(sender: 'user' | 'bot') {
262
- const classes: string[] = ['q-chatbot__messages-wrapper']
263
-
264
- if (sender == 'user') classes.push('q-chatbot__messages-wrapper_right')
265
-
266
- return classes
267
- }
268
-
269
- watch(
270
- () => props.apiEndpoint,
271
- () => {
272
- resetChat()
273
- initChat()
274
- }
275
- )
276
-
277
- defineOptions({ name: 'ChatBot' })
145
+ const props = withDefaults(defineProps<ChatBotProps>(), {
146
+ apiEndpoint: 'http://localhost:3000',
147
+ userImage: UserIcon,
148
+ chatbotImage: ChatBotIcon,
149
+ mode: 'chat',
150
+ texts: () => ({
151
+ chatbotTitle: 'ChatBot',
152
+ sendMessage: 'Send message',
153
+ clearChat: 'Clear chat',
154
+ inputLabel: 'What can I help with?',
155
+ imageUpload: 'Upload Image',
156
+ imageUploadQButton: 'Upload Image',
157
+ goodResponse: 'Good response',
158
+ badResponse: 'Bad response',
159
+ initialMessage:
160
+ "Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
161
+ initialAgentMessage: 'Just a temporary message while we are working on the agent mode',
162
+ loginError:
163
+ 'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
164
+ botIsSick:
165
+ '*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!'
166
+ })
167
+ })
168
+
169
+ onMounted(() => {
170
+ initChat()
171
+ nextTick(() => {
172
+ messagesContainer.value?.addEventListener('scroll', handleScroll)
173
+ })
174
+ })
175
+
176
+ const isDisabled = computed(() => {
177
+ return isChatDisabled.value || isLoading.value
178
+ })
179
+
180
+ const chatBotFooterClasses = computed(() => {
181
+ return {
182
+ 'q-chatbot__footer-disabled': isDisabled.value
183
+ }
184
+ })
185
+
186
+ function setDisabledState(state: boolean) {
187
+ isChatDisabled.value = state
188
+ }
189
+
190
+ function initChat() {
191
+ Axios.post(props.apiEndpoint + '/auth/login', {
192
+ username: props.username,
193
+ password: 'test'
194
+ })
195
+ .then((response: AxiosResponse) => {
196
+ if (response.status !== 200 || !response.data.success) {
197
+ setDisabledState(true)
198
+ addChatMessage(props.texts.loginError)
199
+ console.log(`Unsuccessful login, endpoint gave status ${response.status}`)
200
+ return
201
+ }
202
+ loadChatData()
203
+ })
204
+ .catch((error: Error) => {
205
+ setDisabledState(true)
206
+ addChatMessage(props.texts.loginError)
207
+ console.log('Error during login: ' + error)
208
+ })
209
+ }
210
+
211
+ function loadChatData() {
212
+ Axios.post(props.apiEndpoint + '/prompt/load', {
213
+ username: props.username,
214
+ project: props.projectPath
215
+ })
216
+ .then((response: AxiosResponse) => {
217
+ if (response.status !== 200 || !response.data.success) {
218
+ setDisabledState(true)
219
+ addChatMessage(props.texts.loginError)
220
+ console.log(`Unsuccessful load, endpoint gave status ${response.status}`)
221
+ return
222
+ }
223
+ sendInitialMessage()
224
+ response.data.history.forEach((message: ChatBotMessageContent) => {
225
+ const imgUrl = message.imageUrl ? props.controllerEndpoint + message.imageUrl : undefined
226
+ addChatMessage(
227
+ message.content,
228
+ message.type === 'ai' ? 'bot' : 'user',
229
+ imgUrl,
230
+ message.sessionID
231
+ )
232
+ })
233
+ })
234
+ .catch((error: Error) => {
235
+ setDisabledState(true)
236
+ addChatMessage(props.texts.loginError)
237
+ console.log('Error loading chat data: ' + error)
238
+ })
239
+ }
240
+
241
+ // Modified addChatMessage to add isStreaming flag for empty bot messages
242
+ function addChatMessage(
243
+ message: string,
244
+ sender: 'bot' | 'user' = 'bot',
245
+ imagePreviewUrl: string | null = null,
246
+ sessionID?: string,
247
+ isWelcomeMessage?: boolean
248
+ ) {
249
+ messages.value.push({
250
+ id: nextMessageId.value++,
251
+ message,
252
+ date: new Date(),
253
+ sender: sender,
254
+ imagePreviewUrl: imagePreviewUrl ?? undefined,
255
+ sessionID: sessionID || uuidv4(),
256
+ isWelcomeMessage: isWelcomeMessage ?? false
257
+ })
258
+ nextTick(() => {
259
+ if (autoScrollEnabled.value) scrollToBottom()
260
+ })
261
+ }
262
+
263
+ function getLastMessage() {
264
+ return messages.value.find(
265
+ (m: ChatBotMessage) => m.id === nextMessageId.value - 1
266
+ )
267
+ }
268
+
269
+ function sendInitialMessage() {
270
+ const message = props.mode === 'chat'
271
+ ? props.texts.initialMessage
272
+ : props.texts.initialAgentMessage
273
+ addChatMessage(message, 'bot', null, undefined, true)
274
+ }
275
+
276
+ function resetChat() {
277
+ messages.value = []
278
+ userPrompt.value = ''
279
+ isLoading.value = false
280
+ setDisabledState(false)
281
+ autoScrollEnabled.value = true
282
+ }
283
+
284
+ function scrollToBottom() {
285
+ nextTick(() => {
286
+ if (messagesContainer.value) {
287
+ messagesContainer.value.scrollTo({
288
+ top: messagesContainer.value.scrollHeight,
289
+ behavior: 'smooth'
290
+ })
291
+ }
292
+ })
293
+ }
294
+
295
+ function handleScroll() {
296
+ if (messagesContainer.value) {
297
+ const threshold = 20 // px threshold from the bottom
298
+ const { scrollTop, clientHeight, scrollHeight } = messagesContainer.value
299
+ if (scrollTop + clientHeight >= scrollHeight - threshold) {
300
+ autoScrollEnabled.value = true
301
+ } else {
302
+ autoScrollEnabled.value = false
303
+ }
304
+ }
305
+ }
306
+
307
+ function removeImage() {
308
+ imagePreviewUrl.value = ''
309
+ if (imageInput.value) {
310
+ imageInput.value.value = ''
311
+ }
312
+ hasSelectedImage.value = false
313
+ }
314
+
315
+ function triggerImageUpload() {
316
+ imageInput.value?.click()
317
+ }
318
+
319
+ function handleImageUpload(event: Event) {
320
+ // Store the selected image in imageInput
321
+ const target = event.target as HTMLInputElement
322
+ imageInput.value = target
323
+
324
+ if (target.files && target.files[0]) {
325
+ imagePreviewUrl.value = URL.createObjectURL(target.files[0])
326
+ hasSelectedImage.value = true
327
+ }
328
+ }
329
+
330
+ function sendMessage() {
331
+ if (
332
+ userPrompt.value.trim().length === 0 ||
333
+ isLoading.value ||
334
+ isChatDisabled.value
335
+ )
336
+ return
337
+
338
+ // Add user's message and force scroll to bottom
339
+ addChatMessage(userPrompt.value, 'user', imagePreviewUrl.value)
340
+ scrollToBottom()
341
+
342
+ // Send prompt to bot
343
+ setChatPrompt(userPrompt.value, imageInput.value?.files?.[0])
344
+ removeImage()
345
+ userPrompt.value = '' // Clear user input
346
+ }
347
+
348
+ function setChatPrompt(prompt: string, image?: File) {
349
+ // Add an empty bot message marked as streaming to trigger bouncing dots animation
350
+ addChatMessage('', 'bot')
351
+ let msg = getLastMessage()
352
+
353
+
354
+ const currentSessionID: string = msg?.sessionID || ''
355
+
356
+ let params = null
357
+
358
+ if (image) {
359
+ params = new FormData()
360
+ // Create the FormData
361
+ params.append('message', prompt)
362
+ params.append('project', props.projectPath)
363
+ params.append('user', props.username)
364
+ params.append('image', image)
365
+ params.append('sessionID', currentSessionID)
366
+ } else {
367
+ //Send message request
368
+ params = {
369
+ message: prompt,
370
+ project: props.projectPath,
371
+ user: props.username,
372
+ sessionID: currentSessionID
373
+ }
374
+ }
375
+
376
+ isLoading.value = true
377
+ Axios({
378
+ url: props.apiEndpoint + '/prompt/message',
379
+ method: 'POST',
380
+ data: params,
381
+ onDownloadProgress: (progressEvent) => {
382
+ const chunk = progressEvent.event?.currentTarget.response
383
+ const status = progressEvent.event?.currentTarget.status
384
+
385
+ if (status !== 200) return
386
+
387
+ if (msg) {
388
+ msg.message = chunk
389
+ }
390
+ if (autoScrollEnabled.value) scrollToBottom()
391
+ }
392
+ })
393
+ .then(({ data }) => {
394
+ if (msg) msg.message = data
395
+ })
396
+ .catch((error) => {
397
+ addChatMessage(props.texts.botIsSick)
398
+ setDisabledState(true)
399
+ console.log(error)
400
+ })
401
+ .finally(() => {
402
+ isLoading.value = false
403
+ })
404
+ }
405
+
406
+ function clearChat() {
407
+ Axios.post(props.apiEndpoint + '/prompt/clear', {
408
+ username: props.username,
409
+ project: props.projectPath
410
+ })
411
+ .then((response: AxiosResponse) => {
412
+ if (response.status !== 200 || !response.data.success) {
413
+ setDisabledState(true)
414
+ addChatMessage(props.texts.loginError)
415
+ console.log(`Unsuccessful clear, endpoint gave status ${response.status}`)
416
+ return
417
+ }
418
+ resetChat()
419
+ sendInitialMessage()
420
+ })
421
+ .catch((error: Error) => {
422
+ setDisabledState(true)
423
+ addChatMessage(props.texts.loginError)
424
+ console.log('Error clearing chat: ' + error)
425
+ })
426
+ }
427
+
428
+ function getMessageClasses(sender: ChatBotMessageSender) {
429
+ const classes: string[] = ['q-chatbot__messages-wrapper']
430
+ if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
431
+ return classes
432
+ }
433
+
434
+ watch(
435
+ () => props.apiEndpoint,
436
+ () => {
437
+ resetChat()
438
+ initChat()
439
+ }
440
+ )
441
+
442
+ defineOptions({ name: 'ChatBot' })
278
443
  </script>
279
-
280
- <template>
281
- <div class="q-chatbot">
282
- <div class="q-chatbot__content">
283
- <!-- Chat tools -->
284
- <div class="q-chatbot__tools">
285
- <QButton
286
- :title="props.texts.qButtonTitle"
287
- b-style="secondary"
288
- :disabled="isChatDisabled"
289
- borderless
290
- @click="clearChat">
291
- <QIcon icon="bin" />
292
- </QButton>
293
- </div>
294
-
295
- <div class="q-chatbot__messages-container">
296
- <div
297
- v-for="message in messages"
298
- :key="message.id"
299
- :class="getMessageClasses(message.sender)">
300
- <CBMessage
301
- v-bind="message"
302
- :date-format="props.dateFormat"
303
- :loading="isLoading && !message.message" />
304
- </div>
305
- </div>
306
-
307
- <div ref="scrollElement"></div>
308
-
309
- <div class="q-chatbot__footer-container">
310
- <label> {{ props.texts.inputLabel }} </label>
311
- <div class="q-chatbot__footer">
312
- <div class="q-chatbot__input">
313
- <QTextArea
314
- v-model="userPrompt"
315
- size="block"
316
- autosize
317
- resize="none"
318
- :rows="2"
319
- :disabled="isChatDisabled"
320
- @keyup.enter="sendMessage" />
321
- </div>
322
- <div class="q-chatbot__send-container">
323
- <QButton
324
- :title="props.texts.qButtonTitle"
325
- b-style="primary"
326
- class="q-chatbot__send"
327
- :disabled="isChatDisabled || isLoading"
328
- :loading="isLoading"
329
- @click="sendMessage">
330
- <QIcon icon="send" />
331
- </QButton>
332
- </div>
333
- </div>
334
- </div>
335
- </div>
336
-
337
- </div>
338
- </template>