@quidgest/chatbot 0.5.1 → 0.5.3

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 (70) hide show
  1. package/README.md +1 -2
  2. package/dist/components/ChatBot/types.d.ts +3 -1
  3. package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +3 -3
  4. package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
  5. package/dist/components/ChatBotInput/index.d.ts +2 -2
  6. package/dist/components/ChatBotInput/types.d.ts +4 -4
  7. package/dist/components/ChatBotMessage/__tests__/ChatBotMessage.spec.d.ts +1 -0
  8. package/dist/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
  9. package/dist/components/ChatBotMessage/types.d.ts +3 -2
  10. package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -0
  11. package/dist/components/FieldPreview/__tests__/FieldPreview.spec.d.ts +1 -0
  12. package/dist/components/MarkdownRender/__tests__/MarkdownRender.spec.d.ts +1 -0
  13. package/dist/components/PulseDots/__tests__/PulseDots.spec.d.ts +1 -0
  14. package/dist/composables/__tests__/useChatMessages.spec.d.ts +1 -0
  15. package/dist/composables/__tests__/useSSE.spec.d.ts +1 -0
  16. package/dist/composables/useChatMessages.d.ts +2 -1
  17. package/dist/composables/useSSE.d.ts +1 -2
  18. package/dist/composables/useTexts.d.ts +2 -0
  19. package/dist/index.js +16 -16
  20. package/dist/index.mjs +2924 -1770
  21. package/dist/style.css +1 -1
  22. package/dist/test/setup.d.ts +1 -0
  23. package/dist/utils/__tests__/parseFieldValue.spec.d.ts +1 -0
  24. package/package.json +27 -5
  25. package/src/assets/styles/preview-file.scss +70 -0
  26. package/src/assets/styles/styles.scss +190 -222
  27. package/src/components/ChatBot/ChatBot.vue +345 -368
  28. package/src/components/ChatBot/types.ts +35 -33
  29. package/src/components/ChatBotInput/ChatBotInput.vue +188 -190
  30. package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +279 -0
  31. package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
  32. package/src/components/ChatBotInput/index.ts +2 -2
  33. package/src/components/ChatBotInput/types.ts +25 -25
  34. package/src/components/ChatBotMessage/ChatBotMessage.vue +159 -134
  35. package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
  36. package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +256 -0
  37. package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
  38. package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +35 -0
  39. package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
  40. package/src/components/ChatBotMessage/types.ts +54 -53
  41. package/src/components/ChatToolBar/ChatToolBar.vue +68 -64
  42. package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +118 -0
  43. package/src/components/ChatToolBar/__tests__/__snapshots__/ChatToolBar.spec.ts.snap +11 -0
  44. package/src/components/ChatToolBar/types.ts +12 -12
  45. package/src/components/FieldPreview/FieldPreview.vue +56 -58
  46. package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
  47. package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +25 -0
  48. package/src/components/FieldPreview/field-preview.scss +26 -26
  49. package/src/components/FieldPreview/types.ts +5 -5
  50. package/src/components/MarkdownRender/MarkdownRender.vue +15 -15
  51. package/src/components/MarkdownRender/__tests__/MarkdownRender.spec.ts +68 -0
  52. package/src/components/MarkdownRender/__tests__/__snapshots__/MarkdownRender.spec.ts.snap +8 -0
  53. package/src/components/MarkdownRender/markdown-render.scss +19 -20
  54. package/src/components/MarkdownRender/types.ts +3 -3
  55. package/src/components/PulseDots/PulseDots.vue +17 -17
  56. package/src/components/PulseDots/__tests__/PulseDots.spec.ts +35 -0
  57. package/src/components/PulseDots/__tests__/__snapshots__/PulseDots.spec.ts.snap +7 -0
  58. package/src/components/PulseDots/__tests__/__snapshots__/pulse-dots.spec.ts.snap +7 -0
  59. package/src/components/PulseDots/pulse-dots.scss +24 -23
  60. package/src/composables/__tests__/useChatMessages.spec.ts +51 -0
  61. package/src/composables/__tests__/useSSE.spec.ts +132 -0
  62. package/src/composables/useChatApi.ts +128 -134
  63. package/src/composables/useChatMessages.ts +46 -48
  64. package/src/composables/useSSE.ts +75 -76
  65. package/src/composables/useTexts.ts +30 -30
  66. package/src/test/setup.ts +36 -0
  67. package/src/utils/__tests__/parseFieldValue.spec.ts +27 -0
  68. package/src/utils/parseFieldValue.ts +12 -0
  69. package/src/utils/helper.ts +0 -12
  70. /package/dist/utils/{helper.d.ts → parseFieldValue.d.ts} +0 -0
