@quidgest/chatbot 0.0.8 → 0.0.9

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,335 @@
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
- })
78
-
79
- onMounted(() => {
80
- initChat()
81
- })
82
-
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' })
278
- </script>
279
-
280
1
  <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>
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
+ </div>
33
+ </div>
34
+
35
+ <div class="q-chatbot__footer-container">
36
+ <q-label :label="props.texts.inputLabel"/>
37
+ <div
38
+ class="q-chatbot__footer"
39
+ :class="chatBotFooterClasses">
40
+ <div class="q-chatbot__input">
41
+ <q-text-area
42
+ v-model="userPrompt"
43
+ size="block"
44
+ autosize
45
+ resize="none"
46
+ :rows="2"
47
+ :disabled="isDisabled"
48
+ @keyup.enter="sendMessage" />
49
+ </div>
50
+ <div class="q-chatbot__send-container">
51
+ <q-button
52
+ :title="props.texts.sendMessage"
53
+ b-style="primary"
54
+ class="q-chatbot__send"
55
+ :disabled="isDisabled"
56
+ :readonly="isDisabled"
57
+ :loading="isLoading"
58
+ @click="sendMessage">
59
+ <q-icon icon="send" />
60
+ </q-button>
61
+ </div>
62
+ </div>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </template>
67
+
336
68
 
