@quidgest/chatbot 0.0.8 → 0.1.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/CBMessage.vue.d.ts +59 -2
- package/dist/components/ChatBot.vue.d.ts +25 -26
- package/dist/components/PulseDots.vue.d.ts +2 -0
- package/dist/index.js +68 -9
- package/dist/index.mjs +2532 -1304
- package/dist/style.css +1 -1
- package/dist/types/chatbot.type.d.ts +14 -0
- package/dist/types/message.type.d.ts +7 -1
- package/package.json +13 -12
- package/src/assets/copy.svg +7 -0
- package/src/assets/styles/styles.scss +107 -23
- package/src/assets/thumbDown.svg +9 -0
- package/src/assets/thumbUp.svg +9 -0
- package/src/assets/user_avatar.png +0 -0
- package/src/components/CBMessage.vue +235 -40
- package/src/components/ChatBot.vue +439 -334
- package/src/components/PulseDots.vue +15 -0
- package/src/types/chatbot.type.ts +15 -0
- package/src/types/message.type.ts +8 -1
|
@@ -1,338 +1,443 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
+
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
|
+
:imagePreviewUrl="message.imagePreviewUrl"
|
|
33
|
+
:apiEndpoint="props.apiEndpoint"
|
|
34
|
+
:sessionID="message.sessionID"/>
|
|
35
|
+
</div>
|
|
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
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="q-chatbot__footer-container">
|
|
56
|
+
<q-label :label="props.texts.inputLabel"/>
|
|
57
|
+
<div
|
|
58
|
+
class="q-chatbot__footer"
|
|
59
|
+
:class="chatBotFooterClasses">
|
|
60
|
+
<div class="q-chatbot__input">
|
|
61
|
+
<q-text-area
|
|
62
|
+
v-model="userPrompt"
|
|
63
|
+
size="block"
|
|
64
|
+
autosize
|
|
65
|
+
resize="none"
|
|
66
|
+
:rows="2"
|
|
67
|
+
:disabled="isDisabled"
|
|
68
|
+
@keyup.enter="sendMessage" />
|
|
69
|
+
</div>
|
|
70
|
+
<div class="q-chatbot__send-container">
|
|
71
|
+
<!-- Upload button moved to the same container as send, but positioned to the left -->
|
|
72
|
+
<q-button
|
|
73
|
+
:title="props.texts.imageUpload"
|
|
74
|
+
b-style="primary"
|
|
75
|
+
class="q-chatbot__upload"
|
|
76
|
+
:disabled="isChatDisabled || isLoading || hasSelectedImage"
|
|
77
|
+
@click="triggerImageUpload">
|
|
78
|
+
<q-icon icon="upload" />
|
|
79
|
+
</q-button>
|
|
80
|
+
|
|
81
|
+
<!-- Hidden file input -->
|
|
82
|
+
<input
|
|
83
|
+
id="image-upload"
|
|
84
|
+
type="file"
|
|
85
|
+
ref="imageInput"
|
|
86
|
+
@change="handleImageUpload"
|
|
87
|
+
:accept="acceptedImageTypes"
|
|
88
|
+
class="hidden-input" style="display: none;" />
|
|
89
|
+
|
|
90
|
+
<div class="spacer"></div>
|
|
91
|
+
|
|
92
|
+
<q-button
|
|
93
|
+
:title="props.texts.sendMessage"
|
|
94
|
+
b-style="primary"
|
|
95
|
+
class="q-chatbot__send"
|
|
96
|
+
:disabled="isSendButtonDisabled"
|
|
97
|
+
:readonly="isDisabled"
|
|
98
|
+
:loading="isLoading"
|
|
99
|
+
@click="sendMessage">
|
|
100
|
+
<q-icon icon="send" />
|
|
101
|
+
</q-button>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</template>
|
|
107
|
+
|
|
78
108
|
|
|
79
|
-
|
|
80
|
-
|
|
109
|
+
<script setup lang="ts">
|
|
110
|
+
import { onMounted, nextTick, ref, watch, computed } from 'vue'
|
|
111
|
+
import type { Ref } from 'vue'
|
|
112
|
+
import Axios from 'axios'
|
|
113
|
+
import type { AxiosResponse } from 'axios'
|
|
114
|
+
import { CBMessage } from '@/components'
|
|
115
|
+
|
|
116
|
+
import { QButton, QTextArea, QIcon, QLabel } from '@quidgest/ui/components'
|
|
117
|
+
import type { ChatBotMessage, ChatBotMessageContent, ChatBotMessageSender } from '@/types/message.type'
|
|
118
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
119
|
+
|
|
120
|
+
import ChatBotIcon from '@/assets/chatbot.png'
|
|
121
|
+
import UserIcon from '@/assets/user_avatar.png'
|
|
122
|
+
import { ChatBotProps } from '@/types/chatbot.type'
|
|
123
|
+
|
|
124
|
+
const messages: Ref<ChatBotMessage[]> = ref([])
|
|
125
|
+
const nextMessageId = ref(1)
|
|
126
|
+
const userPrompt = ref('')
|
|
127
|
+
const isLoading = ref(false)
|
|
128
|
+
const isChatDisabled = ref(false)
|
|
129
|
+
|
|
130
|
+
// Ref for the messages container
|
|
131
|
+
const messagesContainer = ref<HTMLElement | null>(null)
|
|
132
|
+
// Flag to control auto-scrolling
|
|
133
|
+
const autoScrollEnabled = ref(true)
|
|
134
|
+
|
|
135
|
+
const imageInput = ref<HTMLInputElement | null>(null)
|
|
136
|
+
const imagePreviewUrl = ref<string | null>(null)
|
|
137
|
+
const acceptedImageTypes = computed(() => '.png, .jpeg, .jpg, .svg, .webp')
|
|
138
|
+
const hasSelectedImage = ref<boolean>(false)
|
|
139
|
+
|
|
140
|
+
// Computed property to check if the send button should be enabled
|
|
141
|
+
const isSendButtonDisabled = computed(() => {
|
|
142
|
+
return userPrompt.value.trim().length === 0 || isLoading.value || isChatDisabled.value
|
|
81
143
|
})
|
|
82
144
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
145
|
+
const props = withDefaults(defineProps<ChatBotProps>(), {
|
|
146
|
+
apiEndpoint: 'http://localhost:3000',
|
|
147
|
+
userImage: UserIcon,
|
|
148
|
+
chatbotImage: ChatBotIcon,
|
|
149
|
+
mode: 'chat',
|
|
150
|
+
texts: () => ({
|
|
151
|
+
chatbotTitle: 'ChatBot',
|
|
152
|
+
sendMessage: 'Send message',
|
|
153
|
+
clearChat: 'Clear chat',
|
|
154
|
+
inputLabel: 'What can I help with?',
|
|
155
|
+
imageUpload: 'Upload Image',
|
|
156
|
+
imageUploadQButton: 'Upload Image',
|
|
157
|
+
goodResponse: 'Good response',
|
|
158
|
+
badResponse: 'Bad response',
|
|
159
|
+
initialMessage:
|
|
160
|
+
"Howdy! I am GenioBot 👋, Quidgest's personal AI assistant! How can I help you?",
|
|
161
|
+
initialAgentMessage: 'Just a temporary message while we are working on the agent mode',
|
|
162
|
+
loginError:
|
|
163
|
+
'Uh oh, I could not authenticate with the Quidgest API endpoint 😓',
|
|
164
|
+
botIsSick:
|
|
165
|
+
'*cough cough* GenioBot is not feeling alright 🥴️🤒, looks like something failed!'
|
|
166
|
+
})
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
onMounted(() => {
|
|
170
|
+
initChat()
|
|
171
|
+
nextTick(() => {
|
|
172
|
+
messagesContainer.value?.addEventListener('scroll', handleScroll)
|
|
173
|
+
})
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
const isDisabled = computed(() => {
|
|
177
|
+
return isChatDisabled.value || isLoading.value
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
const chatBotFooterClasses = computed(() => {
|
|
181
|
+
return {
|
|
182
|
+
'q-chatbot__footer-disabled': isDisabled.value
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
function setDisabledState(state: boolean) {
|
|
187
|
+
isChatDisabled.value = state
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function initChat() {
|
|
191
|
+
Axios.post(props.apiEndpoint + '/auth/login', {
|
|
192
|
+
username: props.username,
|
|
193
|
+
password: 'test'
|
|
194
|
+
})
|
|
195
|
+
.then((response: AxiosResponse) => {
|
|
196
|
+
if (response.status !== 200 || !response.data.success) {
|
|
197
|
+
setDisabledState(true)
|
|
198
|
+
addChatMessage(props.texts.loginError)
|
|
199
|
+
console.log(`Unsuccessful login, endpoint gave status ${response.status}`)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
loadChatData()
|
|
203
|
+
})
|
|
204
|
+
.catch((error: Error) => {
|
|
205
|
+
setDisabledState(true)
|
|
206
|
+
addChatMessage(props.texts.loginError)
|
|
207
|
+
console.log('Error during login: ' + error)
|
|
208
|
+
})
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function loadChatData() {
|
|
212
|
+
Axios.post(props.apiEndpoint + '/prompt/load', {
|
|
213
|
+
username: props.username,
|
|
214
|
+
project: props.projectPath
|
|
215
|
+
})
|
|
216
|
+
.then((response: AxiosResponse) => {
|
|
217
|
+
if (response.status !== 200 || !response.data.success) {
|
|
218
|
+
setDisabledState(true)
|
|
219
|
+
addChatMessage(props.texts.loginError)
|
|
220
|
+
console.log(`Unsuccessful load, endpoint gave status ${response.status}`)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
sendInitialMessage()
|
|
224
|
+
response.data.history.forEach((message: ChatBotMessageContent) => {
|
|
225
|
+
const imgUrl = message.imageUrl ? props.controllerEndpoint + message.imageUrl : undefined
|
|
226
|
+
addChatMessage(
|
|
227
|
+
message.content,
|
|
228
|
+
message.type === 'ai' ? 'bot' : 'user',
|
|
229
|
+
imgUrl,
|
|
230
|
+
message.sessionID
|
|
231
|
+
)
|
|
232
|
+
})
|
|
233
|
+
})
|
|
234
|
+
.catch((error: Error) => {
|
|
235
|
+
setDisabledState(true)
|
|
236
|
+
addChatMessage(props.texts.loginError)
|
|
237
|
+
console.log('Error loading chat data: ' + error)
|
|
238
|
+
})
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Modified addChatMessage to add isStreaming flag for empty bot messages
|
|
242
|
+
function addChatMessage(
|
|
243
|
+
message: string,
|
|
244
|
+
sender: 'bot' | 'user' = 'bot',
|
|
245
|
+
imagePreviewUrl: string | null = null,
|
|
246
|
+
sessionID?: string,
|
|
247
|
+
isWelcomeMessage?: boolean
|
|
248
|
+
) {
|
|
249
|
+
messages.value.push({
|
|
250
|
+
id: nextMessageId.value++,
|
|
251
|
+
message,
|
|
252
|
+
date: new Date(),
|
|
253
|
+
sender: sender,
|
|
254
|
+
imagePreviewUrl: imagePreviewUrl ?? undefined,
|
|
255
|
+
sessionID: sessionID || uuidv4(),
|
|
256
|
+
isWelcomeMessage: isWelcomeMessage ?? false
|
|
257
|
+
})
|
|
258
|
+
nextTick(() => {
|
|
259
|
+
if (autoScrollEnabled.value) scrollToBottom()
|
|
260
|
+
})
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function getLastMessage() {
|
|
264
|
+
return messages.value.find(
|
|
265
|
+
(m: ChatBotMessage) => m.id === nextMessageId.value - 1
|
|
266
|
+
)
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function sendInitialMessage() {
|
|
270
|
+
const message = props.mode === 'chat'
|
|
271
|
+
? props.texts.initialMessage
|
|
272
|
+
: props.texts.initialAgentMessage
|
|
273
|
+
addChatMessage(message, 'bot', null, undefined, true)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function resetChat() {
|
|
277
|
+
messages.value = []
|
|
278
|
+
userPrompt.value = ''
|
|
279
|
+
isLoading.value = false
|
|
280
|
+
setDisabledState(false)
|
|
281
|
+
autoScrollEnabled.value = true
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function scrollToBottom() {
|
|
285
|
+
nextTick(() => {
|
|
286
|
+
if (messagesContainer.value) {
|
|
287
|
+
messagesContainer.value.scrollTo({
|
|
288
|
+
top: messagesContainer.value.scrollHeight,
|
|
289
|
+
behavior: 'smooth'
|
|
290
|
+
})
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function handleScroll() {
|
|
296
|
+
if (messagesContainer.value) {
|
|
297
|
+
const threshold = 20 // px threshold from the bottom
|
|
298
|
+
const { scrollTop, clientHeight, scrollHeight } = messagesContainer.value
|
|
299
|
+
if (scrollTop + clientHeight >= scrollHeight - threshold) {
|
|
300
|
+
autoScrollEnabled.value = true
|
|
301
|
+
} else {
|
|
302
|
+
autoScrollEnabled.value = false
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function removeImage() {
|
|
308
|
+
imagePreviewUrl.value = ''
|
|
309
|
+
if (imageInput.value) {
|
|
310
|
+
imageInput.value.value = ''
|
|
311
|
+
}
|
|
312
|
+
hasSelectedImage.value = false
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function triggerImageUpload() {
|
|
316
|
+
imageInput.value?.click()
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function handleImageUpload(event: Event) {
|
|
320
|
+
// Store the selected image in imageInput
|
|
321
|
+
const target = event.target as HTMLInputElement
|
|
322
|
+
imageInput.value = target
|
|
323
|
+
|
|
324
|
+
if (target.files && target.files[0]) {
|
|
325
|
+
imagePreviewUrl.value = URL.createObjectURL(target.files[0])
|
|
326
|
+
hasSelectedImage.value = true
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function sendMessage() {
|
|
331
|
+
if (
|
|
332
|
+
userPrompt.value.trim().length === 0 ||
|
|
333
|
+
isLoading.value ||
|
|
334
|
+
isChatDisabled.value
|
|
335
|
+
)
|
|
336
|
+
return
|
|
337
|
+
|
|
338
|
+
// Add user's message and force scroll to bottom
|
|
339
|
+
addChatMessage(userPrompt.value, 'user', imagePreviewUrl.value)
|
|
340
|
+
scrollToBottom()
|
|
341
|
+
|
|
342
|
+
// Send prompt to bot
|
|
343
|
+
setChatPrompt(userPrompt.value, imageInput.value?.files?.[0])
|
|
344
|
+
removeImage()
|
|
345
|
+
userPrompt.value = '' // Clear user input
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function setChatPrompt(prompt: string, image?: File) {
|
|
349
|
+
// Add an empty bot message marked as streaming to trigger bouncing dots animation
|
|
350
|
+
addChatMessage('', 'bot')
|
|
351
|
+
let msg = getLastMessage()
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
const currentSessionID: string = msg?.sessionID || ''
|
|
355
|
+
|
|
356
|
+
let params = null
|
|
357
|
+
|
|
358
|
+
if (image) {
|
|
359
|
+
params = new FormData()
|
|
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
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
isLoading.value = true
|
|
377
|
+
Axios({
|
|
378
|
+
url: props.apiEndpoint + '/prompt/message',
|
|
379
|
+
method: 'POST',
|
|
380
|
+
data: params,
|
|
381
|
+
onDownloadProgress: (progressEvent) => {
|
|
382
|
+
const chunk = progressEvent.event?.currentTarget.response
|
|
383
|
+
const status = progressEvent.event?.currentTarget.status
|
|
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
|
|
395
|
+
})
|
|
396
|
+
.catch((error) => {
|
|
397
|
+
addChatMessage(props.texts.botIsSick)
|
|
398
|
+
setDisabledState(true)
|
|
399
|
+
console.log(error)
|
|
400
|
+
})
|
|
401
|
+
.finally(() => {
|
|
402
|
+
isLoading.value = false
|
|
403
|
+
})
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function clearChat() {
|
|
407
|
+
Axios.post(props.apiEndpoint + '/prompt/clear', {
|
|
408
|
+
username: props.username,
|
|
409
|
+
project: props.projectPath
|
|
410
|
+
})
|
|
411
|
+
.then((response: AxiosResponse) => {
|
|
412
|
+
if (response.status !== 200 || !response.data.success) {
|
|
413
|
+
setDisabledState(true)
|
|
414
|
+
addChatMessage(props.texts.loginError)
|
|
415
|
+
console.log(`Unsuccessful clear, endpoint gave status ${response.status}`)
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
resetChat()
|
|
419
|
+
sendInitialMessage()
|
|
420
|
+
})
|
|
421
|
+
.catch((error: Error) => {
|
|
422
|
+
setDisabledState(true)
|
|
423
|
+
addChatMessage(props.texts.loginError)
|
|
424
|
+
console.log('Error clearing chat: ' + error)
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function getMessageClasses(sender: ChatBotMessageSender) {
|
|
429
|
+
const classes: string[] = ['q-chatbot__messages-wrapper']
|
|
430
|
+
if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
|
|
431
|
+
return classes
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
watch(
|
|
435
|
+
() => props.apiEndpoint,
|
|
436
|
+
() => {
|
|
437
|
+
resetChat()
|
|
438
|
+
initChat()
|
|
439
|
+
}
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
defineOptions({ name: 'ChatBot' })
|
|
278
443
|
</script>
|
|
279
|
-
|
|
280
|
-
<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>
|
|
336
|
-
|
|
337
|
-
</div>
|
|
338
|
-
</template>
|