@@ -1,373 +1,350 @@
1
1
  <template>
2
- <div class="q-chatbot">
3
- <div class="q-chatbot__content">
4
- <chat-tool-bar
5
- :disabled="isDisabled"
6
- :available-agents="props.availableAgents"
7
- :selected-agent-key="currentAgentId"
8
- @clear="clearChat"
9
- @change-chat="changeChat" />
10
-
11
- <div
12
- ref="messagesContainer"
13
- class="q-chatbot__messages-container"
14
- @scroll="handleScroll">
15
- <div
16
- v-for="message in messages"
17
- :key="message.id"
18
- :class="getMessageClasses(message.sender)">
19
- <chat-bot-message
20
- v-bind="message"
21
- :date-format="props.dateFormat"
22
- :user-image="props.userImage"
23
- :chatbot-image="props.chatbotImage"
24
- :loading="isLoading && !message.message"
25
- :imagePreviewUrl="message.imagePreviewUrl"
26
- :apiEndpoint="props.apiEndpoint"
27
- :sessionID="message.sessionID"
28
- :fields="message.fields"
29
- @apply-fields="applyFields" />
30
- </div>
31
- </div>
32
- </div>
33
-
34
- <div class="q-chatbot__footer-container">
35
- <chat-bot-input
36
- :disabled="isDisabled"
37
- :loading="isLoading"
38
- @send-message="sendMessage" />
39
- </div>
40
- </div>
2
+ <div class="q-chatbot">
3
+ <div class="q-chatbot__content">
4
+ <chat-tool-bar
5
+ :disabled="isDisabled"
6
+ :available-agents="props.availableAgents"
7
+ :selected-agent-key="currentAgentId"
8
+ @clear="clearChat"
9
+ @change-chat="changeChat" />
10
+
11
+ <div
12
+ ref="messagesContainer"
13
+ class="q-chatbot__messages-container"
14
+ @scroll="handleScroll">
15
+ <div
16
+ v-for="message in messages"
17
+ :key="message.id"
18
+ :class="getMessageClasses(message.sender)">
19
+ <chat-bot-message
20
+ v-bind="message"
21
+ :date-format="props.dateFormat"
22
+ :user-image="props.userImage"
23
+ :chatbot-image="props.chatbotImage"
24
+ :loading="isLoading && !message.message"
25
+ :file="message.file"
26
+ :api-endpoint="props.apiEndpoint"
27
+ :session-i-d="message.sessionID"
28
+ :fields="message.fields"
29
+ @apply-fields="applyFields" />
30
+ </div>
31
+ </div>
32
+ </div>
33
+
34
+ <div class="q-chatbot__footer-container">
35
+ <chat-bot-input
36
+ :disabled="isDisabled"
37
+ :loading="isLoading"
38
+ @send-message="sendMessage" />
39
+ </div>
40
+ </div>
41
41
  </template>
42
42
 
43
43
  <script setup lang="ts">
