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