337
- </div>
338
- </template>
69
+ <script setup lang="ts">
70
+ import { onMounted, nextTick, ref, watch, computed } from 'vue'
71
+ import type { Ref } from 'vue'
72
+ import Axios from 'axios'
73
+ import type { AxiosResponse } from 'axios'
74
+ import { CBMessage } from '@/components'
75
+
76
+ import { QButton, QTextArea, QIcon, QLabel } from '@quidgest/ui/components'
77
+ import type { ChatBotMessage, ChatBotMessageContent, ChatBotMessageSender } from '@/types/message.type'
78
+
79
+ import ChatBotIcon from '@/assets/chatbot.png'
80
+ import UserIcon from '@/assets/user_avatar.png'
81
+ import { ChatBotProps } from '@/types/chatbot.type'
82
+
83
+ const messages: Ref<ChatBotMessage[]> = ref([])
84
+ const nextMessageId = ref(1)
85
+ const userPrompt = ref('')
86
+ const isLoading = ref(false)
87
+ const isChatDisabled = ref(false)
88
+
89
+ // Ref for the messages container
90
+ const messagesContainer = ref<HTMLElement | null>(null)
91
+ // Flag to control auto-scrolling
92
+ const autoScrollEnabled = ref(true)
93
+
94
+ const props = withDefaults(defineProps<ChatBotProps>(), {
95
+ apiEndpoint: 'http://localhost:3000',
96
+ userImage: UserIcon,
97
+ chatbotImage: ChatBotIcon,
98
+ mode: 'chat',
99
+ texts: () => ({
100
+ chatbotTitle: 'ChatBot',
101
+ sendMessage: 'Send message',
102
+ clearChat: 'Clear chat',
103
+ inputLabel: 'What can I help with?',
104
+ initialMessage:
105
+ "Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
106
+ initialAgentMessage: 'Just a temporary message while we are working on the agent mode',
107
+ loginError:
108
+ 'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
109
+ botIsSick:
110
+ '*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!'
111
+ })
112
+ })
113
+
114
+ onMounted(() => {
115
+ initChat()
116
+ nextTick(() => {
117
+ messagesContainer.value?.addEventListener('scroll', handleScroll)
118
+ })
119
+ })
120
+
121
+ const isDisabled = computed(() => {
122
+ return isChatDisabled.value || isLoading.value
123
+ })
124
+
125
+ const chatBotFooterClasses = computed(() => {
126
+ return {
127
+ 'q-chatbot__footer-disabled': isDisabled.value
128
+ }
129
+ })
130
+
131
+ function setDisabledState(state: boolean) {
132
+ isChatDisabled.value = state
133
+ }
134
+
135
+ function initChat() {
136
+ Axios.post(props.apiEndpoint + '/auth/login', {
137
+ username: props.username,
138
+ password: 'test'
139
+ })
140
+ .then((response: AxiosResponse) => {
141
+ if (response.status !== 200 || !response.data.success) {
142
+ setDisabledState(true)
143
+ addChatMessage(props.texts.loginError)
144
+ console.log(`Unsuccessful login, endpoint gave status ${response.status}`)
145
+ return
146
+ }
147
+ loadChatData()
148
+ })
149
+ .catch((error: Error) => {
150
+ setDisabledState(true)
151
+ addChatMessage(props.texts.loginError)
152
+ console.log('Error during login: ' + error)
153
+ })
154
+ }
155
+
156
+ function loadChatData() {
157
+ Axios.post(props.apiEndpoint + '/prompt/load', {
158
+ username: props.username,
159
+ project: props.projectPath
160
+ })
161
+ .then((response: AxiosResponse) => {
162
+ if (response.status !== 200 || !response.data.success) {
163
+ setDisabledState(true)
164
+ addChatMessage(props.texts.loginError)
165
+ console.log(`Unsuccessful load, endpoint gave status ${response.status}`)
166
+ return
167
+ }
168
+ sendInitialMessage()
169
+ response.data.history.forEach((message: ChatBotMessageContent) => {
170
+ addChatMessage(
171
+ message.content,
172
+ message.type === 'ai' ? 'bot' : 'user'
173
+ )
174
+ })
175
+ })
176
+ .catch((error: Error) => {
177
+ setDisabledState(true)
178
+ addChatMessage(props.texts.loginError)
179
+ console.log('Error loading chat data: ' + error)
180
+ })
181
+ }
182
+
183
+ // Modified addChatMessage to add isStreaming flag for empty bot messages
184
+ function addChatMessage(message: string, sender: 'bot' | 'user' = 'bot') {
185
+ messages.value.push({
186
+ id: nextMessageId.value++,
187
+ message,
188
+ date: new Date(),
189
+ sender: sender,
190
+ })
191
+ nextTick(() => {
192
+ if (autoScrollEnabled.value) scrollToBottom()
193
+ })
194
+ }
195
+
196
+ function getLastMessage() {
197
+ return messages.value.find(
198
+ (m: ChatBotMessage) => m.id === nextMessageId.value - 1
199
+ )
200
+ }
201
+
202
+ function sendInitialMessage() {
203
+ const message = props.mode === 'chat'
204
+ ? props.texts.initialMessage
205
+ : props.texts.initialAgentMessage
206
+ addChatMessage(message)
207
+ }
208
+
209
+ function resetChat() {
210
+ messages.value = []
211
+ userPrompt.value = ''
212
+ isLoading.value = false
213
+ setDisabledState(false)
214
+ autoScrollEnabled.value = true
215
+ }
216
+
217
+ function scrollToBottom() {
218
+ nextTick(() => {
219
+ if (messagesContainer.value) {
220
+ messagesContainer.value.scrollTo({
221
+ top: messagesContainer.value.scrollHeight,
222
+ behavior: 'smooth'
223
+ })
224
+ }
225
+ })
226
+ }
227
+
228
+ function handleScroll() {
229
+ if (messagesContainer.value) {
230
+ const threshold = 20 // px threshold from the bottom
231
+ const { scrollTop, clientHeight, scrollHeight } = messagesContainer.value
232
+ if (scrollTop + clientHeight >= scrollHeight - threshold) {
233
+ autoScrollEnabled.value = true
234
+ } else {
235
+ autoScrollEnabled.value = false
236
+ }
237
+ }
238
+ }
239
+
240
+ function sendMessage() {
241
+ if (
242
+ userPrompt.value.trim().length === 0 ||
243
+ isLoading.value ||
244
+ isChatDisabled.value
245
+ )
246
+ return
247
+
248
+ // Add user's message and force scroll to bottom
249
+ addChatMessage(userPrompt.value, 'user')
250
+ scrollToBottom()
251
+
252
+ // Send prompt to bot
253
+ setChatPrompt(userPrompt.value)
254
+ userPrompt.value = '' // Clear user input
255
+ }
256
+
257
+ function setChatPrompt(prompt: string) {
258
+ // Add an empty bot message marked as streaming to trigger bouncing dots animation
259
+ addChatMessage('', 'bot')
260
+ let msg = getLastMessage()
261
+
262
+ const params = {
263
+ message: prompt,
264
+ project: props.projectPath,
265
+ user: props.username
266
+ }
267
+
268
+ isLoading.value = true
269
+ Axios({
270
+ url: props.apiEndpoint + '/prompt/message',
271
+ method: 'POST',
272
+ data: params,
273
+ onDownloadProgress: (progressEvent) => {
274
+ const chunk = progressEvent.event?.currentTarget.response
275
+ const status = progressEvent.event?.currentTarget.status
276
+
277
+ if (status !== 200) return
278
+
279
+ if (msg) {
280
+ msg.message = chunk
281
+ }
282
+ if (autoScrollEnabled.value) scrollToBottom()
283
+ }
284
+ })
285
+ .then(({ data }) => {
286
+ if (msg) msg.message = data
287
+ })
288
+ .catch((error) => {
289
+ addChatMessage(props.texts.botIsSick)
290
+ setDisabledState(true)
291
+ console.log(error)
292
+ })
293
+ .finally(() => {
294
+ isLoading.value = false
295
+ })
296
+ }
297
+
298
+ function clearChat() {
299
+ Axios.post(props.apiEndpoint + '/prompt/clear', {
300
+ username: props.username,
301
+ project: props.projectPath
302
+ })
303
+ .then((response: AxiosResponse) => {
304
+ if (response.status !== 200 || !response.data.success) {
305
+ setDisabledState(true)
306
+ addChatMessage(props.texts.loginError)
307
+ console.log(`Unsuccessful clear, endpoint gave status ${response.status}`)
308
+ return
309
+ }
310
+ resetChat()
311
+ sendInitialMessage()
312
+ })
313
+ .catch((error: Error) => {
314
+ setDisabledState(true)
315
+ addChatMessage(props.texts.loginError)
316
+ console.log('Error clearing chat: ' + error)
317
+ })
318
+ }
319
+
320
+ function getMessageClasses(sender: ChatBotMessageSender) {
321
+ const classes: string[] = ['q-chatbot__messages-wrapper']
322
+ if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
323
+ return classes
324
+ }
325
+
326
+ watch(
327
+ () => props.apiEndpoint,
328
+ () => {
329
+ resetChat()
330
+ initChat()
331
+ }
332
+ )
333
+
334
+ defineOptions({ name: 'ChatBot' })
335
+ </script>
@@ -0,0 +1,16 @@
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>
16
+
@@ -0,0 +1,14 @@
1
+ import { ResourceStrings } from "./texts.type"
2
+
3
+ export type ChatBotProps = {
4
+ apiEndpoint?: string
5
+ texts?: ResourceStrings
6
+ username: string
7
+ projectPath: string
8
+ userImage?: string
9
+ chatbotImage?: string
10
+ dateFormat?: string
11
+ mode?: ChatBotMode
12
+ }
13
+
14
+ export type ChatBotMode = 'chat' | 'agent'