44
- // Components
45
- import { ChatBotMessage } from '@/components/ChatBotMessage'
46
- import { ChatToolBar } from '@/components/ChatToolBar'
47
- import { ChatBotInput } from '@/components/ChatBotInput'
48
-
49
- // Utils
50
- import {
51
- onMounted,
52
- nextTick,
53
- ref,
54
- watch,
55
- computed,
56
- onBeforeMount
57
- } from 'vue'
58
- import { v4 as uuidv4 } from 'uuid'
59
-
60
- // Types
61
- import type {
62
- ChatBotProps,
63
- ChatBotMessageContent,
64
- ChatBotMessageSender,
65
- FieldData,
66
- AppliedFieldData
67
- } from './types'
68
- import type { ChatBotImage } from '../ChatBotInput'
69
-
70
- // Assets
71
- import ChatBotIcon from '@/assets/chatbot_profile.svg'
72
- import UserIcon from '@/assets/user_avatar.png'
73
-
74
- // Composables
75
- import { useTexts } from '@/composables/useTexts'
76
- import { useChatApi } from '@/composables/useChatApi'
77
- import { useChatMessages } from '@/composables/useChatMessages'
78
-
79
- const emits = defineEmits<{
80
- (e: 'apply-fields', fields: AppliedFieldData[]): void
81
- }>()
82
-
83
- const props = withDefaults(defineProps<ChatBotProps>(), {
84
- apiEndpoint: 'http://localhost:3000',
85
- userImage: UserIcon,
86
- chatbotImage: ChatBotIcon,
87
- userPrompt: '',
88
- availableAgents: () => [],
89
- agentData: () => {
90
- return {
91
- id: '',
92
- formId: '',
93
- jobId: ''
94
- }
95
- }
96
- })
97
-
98
- const isChatDisabled = ref(true)
99
- const messagesContainer = ref<HTMLElement | null>(null)
100
- const autoScrollEnabled = ref(true)
101
-
102
- const currentAgentId = ref(props.agentData.id)
103
- const currentFormId = ref(props.agentData.formId)
104
-
105
- // Composables
106
- const texts = useTexts()
107
- const {
108
- isLoading,
109
- clearChatData,
110
- getChatData,
111
- getFieldSuggestionData,
112
- sendPrompt
113
- } = useChatApi(props.apiEndpoint)
114
- const { messages, addChatMessage, clearMessages, getLastMessage } =
115
- useChatMessages()
116
-
117
- const isDisabled = computed(() => {
118
- return isChatDisabled.value || isLoading.value
119
- })
120
-
121
- onBeforeMount(() => {
122
- messagesContainer.value?.removeEventListener('scroll', handleScroll)
123
- })
124
-
125
- onMounted(() => {
126
- initChat()
127
- })
128
-
129
- function setDisabledState(state: boolean) {
130
- isChatDisabled.value = state
131
- }
132
-
133
- async function initChat() {
134
- try {
135
- await loadChatData()
136
- } catch (error) {
137
- addChatMessage(texts.loginError)
138
- console.error('Error logging in: ' + error)
139
- }
140
- }
141
-
142
- async function loadChatData() {
143
- setDisabledState(true)
144
- const { data, error } = await getChatData(
145
- props.username,
146
- props.projectPath,
147
- currentAgentId.value,
148
- currentFormId.value
149
- )
150
-
151
- if (error || !data || !data.success) {
152
- setDisabledState(true)
153
- addChatMessage(texts.botIsSick)
154
- console.log('Error loading chat data: ' + error)
155
- return
156
- }
157
-
158
- setDisabledState(false)
159
- sendInitialMessage()
160
-
161
- data.history.forEach(async (message: ChatBotMessageContent) => {
162
- const imgUrl = message.imageUrl
163
- ? props.controllerEndpoint + message.imageUrl
164
- : undefined
165
-
166
- addChatMessage(
167
- message.content,
168
- message.type === 'ai' ? 'bot' : 'user',
169
- imgUrl,
170
- message.sessionID
171
- )
172
- })
173
- }
174
-
175
- function sendInitialMessage() {
176
- const message = texts.initialMessage
177
- addChatMessage(message, 'bot', null, undefined, true)
178
- }
179
-
180
- function resetChat() {
181
- // Only reset UI state here, message state is handled by composable
182
- clearMessages()
183
- setDisabledState(false)
184
- autoScrollEnabled.value = true
185
- }
186
-
187
- function scrollToBottom() {
188
- nextTick(() => {
189
- if (messagesContainer.value) {
190
- messagesContainer.value.scrollTop =
191
- messagesContainer.value.scrollHeight
192
- }
193
- })
194
- }
195
-
196
- function handleScroll() {
197
- if (messagesContainer.value) {
198
- const threshold = 20 // px threshold from the bottom
199
- const { scrollTop, clientHeight, scrollHeight } =
200
- messagesContainer.value
201
- autoScrollEnabled.value =
202
- scrollTop + clientHeight >= scrollHeight - threshold
203
- }
204
- }
205
-
206
- async function sendMessage(prompt: string, image?: ChatBotImage) {
207
- if (messagesContainer.value) {
208
- messagesContainer.value.scrollTo({
209
- top: messagesContainer.value.scrollHeight,
210
- behavior: 'smooth'
211
- })
212
- }
213
-
214
- addChatMessage(prompt, 'user', image?.previewUrl)
215
- nextTick(() => {
216
- if (autoScrollEnabled.value) scrollToBottom()
217
- })
218
- setChatPrompt(prompt, image?.file)
219
- }
220
-
221
- async function getAgentJob(jobId: string) {
222
- if (messagesContainer.value) {
223
- messagesContainer.value.scrollTo({
224
- top: messagesContainer.value.scrollHeight,
225
- behavior: 'smooth'
226
- })
227
- }
228
-
229
- // Add an empty bot message to trigger bouncing dots animation
230
- addChatMessage('', 'bot')
231
- nextTick(() => {
232
- if (autoScrollEnabled.value) scrollToBottom()
233
- })
234
-
235
- const msg = getLastMessage()
236
- if (!msg) return
237
-
238
- await getFieldSuggestionData(
239
- jobId,
240
- (chunk: string) => {
241
- msg.message += chunk
242
- if (!msg.fields) msg.fields = []
243
-
244
- const lastField = msg.fields[msg.fields.length - 1]
245
- lastField.text = msg.message
246
-
247
- if (autoScrollEnabled.value) scrollToBottom()
248
- },
249
- (metadata: Record<string, unknown>) => {
250
- msg.message = ''
251
- const data = metadata as FieldData
252
- msg.fields?.push({
253
- type: data.type,
254
- name: data.name,
255
- text: data.text
256
- })
257
- }
258
- )
259
- }
260
-
261
- async function setChatPrompt(prompt: string, image?: File) {
262
- // Add an empty bot message marked as streaming to trigger bouncing dots animation
263
- addChatMessage('', 'bot')
264
- const msg = getLastMessage()
265
- if (!msg) return
266
-
267
- const currentSessionID = msg?.sessionID || uuidv4()
268
-
269
- const formData = new FormData()
270
- if (image) {
271
- formData.append('image', image)
272
- }
273
-
274
- formData.append('message', prompt)
275
- formData.append('project', props.projectPath)
276
- formData.append('user', props.username)
277
- formData.append('sessionID', currentSessionID)
278
-
279
- await sendPrompt(
280
- formData,
281
- (chunk) => {
282
- if (msg) msg.message += chunk
283
-
284
- if (autoScrollEnabled.value) scrollToBottom()
285
- },
286
- (error) => {
287
- setDisabledState(true)
288
- addChatMessage(texts.botIsSick)
289
- console.error('Error sending message: ' + error)
290
- }
291
- )
292
- }
293
-
294
- async function clearChat() {
295
- const { data, error } = await clearChatData(
296
- props.username,
297
- props.projectPath,
298
- currentAgentId.value,
299
- currentFormId.value
300
- )
301
- if (error || !data || !data.success) {
302
- setDisabledState(true)
303
- addChatMessage(texts.loginError)
304
- console.log('Error clearing chat: ' + error)
305
- return
306
- }
307
-
308
- resetChat()
309
- sendInitialMessage()
310
- }
311
-
312
- function getMessageClasses(sender: ChatBotMessageSender) {
313
- const classes: string[] = ['q-chatbot__messages-wrapper']
314
- if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
315
- return classes
316
- }
317
-
318
- function changeChat(chat: { formId: string; key: string }) {
319
- ;(currentAgentId.value = chat.key), (currentFormId.value = chat.formId)
320
- resetChat()
321
- loadChatData()
322
- }
323
-
324
- function applyFields(fields: AppliedFieldData[]) {
325
- if (!fields || fields.length === 0) return
326
-
327
- emits('apply-fields', fields)
328
- }
329
-
330
- watch(
331
- () => props.availableAgents,
332
- (newValue, oldValue) => {
333
- if (newValue.length === 0 && oldValue.length > 0) {
334
- currentAgentId.value = ''
335
- currentFormId.value = ''
336
- resetChat()
337
- initChat()
338
- }
339
- }
340
- )
341
-
342
- watch(
343
- () => props.agentData,
344
- async (newValue, oldValue) => {
345
- if (isDisabled.value) return;
346
-
347
- if (props.agentData?.id !== oldValue?.id) {
348
- currentAgentId.value = newValue?.id || ''
349
- currentFormId.value = newValue?.formId || ''
350
- resetChat()
351
- await initChat()
352
- }
353
-
354
- if (
355
- newValue?.id !== '' &&
356
- newValue &&
357
- newValue.jobId !== oldValue.jobId
358
- )
359
- await getAgentJob(newValue.jobId)
360
- }
361
- )
362
-
363
- watch(
364
- () => [props.agentData.id, props.agentData.formId],
365
- ([newId, newFormId]) => {
366
- currentAgentId.value = newId
367
- currentFormId.value = newFormId
368
- },
369
- { immediate: true }
370
- )
371
-
372
- defineOptions({ name: 'ChatBot' })
44
+ // Components
45
+ import { ChatBotMessage } from '@/components/ChatBotMessage'
46
+ import { ChatToolBar } from '@/components/ChatToolBar'
47
+ import { ChatBotInput } from '@/components/ChatBotInput'
48
+
49
+ // Utils
50
+ import { onMounted, nextTick, ref, watch, computed, onBeforeMount } from 'vue'
51
+ import { v4 as uuidv4 } from 'uuid'
52
+
53
+ // Types
54
+ import type {
55
+ ChatBotProps,
56
+ ChatBotMessageContent,
57
+ ChatBotMessageSender,
58
+ FieldData,
59
+ AppliedFieldData
60
+ } from './types'
61
+ import type { ChatBotFile } from '../ChatBotInput'
62
+
63
+ // Assets
64
+ import ChatBotIcon from '@/assets/chatbot_profile.svg'
65
+ import UserIcon from '@/assets/user_avatar.png'
66
+
67
+ // Composables
68
+ import { useTexts } from '@/composables/useTexts'
69
+ import { useChatApi } from '@/composables/useChatApi'
70
+ import { useChatMessages } from '@/composables/useChatMessages'
71
+
72
+ const props = withDefaults(defineProps<ChatBotProps>(), {
73
+ apiEndpoint: 'http://localhost:3000',
74
+ userImage: UserIcon,
75
+ chatbotImage: ChatBotIcon,
76
+ userPrompt: '',
77
+ availableAgents: () => [],
78
+ agentData: () => {
79
+ return {
80
+ id: '',
81
+ formId: '',
82
+ jobId: ''
83
+ }
84
+ }
85
+ })
86
+
87
+ const emit = defineEmits<{
88
+ (e: 'apply-fields', fields: AppliedFieldData[]): void
89
+ }>()
90
+
91
+ const isChatDisabled = ref(true)
92
+ const messagesContainer = ref<HTMLElement | null>(null)
93
+ const autoScrollEnabled = ref(true)
94
+
95
+ const currentAgentId = ref(props.agentData.id)
96
+ const currentFormId = ref(props.agentData.formId)
97
+
98
+ // Composables
99
+ const texts = useTexts()
100
+ const { isLoading, clearChatData, getChatData, getFieldSuggestionData, sendPrompt } =
101
+ useChatApi(props.apiEndpoint)
102
+ const { messages, addChatMessage, clearMessages, getLastMessage } = useChatMessages()
103
+
104
+ const isDisabled = computed(() => {
105
+ return isChatDisabled.value || isLoading.value
106
+ })
107
+
108
+ onBeforeMount(() => {
109
+ messagesContainer.value?.removeEventListener('scroll', handleScroll)
110
+ })
111
+
112
+ onMounted(() => {
113
+ initChat()
114
+ })
115
+
116
+ function setDisabledState(state: boolean) {
117
+ isChatDisabled.value = state
118
+ }
119
+
120
+ async function initChat() {
121
+ try {
122
+ await loadChatData()
123
+ } catch (error) {
124
+ addChatMessage(texts.loginError)
125
+ console.error('Error logging in: ' + error)
126
+ }
127
+ }
128
+
129
+ async function loadChatData() {
130
+ setDisabledState(true)
131
+ const { data, error } = await getChatData(
132
+ props.username,
133
+ props.projectPath,
134
+ currentAgentId.value,
135
+ currentFormId.value
136
+ )
137
+
138
+ if (error || !data || !data.success) {
139
+ setDisabledState(true)
140
+ addChatMessage(texts.botIsSick)
141
+ console.log('Error loading chat data: ' + error)
142
+ return
143
+ }
144
+
145
+ setDisabledState(false)
146
+ sendInitialMessage()
147
+
148
+ data.history.forEach(async (message: ChatBotMessageContent) => {
149
+ addChatMessage(
150
+ message.content,
151
+ message.type === 'ai' ? 'bot' : 'user',
152
+ undefined, // At this point we don't have preview URLs for past messages
153
+ message.sessionID
154
+ )
155
+ })
156
+ }
157
+
158
+ function sendInitialMessage() {
159
+ const message = texts.initialMessage
160
+ addChatMessage(message, 'bot', undefined, undefined, true)
161
+ }
162
+
163
+ function resetChat() {
164
+ // Only reset UI state here, message state is handled by composable
165
+ clearMessages()
166
+ setDisabledState(false)
167
+ autoScrollEnabled.value = true
168
+ }
169
+
170
+ function scrollToBottom() {
171
+ nextTick(() => {
172
+ if (messagesContainer.value) {
173
+ messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
174
+ }
175
+ })
176
+ }
177
+
178
+ function handleScroll() {
179
+ if (messagesContainer.value) {
180
+ const threshold = 20 // px threshold from the bottom
181
+ const { scrollTop, clientHeight, scrollHeight } = messagesContainer.value
182
+ autoScrollEnabled.value = scrollTop + clientHeight >= scrollHeight - threshold
183
+ }
184
+ }
185
+
186
+ async function sendMessage(prompt: string, file?: ChatBotFile) {
187
+ if (messagesContainer.value) {
188
+ messagesContainer.value.scrollTo({
189
+ top: messagesContainer.value.scrollHeight,
190
+ behavior: 'smooth'
191
+ })
192
+ }
193
+
194
+ addChatMessage(prompt, 'user', file)
195
+ nextTick(() => {
196
+ if (autoScrollEnabled.value) scrollToBottom()
197
+ })
198
+ setChatPrompt(prompt, file?.fileData)
199
+ }
200
+
201
+ async function getAgentJob(jobId: string) {
202
+ if (messagesContainer.value) {
203
+ messagesContainer.value.scrollTo({
204
+ top: messagesContainer.value.scrollHeight,
205
+ behavior: 'smooth'
206
+ })
207
+ }
208
+
209
+ // Add an empty bot message to trigger bouncing dots animation
210
+ addChatMessage('', 'bot')
211
+ nextTick(() => {
212
+ if (autoScrollEnabled.value) scrollToBottom()
213
+ })
214
+
215
+ const msg = getLastMessage()
216
+ if (!msg) return
217
+
218
+ await getFieldSuggestionData(
219
+ jobId,
220
+ (chunk: string) => {
221
+ msg.message += chunk
222
+ if (!msg.fields) msg.fields = []
223
+
224
+ const lastField = msg.fields[msg.fields.length - 1]
225
+ lastField.text = msg.message
226
+
227
+ if (autoScrollEnabled.value) scrollToBottom()
228
+ },
229
+ (metadata: Record<string, unknown>) => {
230
+ msg.message = ''
231
+ const data = metadata as FieldData
232
+ msg.fields?.push({
233
+ type: data.type,
234
+ name: data.name,
235
+ text: data.text
236
+ })
237
+ }
238
+ )
239
+ }
240
+
241
+ async function setChatPrompt(prompt: string, file?: File) {
242
+ // Add an empty bot message marked as streaming to trigger bouncing dots animation
243
+ addChatMessage('', 'bot')
244
+ const msg = getLastMessage()
245
+ if (!msg) return
246
+
247
+ const currentSessionID = msg?.sessionID || uuidv4()
248
+
249
+ const formData = new FormData()
250
+ if (file) {
251
+ formData.append('file', file)
252
+ }
253
+
254
+ formData.append('message', prompt)
255
+ formData.append('project', props.projectPath)
256
+ formData.append('user', props.username)
257
+ formData.append('sessionID', currentSessionID)
258
+
259
+ await sendPrompt(
260
+ formData,
261
+ (chunk) => {
262
+ if (msg) msg.message += chunk
263
+
264
+ if (autoScrollEnabled.value) scrollToBottom()
265
+ },
266
+ (error) => {
267
+ setDisabledState(true)
268
+ addChatMessage(texts.botIsSick)
269
+ console.error('Error sending message: ' + error)
270
+ }
271
+ )
272
+ }
273
+
274
+ async function clearChat() {
275
+ const { data, error } = await clearChatData(
276
+ props.username,
277
+ props.projectPath,
278
+ currentAgentId.value,
279
+ currentFormId.value
280
+ )
281
+ if (error || !data || !data.success) {
282
+ setDisabledState(true)
283
+ addChatMessage(texts.loginError)
284
+ console.log('Error clearing chat: ' + error)
285
+ return
286
+ }
287
+
288
+ resetChat()
289
+ sendInitialMessage()
290
+ }
291
+
292
+ function getMessageClasses(sender: ChatBotMessageSender) {
293
+ const classes: string[] = ['q-chatbot__messages-wrapper']
294
+ if (sender === 'user') classes.push('q-chatbot__messages-wrapper_right')
295
+ return classes
296
+ }
297
+
298
+ function changeChat(chat: { formId: string; key: string }) {
299
+ currentAgentId.value = chat.key
300
+ currentFormId.value = chat.formId
301
+ resetChat()
302
+ loadChatData()
303
+ }
304
+
305
+ function applyFields(fields: AppliedFieldData[]) {
306
+ if (!fields || fields.length === 0) return
307
+
308
+ emit('apply-fields', fields)
309
+ }
310
+
311
+ watch(
312
+ () => props.availableAgents,
313
+ (newValue, oldValue) => {
314
+ if (newValue.length === 0 && oldValue.length > 0) {
315
+ currentAgentId.value = ''
316
+ currentFormId.value = ''
317
+ resetChat()
318
+ initChat()
319
+ }
320
+ }
321
+ )
322
+
323
+ watch(
324
+ () => props.agentData,
325
+ async (newValue, oldValue) => {
326
+ if (isDisabled.value) return
327
+
328
+ if (props.agentData?.id !== oldValue?.id) {
329
+ currentAgentId.value = newValue?.id || ''
330
+ currentFormId.value = newValue?.formId || ''
331
+ resetChat()
332
+ await initChat()
333
+ }
334
+
335
+ if (newValue?.id !== '' && newValue && newValue.jobId !== oldValue.jobId)
336
+ await getAgentJob(newValue.jobId)
337
+ }
338
+ )
339
+
340
+ watch(
341
+ () => [props.agentData.id, props.agentData.formId],
342
+ ([newId, newFormId]) => {
343
+ currentAgentId.value = newId
344
+ currentFormId.value = newFormId
345
+ },
346
+ { immediate: true }
347
+ )
348
+
349
+ defineOptions({ name: 'ChatBot' })
373
350
  </script>