@quidgest/chatbot 0.4.0 → 0.5.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.
Files changed (73) hide show
  1. package/dist/components/ChatBot/ChatBot.vue.d.ts +53 -0
  2. package/dist/components/ChatBot/types.d.ts +48 -0
  3. package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +17 -0
  4. package/dist/components/ChatBotInput/index.d.ts +5 -0
  5. package/dist/components/ChatBotInput/types.d.ts +28 -0
  6. package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +41 -0
  7. package/dist/components/ChatBotMessage/ChatBotMessageButtons.vue.d.ts +21 -0
  8. package/dist/components/ChatBotMessage/index.d.ts +6 -0
  9. package/dist/components/ChatBotMessage/types.d.ts +46 -0
  10. package/dist/components/ChatToolBar/ChatToolBar.vue.d.ts +39 -0
  11. package/dist/components/ChatToolBar/index.d.ts +5 -0
  12. package/dist/components/ChatToolBar/types.d.ts +16 -0
  13. package/dist/components/FieldPreview/FieldPreview.vue.d.ts +17 -0
  14. package/dist/components/FieldPreview/index.d.ts +5 -0
  15. package/dist/components/FieldPreview/types.d.ts +7 -0
  16. package/dist/components/MarkdownRender/MarkdownRender.vue.d.ts +13 -0
  17. package/dist/components/MarkdownRender/index.d.ts +5 -0
  18. package/dist/components/MarkdownRender/types.d.ts +7 -0
  19. package/dist/composables/useChatApi.d.ts +23 -0
  20. package/dist/composables/useChatMessages.d.ts +11 -0
  21. package/dist/composables/useSSE.d.ts +10 -0
  22. package/dist/composables/useTexts.d.ts +26 -0
  23. package/dist/index.d.ts +1 -1
  24. package/dist/index.js +27 -47
  25. package/dist/index.mjs +2651 -8663
  26. package/dist/style.css +1 -1
  27. package/dist/utils/helper.d.ts +1 -0
  28. package/package.json +5 -5
  29. package/src/assets/chatbot_profile.svg +1 -0
  30. package/src/assets/styles/styles.scss +10 -42
  31. package/src/components/ChatBot/ChatBot.vue +375 -0
  32. package/src/components/ChatBot/types.ts +55 -0
  33. package/src/components/ChatBotInput/ChatBotInput.vue +195 -0
  34. package/src/components/ChatBotInput/index.ts +5 -0
  35. package/src/components/ChatBotInput/types.ts +33 -0
  36. package/src/components/ChatBotMessage/ChatBotMessage.vue +139 -0
  37. package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +169 -0
  38. package/src/components/ChatBotMessage/index.ts +8 -0
  39. package/src/components/ChatBotMessage/types.ts +70 -0
  40. package/src/components/ChatToolBar/ChatToolBar.vue +82 -0
  41. package/src/components/ChatToolBar/index.ts +5 -0
  42. package/src/components/ChatToolBar/types.ts +18 -0
  43. package/src/components/FieldPreview/FieldPreview.vue +78 -0
  44. package/src/components/FieldPreview/field-preview.scss +34 -0
  45. package/src/components/FieldPreview/index.ts +5 -0
  46. package/src/components/FieldPreview/types.ts +7 -0
  47. package/src/components/MarkdownRender/MarkdownRender.vue +25 -0
  48. package/src/components/MarkdownRender/index.ts +5 -0
  49. package/src/components/MarkdownRender/markdown-render.scss +24 -0
  50. package/src/components/MarkdownRender/types.ts +7 -0
  51. package/src/components/PulseDots/PulseDots.vue +24 -0
  52. package/src/components/PulseDots/pulse-dots.scss +37 -0
  53. package/src/composables/useChatApi.ts +156 -0
  54. package/src/composables/useChatMessages.ts +58 -0
  55. package/src/composables/useSSE.ts +90 -0
  56. package/src/composables/useTexts.ts +32 -0
  57. package/src/index.ts +1 -1
  58. package/src/utils/helper.ts +12 -0
  59. package/dist/components/CBMessage.vue.d.ts +0 -95
  60. package/dist/components/ChatBot.vue.d.ts +0 -65
  61. package/dist/components/index.d.ts +0 -4
  62. package/dist/types/chatbot.type.d.ts +0 -14
  63. package/dist/types/message.type.d.ts +0 -34
  64. package/dist/types/texts.type.d.ts +0 -3
  65. package/src/assets/chatbot.png +0 -0
  66. package/src/components/CBMessage.vue +0 -276
  67. package/src/components/ChatBot.vue +0 -496
  68. package/src/components/PulseDots.vue +0 -15
  69. package/src/components/index.ts +0 -4
  70. package/src/types/chatbot.type.ts +0 -15
  71. package/src/types/message.type.ts +0 -55
  72. package/src/types/texts.type.ts +0 -3
  73. /package/dist/components/{PulseDots.vue.d.ts → PulseDots/PulseDots.vue.d.ts} +0 -0
