@quidgest/chatbot 0.1.0 → 0.3.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/components/index.d.ts +2 -1
- package/dist/index.js +47 -49
- package/dist/index.mjs +7731 -2426
- package/dist/style.css +1 -1
- package/dist/types/message.type.d.ts +18 -0
- package/package.json +9 -7
- package/src/assets/styles/styles.scss +88 -34
- package/src/components/CBMessage.vue +31 -54
- package/src/components/ChatBot.vue +171 -114
- package/src/components/index.ts +1 -1
- package/src/types/message.type.ts +37 -0
- package/src/assets/copy.svg +0 -7
- package/src/assets/thumbDown.svg +0 -9
- package/src/assets/thumbUp.svg +0 -9
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
<div class="q-chatbot__tools">
|
|
6
6
|
<q-button
|
|
7
7
|
:title="props.texts.clearChat"
|
|
8
|
-
b-style="secondary"
|
|
9
8
|
:disabled="isChatDisabled"
|
|
10
9
|
borderless
|
|
11
10
|
@click="clearChat">
|
|
@@ -34,44 +33,48 @@
|
|
|
34
33
|
:sessionID="message.sessionID"/>
|
|
35
34
|
</div>
|
|
36
35
|
</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
36
|
</div>
|
|
54
37
|
|
|
55
38
|
<div class="q-chatbot__footer-container">
|
|
56
39
|
<q-label :label="props.texts.inputLabel"/>
|
|
57
40
|
<div
|
|
58
41
|
class="q-chatbot__footer"
|
|
42
|
+
@dragover.prevent="onDragOver"
|
|
43
|
+
@dragleave.prevent="onDragLeave"
|
|
44
|
+
@drop.prevent="onDrop"
|
|
59
45
|
:class="chatBotFooterClasses">
|
|
60
|
-
<div class="q-chatbot__input">
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
46
|
+
<div class="q-chatbot__input-wrapper">
|
|
47
|
+
<div
|
|
48
|
+
v-if="imagePreviewUrl"
|
|
49
|
+
class="q-chatbot__image-preview">
|
|
50
|
+
<img
|
|
51
|
+
:src="imagePreviewUrl"
|
|
52
|
+
tabindex="0"
|
|
53
|
+
alt="Image preview" />
|
|
54
|
+
<q-button
|
|
55
|
+
class="q-chatbot__remove-image"
|
|
56
|
+
tabindex="0"
|
|
57
|
+
flat
|
|
58
|
+
round
|
|
59
|
+
@click="removeImage">
|
|
60
|
+
<q-icon icon="bin" />
|
|
61
|
+
</q-button>
|
|
62
|
+
</div>
|
|
63
|
+
<div class="q-chatbot__input">
|
|
64
|
+
<q-text-area
|
|
65
|
+
v-model="userPrompt"
|
|
66
|
+
size="block"
|
|
67
|
+
autosize
|
|
68
|
+
resize="none"
|
|
69
|
+
:rows="2"
|
|
70
|
+
:disabled="isDisabled"
|
|
71
|
+
@keyup.enter="sendMessage" />
|
|
72
|
+
</div>
|
|
69
73
|
</div>
|
|
70
74
|
<div class="q-chatbot__send-container">
|
|
71
75
|
<!-- Upload button moved to the same container as send, but positioned to the left -->
|
|
72
76
|
<q-button
|
|
73
77
|
:title="props.texts.imageUpload"
|
|
74
|
-
b-style="primary"
|
|
75
78
|
class="q-chatbot__upload"
|
|
76
79
|
:disabled="isChatDisabled || isLoading || hasSelectedImage"
|
|
77
80
|
@click="triggerImageUpload">
|
|
@@ -86,15 +89,13 @@
|
|
|
86
89
|
@change="handleImageUpload"
|
|
87
90
|
:accept="acceptedImageTypes"
|
|
88
91
|
class="hidden-input" style="display: none;" />
|
|
89
|
-
|
|
90
|
-
<div class="spacer"></div>
|
|
91
92
|
|
|
92
93
|
<q-button
|
|
93
94
|
:title="props.texts.sendMessage"
|
|
94
|
-
|
|
95
|
+
variant="bold"
|
|
95
96
|
class="q-chatbot__send"
|
|
96
97
|
:disabled="isSendButtonDisabled"
|
|
97
|
-
:readonly="
|
|
98
|
+
:readonly="isSendButtonDisabled"
|
|
98
99
|
:loading="isLoading"
|
|
99
100
|
@click="sendMessage">
|
|
100
101
|
<q-icon icon="send" />
|
|
@@ -109,7 +110,7 @@
|
|
|
109
110
|
<script setup lang="ts">
|
|
110
111
|
import { onMounted, nextTick, ref, watch, computed } from 'vue'
|
|
111
112
|
import type { Ref } from 'vue'
|
|
112
|
-
import
|
|
113
|
+
import axios from 'axios'
|
|
113
114
|
import type { AxiosResponse } from 'axios'
|
|
114
115
|
import { CBMessage } from '@/components'
|
|
115
116
|
|
|
@@ -136,6 +137,7 @@
|
|
|
136
137
|
const imagePreviewUrl = ref<string | null>(null)
|
|
137
138
|
const acceptedImageTypes = computed(() => '.png, .jpeg, .jpg, .svg, .webp')
|
|
138
139
|
const hasSelectedImage = ref<boolean>(false)
|
|
140
|
+
const isDragging = ref(false)
|
|
139
141
|
|
|
140
142
|
// Computed property to check if the send button should be enabled
|
|
141
143
|
const isSendButtonDisabled = computed(() => {
|
|
@@ -179,7 +181,8 @@
|
|
|
179
181
|
|
|
180
182
|
const chatBotFooterClasses = computed(() => {
|
|
181
183
|
return {
|
|
182
|
-
'q-chatbot__footer-disabled': isDisabled.value
|
|
184
|
+
'q-chatbot__footer-disabled': isDisabled.value,
|
|
185
|
+
'drag-over' : isDragging.value && !hasSelectedImage.value,
|
|
183
186
|
}
|
|
184
187
|
})
|
|
185
188
|
|
|
@@ -187,39 +190,50 @@
|
|
|
187
190
|
isChatDisabled.value = state
|
|
188
191
|
}
|
|
189
192
|
|
|
190
|
-
function initChat() {
|
|
191
|
-
|
|
193
|
+
async function initChat() {
|
|
194
|
+
const response = await axios.post(props.apiEndpoint + '/auth/login', {
|
|
192
195
|
username: props.username,
|
|
193
196
|
password: 'test'
|
|
194
197
|
})
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
.catch((error: Error) => {
|
|
198
|
+
|
|
199
|
+
if(!response.data) {
|
|
200
|
+
addChatMessage(props.texts.loginError)
|
|
201
|
+
setDisabledState(true)
|
|
202
|
+
isLoading.value = false
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (response.status !== 200 || !response.data.success) {
|
|
205
207
|
setDisabledState(true)
|
|
206
208
|
addChatMessage(props.texts.loginError)
|
|
207
|
-
console.log(
|
|
208
|
-
|
|
209
|
-
|
|
209
|
+
console.log(`Unsuccessful login, endpoint gave status ${response.status}`)
|
|
210
|
+
return
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
loadChatData()
|
|
214
|
+
};
|
|
210
215
|
|
|
211
|
-
function loadChatData() {
|
|
212
|
-
|
|
216
|
+
async function loadChatData() {
|
|
217
|
+
const response = await axios.post(props.apiEndpoint + '/prompt/load', {
|
|
213
218
|
username: props.username,
|
|
214
219
|
project: props.projectPath
|
|
215
220
|
})
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
|
|
222
|
+
if(!response.data) {
|
|
223
|
+
setDisabledState(true)
|
|
224
|
+
addChatMessage(props.texts.loginError)
|
|
225
|
+
setDisabledState(true)
|
|
226
|
+
isLoading.value = false
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (response.status !== 200 || !response.data.success) {
|
|
231
|
+
setDisabledState(true)
|
|
232
|
+
addChatMessage(props.texts.loginError)
|
|
233
|
+
console.log(`Unsuccessful load, endpoint gave status ${response.status}`)
|
|
234
|
+
return
|
|
222
235
|
}
|
|
236
|
+
|
|
223
237
|
sendInitialMessage()
|
|
224
238
|
response.data.history.forEach((message: ChatBotMessageContent) => {
|
|
225
239
|
const imgUrl = message.imageUrl ? props.controllerEndpoint + message.imageUrl : undefined
|
|
@@ -227,14 +241,8 @@
|
|
|
227
241
|
message.content,
|
|
228
242
|
message.type === 'ai' ? 'bot' : 'user',
|
|
229
243
|
imgUrl,
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
.catch((error: Error) => {
|
|
235
|
-
setDisabledState(true)
|
|
236
|
-
addChatMessage(props.texts.loginError)
|
|
237
|
-
console.log('Error loading chat data: ' + error)
|
|
244
|
+
message.sessionID
|
|
245
|
+
)
|
|
238
246
|
})
|
|
239
247
|
}
|
|
240
248
|
|
|
@@ -284,10 +292,7 @@
|
|
|
284
292
|
function scrollToBottom() {
|
|
285
293
|
nextTick(() => {
|
|
286
294
|
if (messagesContainer.value) {
|
|
287
|
-
messagesContainer.value.
|
|
288
|
-
top: messagesContainer.value.scrollHeight,
|
|
289
|
-
behavior: 'smooth'
|
|
290
|
-
})
|
|
295
|
+
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
|
|
291
296
|
}
|
|
292
297
|
})
|
|
293
298
|
}
|
|
@@ -335,9 +340,14 @@
|
|
|
335
340
|
)
|
|
336
341
|
return
|
|
337
342
|
|
|
338
|
-
|
|
343
|
+
|
|
344
|
+
if(messagesContainer.value) {
|
|
345
|
+
messagesContainer.value.scrollTo({
|
|
346
|
+
top: messagesContainer.value.scrollHeight,
|
|
347
|
+
behavior: 'smooth'
|
|
348
|
+
})
|
|
349
|
+
}
|
|
339
350
|
addChatMessage(userPrompt.value, 'user', imagePreviewUrl.value)
|
|
340
|
-
scrollToBottom()
|
|
341
351
|
|
|
342
352
|
// Send prompt to bot
|
|
343
353
|
setChatPrompt(userPrompt.value, imageInput.value?.files?.[0])
|
|
@@ -345,7 +355,7 @@
|
|
|
345
355
|
userPrompt.value = '' // Clear user input
|
|
346
356
|
}
|
|
347
357
|
|
|
348
|
-
function setChatPrompt(prompt: string, image?: File) {
|
|
358
|
+
async function setChatPrompt(prompt: string, image?: File) {
|
|
349
359
|
// Add an empty bot message marked as streaming to trigger bouncing dots animation
|
|
350
360
|
addChatMessage('', 'bot')
|
|
351
361
|
let msg = getLastMessage()
|
|
@@ -353,58 +363,64 @@
|
|
|
353
363
|
|
|
354
364
|
const currentSessionID: string = msg?.sessionID || ''
|
|
355
365
|
|
|
356
|
-
|
|
357
|
-
|
|
366
|
+
const formData = new FormData()
|
|
358
367
|
if (image) {
|
|
359
|
-
|
|
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
|
-
}
|
|
368
|
+
formData.append('image', image)
|
|
374
369
|
}
|
|
375
|
-
|
|
370
|
+
|
|
371
|
+
formData.append('message', prompt)
|
|
372
|
+
formData.append('project', props.projectPath)
|
|
373
|
+
formData.append('user', props.username)
|
|
374
|
+
formData.append('sessionID', currentSessionID)
|
|
375
|
+
|
|
376
|
+
|
|
376
377
|
isLoading.value = true
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
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
|
|
378
|
+
|
|
379
|
+
const response = await axios.post(props.apiEndpoint + '/prompt/submit', formData, {
|
|
380
|
+
headers: {
|
|
381
|
+
'Content-Type': 'text/event-stream',
|
|
382
|
+
'Accept': 'text/event-stream',
|
|
383
|
+
},
|
|
384
|
+
responseType: 'stream',
|
|
385
|
+
adapter: 'fetch',
|
|
395
386
|
})
|
|
396
|
-
|
|
387
|
+
|
|
388
|
+
if(!response.data) {
|
|
397
389
|
addChatMessage(props.texts.botIsSick)
|
|
398
390
|
setDisabledState(true)
|
|
399
|
-
console.log(error)
|
|
400
|
-
})
|
|
401
|
-
.finally(() => {
|
|
402
391
|
isLoading.value = false
|
|
403
|
-
|
|
404
|
-
}
|
|
392
|
+
return
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const reader = response.data.getReader()
|
|
396
|
+
const decoder = new TextDecoder("utf-8")
|
|
397
|
+
|
|
398
|
+
while(true) {
|
|
399
|
+
const { done, value } = await reader.read()
|
|
400
|
+
if(done) break
|
|
401
|
+
|
|
402
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
403
|
+
const eventList = chunk.match(/data:\s*({.*?})/g)
|
|
404
|
+
if(!eventList) continue
|
|
405
|
+
|
|
406
|
+
for(const event of eventList) {
|
|
407
|
+
try {
|
|
408
|
+
const rawData = event.split('data:')[1].trim()
|
|
409
|
+
const data = JSON.parse(rawData)
|
|
410
|
+
if(msg) {
|
|
411
|
+
msg.message += data.value
|
|
412
|
+
}
|
|
413
|
+
} catch (error) {
|
|
414
|
+
console.error('Error parsing match:', error)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
if(autoScrollEnabled.value) scrollToBottom()
|
|
418
|
+
}
|
|
419
|
+
isLoading.value = false
|
|
420
|
+
}
|
|
405
421
|
|
|
406
422
|
function clearChat() {
|
|
407
|
-
|
|
423
|
+
axios.post(props.apiEndpoint + '/prompt/clear', {
|
|
408
424
|
username: props.username,
|
|
409
425
|
project: props.projectPath
|
|
410
426
|
})
|
|
@@ -430,7 +446,48 @@
|
|
|
430
446
|
if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
|
|
431
447
|
return classes
|
|
432
448
|
}
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
function onDragOver(event: DragEvent) {
|
|
452
|
+
event.preventDefault()
|
|
453
|
+
if (!isDisabled.value) {
|
|
454
|
+
isDragging.value = true
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function onDragLeave(event: DragEvent) {
|
|
459
|
+
event.preventDefault()
|
|
460
|
+
isDragging.value = false
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function onDrop(event: DragEvent) {
|
|
464
|
+
event.preventDefault()
|
|
465
|
+
isDragging.value = false
|
|
466
|
+
|
|
467
|
+
if (isDisabled.value || hasSelectedImage.value)
|
|
468
|
+
return
|
|
469
|
+
|
|
470
|
+
const files = event.dataTransfer?.files
|
|
471
|
+
if (files && files.length > 0) {
|
|
472
|
+
// Check if file is an image
|
|
473
|
+
const file = files[0]
|
|
474
|
+
if (file.type.startsWith('image/')) {
|
|
475
|
+
// Create a file input event
|
|
476
|
+
if (imageInput.value) {
|
|
477
|
+
// Create a new FileList-like object
|
|
478
|
+
const dataTransfer = new DataTransfer()
|
|
479
|
+
dataTransfer.items.add(file)
|
|
480
|
+
imageInput.value.files = dataTransfer.files
|
|
481
|
+
|
|
482
|
+
// Set the preview URL
|
|
483
|
+
imagePreviewUrl.value = URL.createObjectURL(file)
|
|
484
|
+
hasSelectedImage.value = true
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
433
488
|
|
|
489
|
+
}
|
|
490
|
+
|
|
434
491
|
watch(
|
|
435
492
|
() => props.apiEndpoint,
|
|
436
493
|
() => {
|
package/src/components/index.ts
CHANGED
|
@@ -16,3 +16,40 @@ export type ChatBotMessageContent = {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export type ChatBotMessageSender = 'bot' | 'user'
|
|
19
|
+
|
|
20
|
+
export interface CBMessageProps {
|
|
21
|
+
/*
|
|
22
|
+
* Sender of the message
|
|
23
|
+
*/
|
|
24
|
+
sender?: ChatBotMessageSender
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* Message to be displayed
|
|
28
|
+
*/
|
|
29
|
+
message?: string
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* Date of when the message was sent
|
|
33
|
+
*/
|
|
34
|
+
date?: Date
|
|
35
|
+
|
|
36
|
+
/*
|
|
37
|
+
* If the message is loading
|
|
38
|
+
*/
|
|
39
|
+
loading?: boolean
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Project locale
|
|
43
|
+
*/
|
|
44
|
+
dateFormat?: string
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* User image
|
|
48
|
+
*/
|
|
49
|
+
userImage: string
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Chatbot image
|
|
53
|
+
*/
|
|
54
|
+
chatbotImage: string
|
|
55
|
+
}
|
package/src/assets/copy.svg
DELETED
package/src/assets/thumbDown.svg
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
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>
|
package/src/assets/thumbUp.svg
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
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>
|