@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.
- package/dist/components/CBMessage.vue.d.ts +15 -2
- package/dist/components/ChatBot.vue.d.ts +17 -26
- package/dist/components/PulseDots.vue.d.ts +2 -0
- package/dist/index.js +6 -9
- package/dist/index.mjs +1307 -1258
- package/dist/style.css +1 -1
- package/dist/types/chatbot.type.d.ts +13 -0
- package/dist/types/message.type.d.ts +2 -1
- package/package.json +2 -2
- package/src/assets/styles/styles.scss +43 -13
- package/src/assets/user_avatar.png +0 -0
- package/src/components/CBMessage.vue +51 -42
- package/src/components/ChatBot.vue +333 -336
- package/src/components/PulseDots.vue +16 -0
- package/src/types/chatbot.type.ts +14 -0
- package/src/types/message.type.ts +3 -1
|
@@ -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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
338
|
-
|
|
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
|
+
•
|
|
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'
|