@@ -1,496 +0,0 @@
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
- :disabled="isChatDisabled"
9
- borderless
10
- @click="clearChat">
11
- <QIcon icon="bin" />
12
- </q-button>
13
- </div>
14
-
15
- <!-- Attach ref to messages container -->
16
- <div
17
- ref="messagesContainer"
18
- class="q-chatbot__messages-container"
19
- @scroll="handleScroll">
20
- <div
21
- v-for="message in messages"
22
- :key="message.id"
23
- :class="getMessageClasses(message.sender)">
24
- <!-- Pass the streaming flag to CBMessage for animation -->
25
- <c-b-message
26
- v-bind="message"
27
- :date-format="props.dateFormat"
28
- :user-image="props.userImage"
29
- :chatbot-image="props.chatbotImage"
30
- :loading="isLoading && !message.message"
31
- :imagePreviewUrl="message.imagePreviewUrl"
32
- :apiEndpoint="props.apiEndpoint"
33
- :sessionID="message.sessionID"/>
34
- </div>
35
- </div>
36
- </div>
37
-
38
- <div class="q-chatbot__footer-container">
39
- <q-label :label="props.texts.inputLabel"/>
40
- <div
41
- class="q-chatbot__footer"
42
- @dragover.prevent="onDragOver"
43
- @dragleave.prevent="onDragLeave"
44
- @drop.prevent="onDrop"
45
- :class="chatBotFooterClasses">
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>
73
- </div>
74
- <div class="q-chatbot__send-container">
75
- <!-- Upload button moved to the same container as send, but positioned to the left -->
76
- <q-button
77
- :title="props.texts.imageUpload"
78
- class="q-chatbot__upload"
79
- :disabled="isChatDisabled || isLoading || hasSelectedImage"
80
- @click="triggerImageUpload">
81
- <q-icon icon="upload" />
82
- </q-button>
83
-
84
- <!-- Hidden file input -->
85
- <input
86
- id="image-upload"
87
- type="file"
88
- ref="imageInput"
89
- @change="handleImageUpload"
90
- :accept="acceptedImageTypes"
91
- class="hidden-input" style="display: none;" />
92
-
93
- <q-button
94
- :title="props.texts.sendMessage"
95
- variant="bold"
96
- class="q-chatbot__send"
97
- :disabled="isSendButtonDisabled"
98
- :readonly="isSendButtonDisabled"
99
- :loading="isLoading"
100
- @click="sendMessage">
101
- <q-icon icon="send" />
102
- </q-button>
103
- </div>
104
- </div>
105
- </div>
106
- </div>
107
- </template>
108
-
109
-
110
- <script setup lang="ts">
111
- import { onMounted, nextTick, ref, watch, computed } from 'vue'
112
- import type { Ref } from 'vue'
113
- import axios from 'axios'
114
- import type { AxiosResponse } from 'axios'
115
- import { CBMessage } from '@/components'
116
-
117
- import { QButton, QTextArea, QIcon, QLabel } from '@quidgest/ui/components'
118
- import type { ChatBotMessage, ChatBotMessageContent, ChatBotMessageSender } from '@/types/message.type'
119
- import { v4 as uuidv4 } from 'uuid'
120
-
121
- import ChatBotIcon from '@/assets/chatbot.png'
122
- import UserIcon from '@/assets/user_avatar.png'
123
- import { ChatBotProps } from '@/types/chatbot.type'
124
-
125
- const messages: Ref<ChatBotMessage[]> = ref([])
126
- const nextMessageId = ref(1)
127
- const userPrompt = ref('')
128
- const isLoading = ref(false)
129
- const isChatDisabled = ref(false)
130
-
131
- // Ref for the messages container
132
- const messagesContainer = ref<HTMLElement | null>(null)
133
- // Flag to control auto-scrolling
134
- const autoScrollEnabled = ref(true)
135
-
136
- const imageInput = ref<HTMLInputElement | null>(null)
137
- const imagePreviewUrl = ref<string | null>(null)
138
- const acceptedImageTypes = computed(() => '.png, .jpeg, .jpg, .svg, .webp')
139
- const hasSelectedImage = ref<boolean>(false)
140
- const isDragging = ref(false)
141
-
142
- // Computed property to check if the send button should be enabled
143
- const isSendButtonDisabled = computed(() => {
144
- return userPrompt.value.trim().length === 0 || isLoading.value || isChatDisabled.value
145
- })
146
-
147
- const props = withDefaults(defineProps<ChatBotProps>(), {
148
- apiEndpoint: 'http://localhost:3000',
149
- userImage: UserIcon,
150
- chatbotImage: ChatBotIcon,
151
- mode: 'chat',
152
- texts: () => ({
153
- chatbotTitle: 'ChatBot',
154
- sendMessage: 'Send message',
155
- clearChat: 'Clear chat',
156
- inputLabel: 'What can I help with?',
157
- imageUpload: 'Upload Image',
158
- imageUploadQButton: 'Upload Image',
159
- goodResponse: 'Good response',
160
- badResponse: 'Bad response',
161
- initialMessage:
162
- "Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
163
- initialAgentMessage: 'Just a temporary message while we are working on the agent mode',
164
- loginError:
165
- 'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
166
- botIsSick:
167
- '*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!'
168
- })
169
- })
170
-
171
- onMounted(() => {
172
- initChat()
173
- nextTick(() => {
174
- messagesContainer.value?.addEventListener('scroll', handleScroll)
175
- })
176
- })
177
-
178
- const isDisabled = computed(() => {
179
- return isChatDisabled.value || isLoading.value
180
- })
181
-
182
- const chatBotFooterClasses = computed(() => {
183
- return {
184
- 'q-chatbot__footer-disabled': isDisabled.value,
185
- 'drag-over' : isDragging.value && !hasSelectedImage.value,
186
- }
187
- })
188
-
189
- function setDisabledState(state: boolean) {
190
- isChatDisabled.value = state
191
- }
192
-
193
- async function initChat() {
194
- try {
195
- await axios.post(props.apiEndpoint + '/auth/login', {
196
- username: props.username,
197
- password: 'test'
198
- })
199
-
200
- loadChatData()
201
- } catch (error) {
202
- setDisabledState(true)
203
- addChatMessage(props.texts.loginError)
204
- console.log('Error logging in: ' + error)
205
- }
206
- };
207
-
208
- async function loadChatData() {
209
- try {
210
- const response = await axios.post(props.apiEndpoint + '/prompt/load', {
211
- username: props.username,
212
- project: props.projectPath
213
- });
214
-
215
- if(!response) return console.error('No response from server');
216
-
217
- if (response.status !== 200 || !response.data.success) {
218
- setDisabledState(true)
219
- addChatMessage(props.texts.botIsSick)
220
- console.log(`Unsuccessful load, endpoint gave status ${response.status}`)
221
- return
222
- }
223
-
224
- sendInitialMessage()
225
- response.data.history.forEach((message: ChatBotMessageContent) => {
226
- const imgUrl = message.imageUrl ? props.controllerEndpoint + message.imageUrl : undefined
227
- addChatMessage(
228
- message.content,
229
- message.type === 'ai' ? 'bot' : 'user',
230
- imgUrl,
231
- message.sessionID)
232
- })
233
-
234
- } catch(error) {
235
- setDisabledState(true)
236
- addChatMessage(props.texts.botIsSick)
237
- console.log('Error loading: ' + error)
238
- }
239
-
240
- }
241
-
242
- // Modified addChatMessage to add isStreaming flag for empty bot messages
243
- function addChatMessage(
244
- message: string,
245
- sender: 'bot' | 'user' = 'bot',
246
- imagePreviewUrl: string | null = null,
247
- sessionID?: string,
248
- isWelcomeMessage?: boolean
249
- ) {
250
- messages.value.push({
251
- id: nextMessageId.value++,
252
- message,
253
- date: new Date(),
254
- sender: sender,
255
- imagePreviewUrl: imagePreviewUrl ?? undefined,
256
- sessionID: sessionID || uuidv4(),
257
- isWelcomeMessage: isWelcomeMessage ?? false
258
- })
259
- nextTick(() => {
260
- if (autoScrollEnabled.value) scrollToBottom()
261
- })
262
- }
263
-
264
- function getLastMessage() {
265
- return messages.value.find(
266
- (m: ChatBotMessage) => m.id === nextMessageId.value - 1
267
- )
268
- }
269
-
270
- function sendInitialMessage() {
271
- const message = props.mode === 'chat'
272
- ? props.texts.initialMessage
273
- : props.texts.initialAgentMessage
274
- addChatMessage(message, 'bot', null, undefined, true)
275
- }
276
-
277
- function resetChat() {
278
- messages.value = []
279
- userPrompt.value = ''
280
- isLoading.value = false
281
- setDisabledState(false)
282
- autoScrollEnabled.value = true
283
- }
284
-
285
- function scrollToBottom() {
286
- nextTick(() => {
287
- if (messagesContainer.value) {
288
- messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
289
- }
290
- })
291
- }
292
-
293
- function handleScroll() {
294
- if (messagesContainer.value) {
295
- const threshold = 20 // px threshold from the bottom
296
- const { scrollTop, clientHeight, scrollHeight } = messagesContainer.value
297
- if (scrollTop + clientHeight >= scrollHeight - threshold) {
298
- autoScrollEnabled.value = true
299
- } else {
300
- autoScrollEnabled.value = false
301
- }
302
- }
303
- }
304
-
305
- function removeImage() {
306
- imagePreviewUrl.value = ''
307
- if (imageInput.value) {
308
- imageInput.value.value = ''
309
- }
310
- hasSelectedImage.value = false
311
- }
312
-
313
- function triggerImageUpload() {
314
- imageInput.value?.click()
315
- }
316
-
317
- function handleImageUpload(event: Event) {
318
- // Store the selected image in imageInput
319
- const target = event.target as HTMLInputElement
320
- imageInput.value = target
321
-
322
- if (target.files && target.files[0]) {
323
- imagePreviewUrl.value = URL.createObjectURL(target.files[0])
324
- hasSelectedImage.value = true
325
- }
326
- }
327
-
328
- function sendMessage() {
329
- if (
330
- userPrompt.value.trim().length === 0 ||
331
- isLoading.value ||
332
- isChatDisabled.value
333
- )
334
- return
335
-
336
-
337
- if(messagesContainer.value) {
338
- messagesContainer.value.scrollTo({
339
- top: messagesContainer.value.scrollHeight,
340
- behavior: 'smooth'
341
- })
342
- }
343
- addChatMessage(userPrompt.value, 'user', imagePreviewUrl.value)
344
-
345
- // Send prompt to bot
346
- setChatPrompt(userPrompt.value, imageInput.value?.files?.[0])
347
- removeImage()
348
- userPrompt.value = '' // Clear user input
349
- }
350
-
351
- async function setChatPrompt(prompt: string, image?: File) {
352
- // Add an empty bot message marked as streaming to trigger bouncing dots animation
353
- addChatMessage('', 'bot')
354
- let msg = getLastMessage()
355
-
356
-
357
- const currentSessionID: string = msg?.sessionID || ''
358
-
359
- const formData = new FormData()
360
- if (image) {
361
- formData.append('image', image)
362
- }
363
-
364
- formData.append('message', prompt)
365
- formData.append('project', props.projectPath)
366
- formData.append('user', props.username)
367
- formData.append('sessionID', currentSessionID)
368
-
369
-
370
- isLoading.value = true
371
-
372
- try {
373
- const response = await axios.post(props.apiEndpoint + '/prompt/submit', formData, {
374
- headers: {
375
- 'Content-Type': 'text/event-stream',
376
- 'Accept': 'text/event-stream',
377
- },
378
- responseType: 'stream',
379
- adapter: 'fetch',
380
- })
381
-
382
- if(!response) return console.error('No response from server')
383
-
384
- const reader = response.data.getReader()
385
- const decoder = new TextDecoder("utf-8")
386
-
387
- while(true) {
388
- const { done, value } = await reader.read()
389
- if(done) break
390
-
391
- const chunk = decoder.decode(value, { stream: true })
392
- const eventList = chunk.match(/data:\s*({.*?})/g)
393
- if(!eventList) continue
394
-
395
- for(const event of eventList) {
396
- try {
397
- const rawData = event.split('data:')[1].trim()
398
- const data = JSON.parse(rawData)
399
- if(msg) {
400
- msg.message += data.value
401
- }
402
- } catch (error) {
403
- console.error('Error parsing match:', error)
404
- }
405
- }
406
- if(autoScrollEnabled.value) scrollToBottom()
407
- }
408
-
409
- isLoading.value = false
410
- } catch (error) {
411
- setDisabledState(true)
412
- addChatMessage(props.texts.botIsSick)
413
- console.log('Error setting chat prompt: ' + error)
414
- return
415
- }
416
- }
417
-
418
- function clearChat() {
419
- axios.post(props.apiEndpoint + '/prompt/clear', {
420
- username: props.username,
421
- project: props.projectPath
422
- })
423
- .then((response: AxiosResponse) => {
424
- if (response.status !== 200 || !response.data.success) {
425
- setDisabledState(true)
426
- addChatMessage(props.texts.loginError)
427
- console.log(`Unsuccessful clear, endpoint gave status ${response.status}`)
428
- return
429
- }
430
- resetChat()
431
- sendInitialMessage()
432
- })
433
- .catch((error: Error) => {
434
- setDisabledState(true)
435
- addChatMessage(props.texts.loginError)
436
- console.log('Error clearing chat: ' + error)
437
- })
438
- }
439
-
440
- function getMessageClasses(sender: ChatBotMessageSender) {
441
- const classes: string[] = ['q-chatbot__messages-wrapper']
442
- if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
443
- return classes
444
- }
445
-
446
-
447
- function onDragOver(event: DragEvent) {
448
- event.preventDefault()
449
- if (!isDisabled.value) {
450
- isDragging.value = true
451
- }
452
- }
453
-
454
- function onDragLeave(event: DragEvent) {
455
- event.preventDefault()
456
- isDragging.value = false
457
- }
458
-
459
- function onDrop(event: DragEvent) {
460
- event.preventDefault()
461
- isDragging.value = false
462
-
463
- if (isDisabled.value || hasSelectedImage.value)
464
- return
465
-
466
- const files = event.dataTransfer?.files
467
- if (files && files.length > 0) {
468
- // Check if file is an image
469
- const file = files[0]
470
- if (file.type.startsWith('image/')) {
471
- // Create a file input event
472
- if (imageInput.value) {
473
- // Create a new FileList-like object
474
- const dataTransfer = new DataTransfer()
475
- dataTransfer.items.add(file)
476
- imageInput.value.files = dataTransfer.files
477
-
478
- // Set the preview URL
479
- imagePreviewUrl.value = URL.createObjectURL(file)
480
- hasSelectedImage.value = true
481
- }
482
- }
483
- }
484
-
485
- }
486
-
487
- watch(
488
- () => props.apiEndpoint,
489
- () => {
490
- resetChat()
491
- initChat()
492
- }
493
- )
494
-
495
- defineOptions({ name: 'ChatBot' })
496
- </script>
@@ -1,15 +0,0 @@
1
- <template>
2
- <div class="pulsing-dots">
3
- <span
4
- v-for="(_, index) in dots"
5
- :key="index"
6
- class="dot"
7
- :style="{ animationDelay: index * 0.2 + 's' }">
8
- &bull;
9
- </span>
10
- </div>
11
- </template>
12
-
13
- <script setup lang="ts">
14
- const dots = [1, 2, 3]
15
- </script>
@@ -1,4 +0,0 @@
1
- import CBMessage from './CBMessage.vue'
2
- import type { CBMessageProps } from '../types/message.type'
3
-
4
- export { CBMessage, CBMessageProps }
@@ -1,15 +0,0 @@
1
- import { ResourceStrings } from './texts.type'
2
-
3
- export type ChatBotProps = {
4
- apiEndpoint?: string
5
- controllerEndpoint?: string
6
- texts?: ResourceStrings
7
- username: string
8
- projectPath: string
9
- userImage?: string
10
- chatbotImage?: string
11
- dateFormat?: string
12
- mode?: ChatBotMode
13
- }
14
-
15
- export type ChatBotMode = 'chat' | 'agent'
@@ -1,55 +0,0 @@
1
- export type ChatBotMessage = {
2
- id: number
3
- message: string
4
- date: Date
5
- sender: ChatBotMessageSender
6
- sessionID: string
7
- imagePreviewUrl?: string
8
- isWelcomeMessage?: boolean
9
- }
10
-
11
- export type ChatBotMessageContent = {
12
- content: string
13
- type: string
14
- sessionID: string
15
- imageUrl?: string
16
- }
17
-
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
- }
@@ -1,3 +0,0 @@
1
- export type ResourceStrings = {
2
- [key: string]: string
3
- }