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