@quidgest/chatbot 0.5.3 → 0.5.4
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/ChatBot/ChatBot.vue.d.ts +2 -0
- package/dist/components/ChatBot/__tests__/ChatBot.spec.d.ts +1 -0
- package/dist/components/ChatBot/types.d.ts +2 -1
- package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +3 -1
- package/dist/components/FieldPreview/FieldPreview.vue.d.ts +2 -0
- package/dist/composables/useChatApi.d.ts +1 -1
- package/dist/composables/useChatMessages.d.ts +1 -0
- package/dist/composables/useTexts.d.ts +4 -0
- package/dist/index.js +15 -15
- package/dist/index.mjs +1597 -2209
- package/dist/style.css +1 -1
- package/package.json +2 -2
- package/src/components/ChatBot/ChatBot.vue +57 -60
- package/src/components/ChatBot/__tests__/ChatBot.spec.ts +52 -0
- package/src/components/ChatBot/__tests__/__snapshots__/ChatBot.spec.ts.snap +39 -0
- package/src/components/ChatBot/types.ts +2 -1
- package/src/components/ChatBotMessage/ChatBotMessage.vue +5 -3
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +1 -0
- package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +7 -3
- package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +4 -4
- package/src/components/ChatToolBar/ChatToolBar.vue +2 -1
- package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +38 -18
- package/src/components/FieldPreview/FieldPreview.vue +31 -9
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +4 -10
- package/src/components/FieldPreview/field-preview.scss +4 -0
- package/src/components/MarkdownRender/MarkdownRender.vue +1 -0
- package/src/composables/__tests__/useChatMessages.spec.ts +14 -1
- package/src/composables/useChatApi.ts +57 -53
- package/src/composables/useChatMessages.ts +6 -1
- package/src/composables/useTexts.ts +4 -0
- package/src/test/setup.ts +5 -0
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.markdown-renderer pre,.markdown-renderer code{white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:auto}.markdown-renderer pre{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.markdown-renderer code{padding:.2rem .4rem;border-radius:4px;font-size:.875rem}.q-field-preview{position:relative;display:flex;flex-direction:column;margin:1rem .25rem}.q-field-preview__toolbar{z-index:1;display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.1rem .2rem}.q-field-preview__content{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.q-field-preview__footer{display:flex;flex-direction:row;margin-top:.25rem}.q-field-preview:first-child{margin:0 1rem .25rem .25rem}.pulsing-dots{display:flex;align-items:center;justify-content:center;flex-direction:row;gap:.25rem}.generating-text{font-size:.9rem;color:var(--q-theme-primary)}.dots-container{display:flex;align-items:center;gap:.1rem}.dot{font-size:16px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}.q-chatbot__file-preview img,.q-chatbot__image-preview img{width:60px;height:60px;object-fit:cover;border-radius:4px;margin-right:.25rem;border:1px solid #eaebec;overflow:hidden}.q-chatbot__file-preview{display:inline-flex;align-items:center;position:relative;margin-top:.5rem;gap:.25rem;width:fit-content}.q-chatbot__file-preview img:focus{outline:solid rgb(var(--q-theme-info-rgb)/50%)}.q-chatbot__file-preview-container{display:flex;border-radius:.5rem;padding:.25rem .5rem;max-width:320px;align-items:center;justify-content:center;border:1px solid var(--q-theme-primary-light)}.q-chatbot__file-icon-container{display:flex;align-items:center;justify-content:center;border-radius:8px;width:36px;height:36px;flex-shrink:0;margin-right:10px;background:var(--q-theme-primary-light)}.q-chatbot__file-icon-container .q-icon{width:20px;height:20px}.q-chatbot__file-info{display:flex;flex-direction:column;overflow:hidden}.q-chatbot__file-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.q-chatbot__file-extension{font-size:10px}.q-chatbot{width:100%;height:100%;display:flex;flex-direction:column}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__remove-file{position:absolute;top:-8px;right:-8px}.q-chatbot__text p{margin:0}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;flex-direction:column;gap:.75rem;overflow:hidden}.q-chatbot__footer-container{padding:.8rem 0 0}.q-chatbot__input-wrapper{display:flex;flex-direction:column;position:relative}.q-chatbot .q-button.q-chatbot__remove-file{position:absolute;top:-5px;right:-5px;background-color:#00000080;color:#fff;border-radius:50%;padding:5px;font-size:10px;border:none}.q-chatbot .q-button.q-chatbot__remove-file:hover,.q-chatbot .q-button.q-chatbot__remove-file:focus{opacity:1;pointer-events:auto}.q-chatbot__send-container{padding-bottom:.25rem;display:flex;justify-content:space-between;width:100%}.q-chatbot__send-container .q-chatbot__send,.q-chatbot__send-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__send-container .spacer{flex-grow:1}.q-chatbot__footer{position:sticky;padding:0 .5rem;border:1px solid #eaebec;border-radius:.25rem;bottom:0;width:100%;display:flex;flex-direction:column;gap:.25rem}.q-chatbot__footer-disabled{background-color:rgb(var(--q-theme-neutral-light-rgb)/25%);cursor:not-allowed}.q-chatbot__footer.drag-over{border:2px dashed rgb(var(--q-theme-primary-rgb)/25%);background-color:#018bd20d}.q-chatbot__footer .q-chatbot__input{min-height:50px;max-height:100px;border-bottom:1px solid #eaebec;overflow-y:auto}.q-chatbot__footer .q-text-area{max-height:100%;overflow-y:auto}.q-chatbot__footer .q-text-area .q-field__control{border:none}.q-chatbot__upload-container{display:flex;justify-content:flex-start;padding:.25rem 0}.q-chatbot__upload-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__messages-container{display:flex;flex-direction:column;flex-grow:1;padding:0 1rem 2rem;gap:1.5rem;overflow-y:auto}.q-chatbot__messages-wrapper{display:flex;max-width:100%;gap:.2rem}.q-chatbot__tools{display:flex;flex-direction:row;justify-content:space-between;max-width:100%;padding:.25rem .5rem}.q-chatbot__message-wrapper{display:flex;flex-direction:column;gap:.2rem}.q-chatbot__message-container{display:flex;flex-direction:column;gap:.25rem}.q-chatbot__messages-wrapper_right{justify-content:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-container{align-items:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-wrapper{display:flex;align-items:flex-end}.q-chatbot__profile.q-icon__img{border-radius:50%;height:2rem;width:2rem}.q-chatbot__message{padding:.3rem .5rem;background-color:#eaebec;width:fit-content;min-height:2rem;white-space:normal;border-radius:0 .5rem .5rem}.q-chatbot__messages-wrapper_right .q-chatbot__message{background-color:#018bd233;border-radius:.5rem 0 .5rem .5rem;white-space:normal}.q-chatbot__sender{white-space:nowrap;color:#7c858d;font-size:.7rem}.q-chatbot__retry-button{align-items:center;display:flex}.q-chatbot__dialog-title{margin:.5rem 0}.hidden-input{display:none}
|
|
1
|
+
.markdown-renderer pre,.markdown-renderer code{white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:auto}.markdown-renderer pre{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.markdown-renderer code{padding:.2rem .4rem;border-radius:4px;font-size:.875rem}.q-field-preview{position:relative;display:flex;flex-direction:column;margin:1rem .25rem}.q-field-preview__toolbar{z-index:1;display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.1rem .2rem}.q-field-preview__content{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.q-field-preview__content.preserve-whitespace{white-space:pre-wrap}.q-field-preview__footer{display:flex;flex-direction:row;margin-top:.25rem}.q-field-preview:first-child{margin:0 1rem .25rem .25rem}.pulsing-dots{display:flex;align-items:center;justify-content:center;flex-direction:row;gap:.25rem}.generating-text{font-size:.9rem;color:var(--q-theme-primary)}.dots-container{display:flex;align-items:center;gap:.1rem}.dot{font-size:16px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}.q-chatbot__file-preview img,.q-chatbot__image-preview img{width:60px;height:60px;object-fit:cover;border-radius:4px;margin-right:.25rem;border:1px solid #eaebec;overflow:hidden}.q-chatbot__file-preview{display:inline-flex;align-items:center;position:relative;margin-top:.5rem;gap:.25rem;width:fit-content}.q-chatbot__file-preview img:focus{outline:solid rgb(var(--q-theme-info-rgb)/50%)}.q-chatbot__file-preview-container{display:flex;border-radius:.5rem;padding:.25rem .5rem;max-width:320px;align-items:center;justify-content:center;border:1px solid var(--q-theme-primary-light)}.q-chatbot__file-icon-container{display:flex;align-items:center;justify-content:center;border-radius:8px;width:36px;height:36px;flex-shrink:0;margin-right:10px;background:var(--q-theme-primary-light)}.q-chatbot__file-icon-container .q-icon{width:20px;height:20px}.q-chatbot__file-info{display:flex;flex-direction:column;overflow:hidden}.q-chatbot__file-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.q-chatbot__file-extension{font-size:10px}.q-chatbot{width:100%;height:100%;display:flex;flex-direction:column}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__remove-file{position:absolute;top:-8px;right:-8px}.q-chatbot__text p{margin:0}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;flex-direction:column;gap:.75rem;overflow:hidden}.q-chatbot__footer-container{padding:.8rem 0 0}.q-chatbot__input-wrapper{display:flex;flex-direction:column;position:relative}.q-chatbot .q-button.q-chatbot__remove-file{position:absolute;top:-5px;right:-5px;background-color:#00000080;color:#fff;border-radius:50%;padding:5px;font-size:10px;border:none}.q-chatbot .q-button.q-chatbot__remove-file:hover,.q-chatbot .q-button.q-chatbot__remove-file:focus{opacity:1;pointer-events:auto}.q-chatbot__send-container{padding-bottom:.25rem;display:flex;justify-content:space-between;width:100%}.q-chatbot__send-container .q-chatbot__send,.q-chatbot__send-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__send-container .spacer{flex-grow:1}.q-chatbot__footer{position:sticky;padding:0 .5rem;border:1px solid #eaebec;border-radius:.25rem;bottom:0;width:100%;display:flex;flex-direction:column;gap:.25rem}.q-chatbot__footer-disabled{background-color:rgb(var(--q-theme-neutral-light-rgb)/25%);cursor:not-allowed}.q-chatbot__footer.drag-over{border:2px dashed rgb(var(--q-theme-primary-rgb)/25%);background-color:#018bd20d}.q-chatbot__footer .q-chatbot__input{min-height:50px;max-height:100px;border-bottom:1px solid #eaebec;overflow-y:auto}.q-chatbot__footer .q-text-area{max-height:100%;overflow-y:auto}.q-chatbot__footer .q-text-area .q-field__control{border:none}.q-chatbot__upload-container{display:flex;justify-content:flex-start;padding:.25rem 0}.q-chatbot__upload-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__messages-container{display:flex;flex-direction:column;flex-grow:1;padding:0 1rem 2rem;gap:1.5rem;overflow-y:auto}.q-chatbot__messages-wrapper{display:flex;max-width:100%;gap:.2rem}.q-chatbot__tools{display:flex;flex-direction:row;justify-content:space-between;max-width:100%;padding:.25rem .5rem}.q-chatbot__message-wrapper{display:flex;flex-direction:column;gap:.2rem}.q-chatbot__message-container{display:flex;flex-direction:column;gap:.25rem}.q-chatbot__messages-wrapper_right{justify-content:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-container{align-items:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-wrapper{display:flex;align-items:flex-end}.q-chatbot__profile.q-icon__img{border-radius:50%;height:2rem;width:2rem}.q-chatbot__message{padding:.3rem .5rem;background-color:#eaebec;width:fit-content;min-height:2rem;white-space:normal;border-radius:0 .5rem .5rem}.q-chatbot__messages-wrapper_right .q-chatbot__message{background-color:#018bd233;border-radius:.5rem 0 .5rem .5rem;white-space:normal}.q-chatbot__sender{white-space:nowrap;color:#7c858d;font-size:.7rem}.q-chatbot__retry-button{align-items:center;display:flex}.q-chatbot__dialog-title{margin:.5rem 0}.hidden-input{display:none}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quidgest/chatbot",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"main": "dist/index.cjs",
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"vue-tsc": "^1.8.27"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
|
-
"@quidgest/ui": "0.16.
|
|
70
|
+
"@quidgest/ui": "0.16.19",
|
|
71
71
|
"vue": "3.5.17"
|
|
72
72
|
},
|
|
73
73
|
"engines": {
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
:api-endpoint="props.apiEndpoint"
|
|
27
27
|
:session-i-d="message.sessionID"
|
|
28
28
|
:fields="message.fields"
|
|
29
|
+
@regenerate="onFieldRegenerate"
|
|
29
30
|
@apply-fields="applyFields" />
|
|
30
31
|
</div>
|
|
31
32
|
</div>
|
|
@@ -47,7 +48,7 @@
|
|
|
47
48
|
import { ChatBotInput } from '@/components/ChatBotInput'
|
|
48
49
|
|
|
49
50
|
// Utils
|
|
50
|
-
import { onMounted,
|
|
51
|
+
import { onMounted, ref, watch, computed, onBeforeMount } from 'vue'
|
|
51
52
|
import { v4 as uuidv4 } from 'uuid'
|
|
52
53
|
|
|
53
54
|
// Types
|
|
@@ -86,6 +87,7 @@
|
|
|
86
87
|
|
|
87
88
|
const emit = defineEmits<{
|
|
88
89
|
(e: 'apply-fields', fields: AppliedFieldData[]): void
|
|
90
|
+
(e: 'direct-agent-chat', agentId: string, prompt: string): void
|
|
89
91
|
}>()
|
|
90
92
|
|
|
91
93
|
const isChatDisabled = ref(true)
|
|
@@ -97,9 +99,11 @@
|
|
|
97
99
|
|
|
98
100
|
// Composables
|
|
99
101
|
const texts = useTexts()
|
|
100
|
-
const { isLoading, clearChatData, getChatData,
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
const { isLoading, clearChatData, getChatData, getJobResultData, sendPrompt } = useChatApi(
|
|
103
|
+
props.apiEndpoint
|
|
104
|
+
)
|
|
105
|
+
const { messages, addChatMessage, clearMessages, getLastMessage, deleteMessageById } =
|
|
106
|
+
useChatMessages()
|
|
103
107
|
|
|
104
108
|
const isDisabled = computed(() => {
|
|
105
109
|
return isChatDisabled.value || isLoading.value
|
|
@@ -167,14 +171,6 @@
|
|
|
167
171
|
autoScrollEnabled.value = true
|
|
168
172
|
}
|
|
169
173
|
|
|
170
|
-
function scrollToBottom() {
|
|
171
|
-
nextTick(() => {
|
|
172
|
-
if (messagesContainer.value) {
|
|
173
|
-
messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
|
|
174
|
-
}
|
|
175
|
-
})
|
|
176
|
-
}
|
|
177
|
-
|
|
178
174
|
function handleScroll() {
|
|
179
175
|
if (messagesContainer.value) {
|
|
180
176
|
const threshold = 20 // px threshold from the bottom
|
|
@@ -192,9 +188,6 @@
|
|
|
192
188
|
}
|
|
193
189
|
|
|
194
190
|
addChatMessage(prompt, 'user', file)
|
|
195
|
-
nextTick(() => {
|
|
196
|
-
if (autoScrollEnabled.value) scrollToBottom()
|
|
197
|
-
})
|
|
198
191
|
setChatPrompt(prompt, file?.fileData)
|
|
199
192
|
}
|
|
200
193
|
|
|
@@ -208,43 +201,43 @@
|
|
|
208
201
|
|
|
209
202
|
// Add an empty bot message to trigger bouncing dots animation
|
|
210
203
|
addChatMessage('', 'bot')
|
|
211
|
-
nextTick(() => {
|
|
212
|
-
if (autoScrollEnabled.value) scrollToBottom()
|
|
213
|
-
})
|
|
214
204
|
|
|
215
205
|
const msg = getLastMessage()
|
|
216
206
|
if (!msg) return
|
|
217
207
|
|
|
218
|
-
await
|
|
208
|
+
await getJobResultData(
|
|
219
209
|
jobId,
|
|
220
210
|
(chunk: string) => {
|
|
221
211
|
msg.message += chunk
|
|
222
212
|
if (!msg.fields) msg.fields = []
|
|
223
213
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
if (msg.fields.length > 0) {
|
|
215
|
+
const lastField = msg.fields[msg.fields.length - 1]
|
|
216
|
+
lastField.text = msg.message
|
|
217
|
+
}
|
|
228
218
|
},
|
|
229
219
|
(metadata: Record<string, unknown>) => {
|
|
230
220
|
msg.message = ''
|
|
231
221
|
const data = metadata as FieldData
|
|
232
222
|
msg.fields?.push({
|
|
223
|
+
id: data.id,
|
|
233
224
|
type: data.type,
|
|
234
225
|
name: data.name,
|
|
235
226
|
text: data.text
|
|
236
227
|
})
|
|
228
|
+
},
|
|
229
|
+
(error: Error) => {
|
|
230
|
+
setDisabledState(true)
|
|
231
|
+
addChatMessage(texts.botIsSick)
|
|
232
|
+
console.error('Error getting job result: ' + error)
|
|
233
|
+
deleteMessageById(msg.id)
|
|
237
234
|
}
|
|
238
235
|
)
|
|
239
236
|
}
|
|
240
237
|
|
|
241
238
|
async function setChatPrompt(prompt: string, file?: File) {
|
|
242
239
|
// Add an empty bot message marked as streaming to trigger bouncing dots animation
|
|
243
|
-
|
|
244
|
-
const msg = getLastMessage()
|
|
245
|
-
if (!msg) return
|
|
246
|
-
|
|
247
|
-
const currentSessionID = msg?.sessionID || uuidv4()
|
|
240
|
+
const currentSessionID = uuidv4()
|
|
248
241
|
|
|
249
242
|
const formData = new FormData()
|
|
250
243
|
if (file) {
|
|
@@ -256,19 +249,26 @@
|
|
|
256
249
|
formData.append('user', props.username)
|
|
257
250
|
formData.append('sessionID', currentSessionID)
|
|
258
251
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
(
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
252
|
+
if (currentAgentId.value !== '') {
|
|
253
|
+
emit('direct-agent-chat', currentAgentId.value, prompt)
|
|
254
|
+
} else {
|
|
255
|
+
addChatMessage('', 'bot')
|
|
256
|
+
const msg = getLastMessage()
|
|
257
|
+
if (!msg) return
|
|
258
|
+
|
|
259
|
+
await sendPrompt(
|
|
260
|
+
formData,
|
|
261
|
+
(chunk) => {
|
|
262
|
+
if (msg) msg.message += chunk
|
|
263
|
+
},
|
|
264
|
+
(error) => {
|
|
265
|
+
setDisabledState(true)
|
|
266
|
+
addChatMessage(texts.botIsSick)
|
|
267
|
+
console.error('Error sending message: ' + error)
|
|
268
|
+
deleteMessageById(msg.id)
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
}
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
async function clearChat() {
|
|
@@ -295,11 +295,12 @@
|
|
|
295
295
|
return classes
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
function changeChat(chat: { formId: string; key: string }) {
|
|
298
|
+
async function changeChat(chat: { formId: string; key: string }) {
|
|
299
299
|
currentAgentId.value = chat.key
|
|
300
300
|
currentFormId.value = chat.formId
|
|
301
|
+
|
|
301
302
|
resetChat()
|
|
302
|
-
|
|
303
|
+
await initChat()
|
|
303
304
|
}
|
|
304
305
|
|
|
305
306
|
function applyFields(fields: AppliedFieldData[]) {
|
|
@@ -308,6 +309,11 @@
|
|
|
308
309
|
emit('apply-fields', fields)
|
|
309
310
|
}
|
|
310
311
|
|
|
312
|
+
function onFieldRegenerate(name: string) {
|
|
313
|
+
const prompt = texts.regenerateResponsePrompt.replace('{0}', name)
|
|
314
|
+
emit('direct-agent-chat', currentAgentId.value, prompt)
|
|
315
|
+
}
|
|
316
|
+
|
|
311
317
|
watch(
|
|
312
318
|
() => props.availableAgents,
|
|
313
319
|
(newValue, oldValue) => {
|
|
@@ -322,28 +328,19 @@
|
|
|
322
328
|
|
|
323
329
|
watch(
|
|
324
330
|
() => props.agentData,
|
|
325
|
-
async (
|
|
331
|
+
async (newData, oldData) => {
|
|
326
332
|
if (isDisabled.value) return
|
|
327
333
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
currentFormId.value = newValue?.formId || ''
|
|
331
|
-
resetChat()
|
|
332
|
-
await initChat()
|
|
333
|
-
}
|
|
334
|
+
const newJobId = newData.jobId
|
|
335
|
+
const oldJobId = oldData.jobId
|
|
334
336
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
)
|
|
337
|
+
const jobChanged = newJobId !== oldJobId
|
|
338
|
+
const agentChanged = newData.id !== currentAgentId.value
|
|
339
|
+
if (agentChanged) await changeChat({ formId: newData.formId, key: newData.id })
|
|
339
340
|
|
|
340
|
-
|
|
341
|
-
() => [props.agentData.id, props.agentData.formId],
|
|
342
|
-
([newId, newFormId]) => {
|
|
343
|
-
currentAgentId.value = newId
|
|
344
|
-
currentFormId.value = newFormId
|
|
341
|
+
if (newJobId && jobChanged) await getAgentJob(newJobId)
|
|
345
342
|
},
|
|
346
|
-
{
|
|
343
|
+
{ deep: true }
|
|
347
344
|
)
|
|
348
345
|
|
|
349
346
|
defineOptions({ name: 'ChatBot' })
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import ChatBot from '../ChatBot.vue'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
7
|
+
import { ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
// composables
|
|
10
|
+
import { useTexts } from '@/composables/useTexts'
|
|
11
|
+
|
|
12
|
+
// Types
|
|
13
|
+
import type { ChatBotProps } from '../types'
|
|
14
|
+
|
|
15
|
+
// Mock UseChatApi composable to avoid real API calls during tests
|
|
16
|
+
vi.mock('@/composables/useChatApi', () => {
|
|
17
|
+
return {
|
|
18
|
+
useChatApi: vi.fn(() => ({
|
|
19
|
+
isLoading: ref(false),
|
|
20
|
+
lastError: { value: null },
|
|
21
|
+
getChatData: vi.fn().mockResolvedValue({
|
|
22
|
+
data: { success: true, history: [] },
|
|
23
|
+
error: null
|
|
24
|
+
}),
|
|
25
|
+
clearChatData: vi.fn().mockResolvedValue({ data: { success: true }, error: null }),
|
|
26
|
+
getJobResultData: vi.fn(),
|
|
27
|
+
sendPrompt: vi.fn(),
|
|
28
|
+
handleFeedback: vi.fn()
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('ChatBot', () => {
|
|
34
|
+
const props: ChatBotProps = {
|
|
35
|
+
apiEndpoint: 'https://api.example.com/chat',
|
|
36
|
+
username: 'TestUser',
|
|
37
|
+
projectPath: 'quidgest',
|
|
38
|
+
dateFormat: 'HH:mm'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
it('renders the component with default props', () => {
|
|
42
|
+
const wrapper = mount(ChatBot, { props })
|
|
43
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('shows the initial message after loading', () => {
|
|
47
|
+
const wrapper = mount(ChatBot, { props })
|
|
48
|
+
const { initialMessage } = useTexts()
|
|
49
|
+
|
|
50
|
+
expect(wrapper.text()).toContain(initialMessage)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChatBot > renders the component with default props 1`] = `
|
|
4
|
+
"<div class="q-chatbot">
|
|
5
|
+
<div class="q-chatbot__content">
|
|
6
|
+
<div class="q-chatbot__tools">
|
|
7
|
+
<div class="q-chatbot__tools__select">
|
|
8
|
+
<!--v-if-->
|
|
9
|
+
</div><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__tools-clear" disabled="" title="Clear chat">
|
|
10
|
+
<!--v-if--><span class="q-button__content"><span data-test="bin"></span> </span>
|
|
11
|
+
</button>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="q-chatbot__messages-container"></div>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="q-chatbot__footer-container">
|
|
16
|
+
<div class="q-label"><label>What can I help with?</label></div>
|
|
17
|
+
<div class="q-chatbot__footer q-chatbot__footer-disabled">
|
|
18
|
+
<div class="q-chatbot__input-wrapper">
|
|
19
|
+
<!--v-if-->
|
|
20
|
+
<div class="q-chatbot__input">
|
|
21
|
+
<div class="q-field q-field--block q-field--disabled q-text-area">
|
|
22
|
+
<!--v-if-->
|
|
23
|
+
<div class="q-field__control">
|
|
24
|
+
<!--v-if--><textarea id="uid-1" class="q-text-area__input" disabled="" rows="2" resize="none" wrap="soft"></textarea>
|
|
25
|
+
<!--v-if-->
|
|
26
|
+
</div>
|
|
27
|
+
<!--v-if-->
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="q-chatbot__send-container"><button type="button" class="q-button q-button--outlined q-button--primary q-chatbot__upload" disabled="" title="Upload Image">
|
|
31
|
+
<!--v-if--><span class="q-button__content"><span data-test="upload"></span> </span>
|
|
32
|
+
</button><!-- Hidden file input --><input id="file-upload" type="file" accept=".png,.jpeg,.jpg,.svg,.webp,.pdf,.doc,.docx" class="hidden-input"><button type="button" class="q-button q-button--bold q-button--primary q-chatbot__send" disabled="" title="Send message" readonly="">
|
|
33
|
+
<!--v-if--><span class="q-button__content"><span data-test="send"></span> </span>
|
|
34
|
+
</button></div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>"
|
|
39
|
+
`;
|
|
@@ -25,6 +25,7 @@ export type AgentData = {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export type FieldData = {
|
|
28
|
+
id: string
|
|
28
29
|
name: string
|
|
29
30
|
type: string
|
|
30
31
|
text: string
|
|
@@ -32,7 +33,7 @@ export type FieldData = {
|
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export type AppliedFieldData = {
|
|
35
|
-
|
|
36
|
+
id: string
|
|
36
37
|
text: unknown
|
|
37
38
|
}
|
|
38
39
|
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
:name="field.name"
|
|
43
43
|
:type="field.type"
|
|
44
44
|
:disabled="loading"
|
|
45
|
+
@regenerate="() => emit('regenerate', field.name)"
|
|
45
46
|
@apply="(text) => applyField(text, field)" />
|
|
46
47
|
</template>
|
|
47
48
|
<template v-else>
|
|
@@ -74,8 +75,8 @@
|
|
|
74
75
|
import { QIcon } from '@quidgest/ui/components'
|
|
75
76
|
|
|
76
77
|
// Types
|
|
77
|
-
import type { ChatBotMessageProps } from './'
|
|
78
78
|
import { AppliedFieldData, FieldData } from '../ChatBot/types'
|
|
79
|
+
import type { ChatBotMessageProps } from './types'
|
|
79
80
|
|
|
80
81
|
// Composables
|
|
81
82
|
import { useTexts } from '@/composables/useTexts'
|
|
@@ -97,6 +98,7 @@
|
|
|
97
98
|
|
|
98
99
|
const emit = defineEmits<{
|
|
99
100
|
(e: 'apply-fields', fields: AppliedFieldData[]): void
|
|
101
|
+
(e: 'regenerate', fieldName: string): void
|
|
100
102
|
}>()
|
|
101
103
|
|
|
102
104
|
const texts = useTexts()
|
|
@@ -140,7 +142,7 @@
|
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
function applyField(text: unknown, field: FieldData) {
|
|
143
|
-
emit('apply-fields', [{
|
|
145
|
+
emit('apply-fields', [{ id: field.id, text }])
|
|
144
146
|
}
|
|
145
147
|
|
|
146
148
|
function applyAllFields() {
|
|
@@ -148,7 +150,7 @@
|
|
|
148
150
|
|
|
149
151
|
const fieldsToApply = props.fields.map((field) => {
|
|
150
152
|
return {
|
|
151
|
-
|
|
153
|
+
id: field.id,
|
|
152
154
|
text: parseFieldValue(field.type, field.text)
|
|
153
155
|
}
|
|
154
156
|
})
|
|
@@ -81,6 +81,7 @@ describe('ChatBotMessage', () => {
|
|
|
81
81
|
sender: 'bot',
|
|
82
82
|
fields: [
|
|
83
83
|
{
|
|
84
|
+
id: 'field-1',
|
|
84
85
|
type: 'text',
|
|
85
86
|
text: 'Sample Field',
|
|
86
87
|
name: 'This is a sample field value'
|
|
@@ -163,11 +164,13 @@ describe('ChatBotMessage', () => {
|
|
|
163
164
|
it('emits apply-fields event with fields when apply all button is clicked', async () => {
|
|
164
165
|
const testFields = [
|
|
165
166
|
{
|
|
167
|
+
id: 'field-1',
|
|
166
168
|
type: 'text',
|
|
167
169
|
text: 'Sample Field',
|
|
168
170
|
name: 'This is a sample field value'
|
|
169
171
|
},
|
|
170
172
|
{
|
|
173
|
+
id: 'field-2',
|
|
171
174
|
type: 'number',
|
|
172
175
|
text: 'Number Field',
|
|
173
176
|
name: '42'
|
|
@@ -187,7 +190,7 @@ describe('ChatBotMessage', () => {
|
|
|
187
190
|
|
|
188
191
|
const fieldsToApply = testFields.map((field) => {
|
|
189
192
|
return {
|
|
190
|
-
|
|
193
|
+
id: field.id,
|
|
191
194
|
text: parseFieldValue(field.type, field.text)
|
|
192
195
|
}
|
|
193
196
|
})
|
|
@@ -196,9 +199,10 @@ describe('ChatBotMessage', () => {
|
|
|
196
199
|
expect(wrapper.emitted('apply-fields')?.[0]).toEqual([fieldsToApply])
|
|
197
200
|
})
|
|
198
201
|
|
|
199
|
-
it('emits apply-
|
|
202
|
+
it('emits apply-field when a single field is applied', async () => {
|
|
200
203
|
const testFields = [
|
|
201
204
|
{
|
|
205
|
+
id: 'field-1',
|
|
202
206
|
type: 'text',
|
|
203
207
|
text: 'Sample Field',
|
|
204
208
|
name: 'This is a sample field value'
|
|
@@ -220,7 +224,7 @@ describe('ChatBotMessage', () => {
|
|
|
220
224
|
|
|
221
225
|
const fieldToApply = [
|
|
222
226
|
{
|
|
223
|
-
|
|
227
|
+
id: testFields[0].id,
|
|
224
228
|
text: parseFieldValue(testFields[0].type, testFields[0].text)
|
|
225
229
|
}
|
|
226
230
|
]
|
|
@@ -85,8 +85,8 @@ describe('ChatBotMessageButtons', () => {
|
|
|
85
85
|
const message = addChatMessage('Test message 1', 'bot')
|
|
86
86
|
|
|
87
87
|
message.fields = [
|
|
88
|
-
{ name: 'field1', text: 'value1', type: 'text' },
|
|
89
|
-
{ name: 'field2', text: 'value2', type: 'text' }
|
|
88
|
+
{ name: 'field1', text: 'value1', type: 'text', id: '1' },
|
|
89
|
+
{ name: 'field2', text: 'value2', type: 'text', id: '2' }
|
|
90
90
|
]
|
|
91
91
|
|
|
92
92
|
const wrapper = mount(ChatBotMessageButtons, {
|
|
@@ -104,8 +104,8 @@ describe('ChatBotMessageButtons', () => {
|
|
|
104
104
|
const message = addChatMessage('Test message 1', 'bot')
|
|
105
105
|
|
|
106
106
|
message.fields = [
|
|
107
|
-
{ name: 'field1', text: 'value1', type: 'text' },
|
|
108
|
-
{ name: 'field2', text: 'value2', type: 'text' }
|
|
107
|
+
{ name: 'field1', text: 'value1', type: 'text', id: '1' },
|
|
108
|
+
{ name: 'field2', text: 'value2', type: 'text', id: '2' }
|
|
109
109
|
]
|
|
110
110
|
|
|
111
111
|
const wrapper = mount(ChatBotMessageButtons, {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
<q-select
|
|
5
5
|
v-if="hasAgents"
|
|
6
6
|
v-model="selectedChat"
|
|
7
|
+
inline
|
|
7
8
|
class="q-chatbot__tools-select-input"
|
|
8
9
|
size="medium"
|
|
9
10
|
:items="availableChats"
|
|
@@ -22,7 +23,7 @@
|
|
|
22
23
|
</template>
|
|
23
24
|
|
|
24
25
|
<script setup lang="ts">
|
|
25
|
-
import { QButton,
|
|
26
|
+
import { QButton, QIcon } from '@quidgest/ui/components'
|
|
26
27
|
|
|
27
28
|
import { useTexts } from '@/composables/useTexts'
|
|
28
29
|
import { ChatToolBarProps } from './types'
|
|
@@ -7,6 +7,7 @@ import { mount } from '@vue/test-utils'
|
|
|
7
7
|
|
|
8
8
|
// Types
|
|
9
9
|
import type { ChatToolBarProps } from '../'
|
|
10
|
+
import { nextTick } from 'vue'
|
|
10
11
|
|
|
11
12
|
describe('ChatToolBar', () => {
|
|
12
13
|
const props: ChatToolBarProps = {
|
|
@@ -53,24 +54,25 @@ describe('ChatToolBar', () => {
|
|
|
53
54
|
const wrapper = mount(ChatToolBar, {
|
|
54
55
|
props: {
|
|
55
56
|
...props,
|
|
56
|
-
availableAgents: [
|
|
57
|
-
|
|
58
|
-
{ key: 'agent2', value: 'Agent 2', formId: 'key' }
|
|
59
|
-
],
|
|
60
|
-
selectedAgentKey: 'agent1'
|
|
57
|
+
availableAgents: [{ key: 'agent1', value: 'Agent 1', formId: 'key' }],
|
|
58
|
+
selectedAgentKey: ''
|
|
61
59
|
}
|
|
62
60
|
})
|
|
63
61
|
|
|
64
|
-
const select = wrapper.
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
const select = wrapper.find('.q-field__control')
|
|
63
|
+
await select.trigger('click')
|
|
64
|
+
|
|
65
|
+
const overlay = wrapper.find('.q-overlay__content')
|
|
66
|
+
|
|
67
|
+
const item = overlay.find('.q-list-item')
|
|
68
|
+
await item.trigger('click')
|
|
67
69
|
|
|
68
70
|
expect(wrapper.emitted()['change-chat']).toBeTruthy()
|
|
69
|
-
expect(wrapper.emitted()['change-chat']
|
|
71
|
+
expect(wrapper.emitted()['change-chat'][0]).toEqual([
|
|
70
72
|
{
|
|
71
|
-
key: '
|
|
73
|
+
key: 'agent1',
|
|
72
74
|
formId: 'key',
|
|
73
|
-
value: 'Agent
|
|
75
|
+
value: 'Agent 1'
|
|
74
76
|
}
|
|
75
77
|
])
|
|
76
78
|
})
|
|
@@ -86,9 +88,21 @@ describe('ChatToolBar', () => {
|
|
|
86
88
|
selectedAgentKey: 'agent1'
|
|
87
89
|
}
|
|
88
90
|
})
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
await select.
|
|
91
|
+
|
|
92
|
+
const select = wrapper.find('.q-field__control')
|
|
93
|
+
await select.trigger('click')
|
|
94
|
+
|
|
95
|
+
const overlay = wrapper.find('.q-overlay__content')
|
|
96
|
+
|
|
97
|
+
// Manually add a fake item that does not exist in the options
|
|
98
|
+
const fakeItem = document.createElement('div')
|
|
99
|
+
fakeItem.setAttribute('data-key', 'agent3')
|
|
100
|
+
overlay.element.appendChild(fakeItem)
|
|
101
|
+
|
|
102
|
+
await nextTick()
|
|
103
|
+
const item = overlay.find('[data-key="agent3"]')
|
|
104
|
+
await item.trigger('click')
|
|
105
|
+
|
|
92
106
|
expect(wrapper.emitted()['change-chat']).toBeFalsy()
|
|
93
107
|
})
|
|
94
108
|
|
|
@@ -103,12 +117,18 @@ describe('ChatToolBar', () => {
|
|
|
103
117
|
selectedAgentKey: 'agent1'
|
|
104
118
|
}
|
|
105
119
|
})
|
|
106
|
-
const select = wrapper.
|
|
107
|
-
|
|
108
|
-
|
|
120
|
+
const select = wrapper.find('.q-field__control')
|
|
121
|
+
await select.trigger('click')
|
|
122
|
+
|
|
123
|
+
const overlay = wrapper.find('.q-overlay__content')
|
|
124
|
+
const items = overlay.findAll('.q-list-item')
|
|
125
|
+
|
|
126
|
+
// Default options is always the last one
|
|
127
|
+
const defaultOption = items[items.length - 1]
|
|
128
|
+
await defaultOption.trigger('click')
|
|
109
129
|
|
|
110
130
|
expect(wrapper.emitted()['change-chat']).toBeTruthy()
|
|
111
|
-
expect(wrapper.emitted()['change-chat']
|
|
131
|
+
expect(wrapper.emitted()['change-chat'][0]).toEqual([
|
|
112
132
|
{
|
|
113
133
|
key: '',
|
|
114
134
|
formId: ''
|