@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.
- package/README.md +1 -2
- package/dist/components/ChatBot/ChatBot.vue.d.ts +2 -0
- package/dist/components/ChatBot/types.d.ts +2 -1
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +2 -2
- package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +3 -1
- package/dist/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
- package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -0
- package/dist/components/FieldPreview/FieldPreview.vue.d.ts +2 -0
- package/dist/components/FieldPreview/__tests__/FieldPreview.spec.d.ts +1 -0
- package/dist/components/MarkdownRender/__tests__/MarkdownRender.spec.d.ts +1 -0
- package/dist/components/PulseDots/__tests__/PulseDots.spec.d.ts +1 -0
- package/dist/composables/__tests__/useChatMessages.spec.d.ts +1 -0
- package/dist/composables/__tests__/useSSE.spec.d.ts +1 -0
- package/dist/composables/useChatApi.d.ts +1 -1
- package/dist/composables/useChatMessages.d.ts +2 -1
- package/dist/composables/useSSE.d.ts +1 -2
- package/dist/composables/useTexts.d.ts +5 -0
- package/dist/index.js +25 -25
- package/dist/index.mjs +2317 -1810
- package/dist/style.css +1 -1
- package/dist/test/setup.d.ts +1 -0
- package/dist/utils/__tests__/parseFieldValue.spec.d.ts +1 -0
- package/package.json +28 -7
- package/src/assets/styles/styles.scss +212 -220
- package/src/components/ChatBot/ChatBot.vue +346 -370
- package/src/components/ChatBot/types.ts +34 -33
- package/src/components/ChatBotInput/ChatBotInput.vue +181 -190
- package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +292 -0
- package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
- package/src/components/ChatBotInput/types.ts +24 -24
- package/src/components/ChatBotMessage/ChatBotMessage.vue +133 -134
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
- package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
- package/src/components/ChatBotMessage/types.ts +52 -52
- package/src/components/ChatToolBar/ChatToolBar.vue +69 -64
- package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +138 -0
- package/src/components/ChatToolBar/__tests__/__snapshots__/ChatToolBar.spec.ts.snap +11 -0
- package/src/components/ChatToolBar/types.ts +12 -12
- package/src/components/FieldPreview/FieldPreview.vue +83 -63
- package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +19 -0
- package/src/components/FieldPreview/field-preview.scss +28 -24
- package/src/components/FieldPreview/types.ts +5 -5
- package/src/components/MarkdownRender/MarkdownRender.vue +16 -15
- package/src/components/MarkdownRender/__tests__/MarkdownRender.spec.ts +68 -0
- package/src/components/MarkdownRender/__tests__/__snapshots__/MarkdownRender.spec.ts.snap +8 -0
- package/src/components/MarkdownRender/markdown-render.scss +19 -20
- package/src/components/MarkdownRender/types.ts +3 -3
- package/src/components/PulseDots/PulseDots.vue +17 -17
- package/src/components/PulseDots/__tests__/PulseDots.spec.ts +35 -0
- package/src/components/PulseDots/__tests__/__snapshots__/PulseDots.spec.ts.snap +7 -0
- package/src/components/PulseDots/__tests__/__snapshots__/pulse-dots.spec.ts.snap +7 -0
- package/src/components/PulseDots/pulse-dots.scss +24 -23
- package/src/composables/__tests__/useChatMessages.spec.ts +64 -0
- package/src/composables/__tests__/useSSE.spec.ts +132 -0
- package/src/composables/useChatApi.ts +132 -134
- package/src/composables/useChatMessages.ts +50 -48
- package/src/composables/useSSE.ts +75 -73
- package/src/composables/useTexts.ts +33 -30
- package/src/test/setup.ts +41 -0
- package/src/utils/__tests__/parseFieldValue.spec.ts +27 -0
- package/src/utils/parseFieldValue.ts +12 -0
- package/src/utils/helper.ts +0 -12
- /package/dist/utils/{helper.d.ts → parseFieldValue.d.ts} +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import { ChatBotInput } from '..'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type { ChatBotInputProps } from '..'
|
|
10
|
+
import type { ComponentPublicInstance } from 'vue'
|
|
11
|
+
|
|
12
|
+
type ChatBotInputVm = ComponentPublicInstance<{
|
|
13
|
+
imageInput: HTMLInputElement | null
|
|
14
|
+
imagePreviewUrl: string
|
|
15
|
+
hasSelectedImage: boolean
|
|
16
|
+
}>
|
|
17
|
+
|
|
18
|
+
describe('ChatBotInput', () => {
|
|
19
|
+
const props: ChatBotInputProps = {
|
|
20
|
+
disabled: false,
|
|
21
|
+
loading: false,
|
|
22
|
+
userPrompt: '',
|
|
23
|
+
agentId: 'test-agent-id'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
it('renders correctly with default props', () => {
|
|
27
|
+
const wrapper = mount(ChatBotInput, {
|
|
28
|
+
props: { ...props }
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('adds the right class when disabled', async () => {
|
|
35
|
+
const wrapper = mount(ChatBotInput, {
|
|
36
|
+
props: { ...props, disabled: true }
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
40
|
+
expect(footer.classes()).toContain('q-chatbot__footer-disabled')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('adds the dragover class when a file is being dragged over', async () => {
|
|
44
|
+
const wrapper = mount(ChatBotInput, {
|
|
45
|
+
props: { ...props }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
49
|
+
await footer.trigger('dragover')
|
|
50
|
+
expect(footer.classes()).toContain('drag-over')
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
it('uses provided userPrompt from props', () => {
|
|
54
|
+
const wrapper = mount(ChatBotInput, {
|
|
55
|
+
props: { ...props, userPrompt: 'Hello, World!' }
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
const textarea = wrapper.find<HTMLTextAreaElement>('.q-chatbot__input textarea')
|
|
59
|
+
expect(textarea.element.value).toBe('Hello, World!')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('removes the dragover class when dragleave is triggered', async () => {
|
|
63
|
+
const wrapper = mount(ChatBotInput, {
|
|
64
|
+
props: { ...props }
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
68
|
+
await footer.trigger('dragover')
|
|
69
|
+
expect(footer.classes()).toContain('drag-over')
|
|
70
|
+
|
|
71
|
+
await footer.trigger('dragleave')
|
|
72
|
+
expect(footer.classes()).not.toContain('drag-over')
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('prevents drop if input is disabled', async () => {
|
|
76
|
+
const wrapper = mount(ChatBotInput, {
|
|
77
|
+
props: { ...props, disabled: true }
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
81
|
+
await footer.trigger('drop')
|
|
82
|
+
|
|
83
|
+
expect(wrapper.find('img').exists()).toBe(false)
|
|
84
|
+
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(false)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('prevents drop if theres is no files', async () => {
|
|
88
|
+
const wrapper = mount(ChatBotInput, {
|
|
89
|
+
props: { ...props }
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
93
|
+
await footer.trigger('drop', {
|
|
94
|
+
dataTransfer: {}
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
expect(wrapper.find('img').exists()).toBe(false)
|
|
98
|
+
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(false)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('accepts image files on drop', async () => {
|
|
102
|
+
const wrapper = mount(ChatBotInput, {
|
|
103
|
+
props: { ...props }
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
107
|
+
const file = new File(['dummy content'], 'example.png', { type: 'image/png' })
|
|
108
|
+
|
|
109
|
+
await footer.trigger('drop', {
|
|
110
|
+
dataTransfer: {
|
|
111
|
+
files: [file]
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
expect(wrapper.find('img').exists()).toBe(true)
|
|
116
|
+
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(true)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('does not drop image if hidden input does not exist', async () => {
|
|
120
|
+
const wrapper = mount(ChatBotInput, {
|
|
121
|
+
props: { ...props }
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
125
|
+
const file = new File(['dummy content'], 'example.png', { type: 'image/png' })
|
|
126
|
+
|
|
127
|
+
// Manually set imageInput to null to simulate the missing input
|
|
128
|
+
// In reality, this should not happen as the input is always rendered but hidden
|
|
129
|
+
const vm = wrapper.vm as ChatBotInputVm
|
|
130
|
+
vm.imageInput = null
|
|
131
|
+
|
|
132
|
+
// Trigger the drop event
|
|
133
|
+
await footer.trigger('drop', {
|
|
134
|
+
dataTransfer: {
|
|
135
|
+
files: [file]
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
// Assert that no image preview is created
|
|
140
|
+
expect(wrapper.find('img').exists()).toBe(false)
|
|
141
|
+
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(false)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
it('does not send empty prompts', async () => {
|
|
145
|
+
const wrapper = mount(ChatBotInput, {
|
|
146
|
+
props: { ...props, userPrompt: '' }
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const sendButton = wrapper.find('.q-chatbot__send')
|
|
150
|
+
await sendButton.trigger('click')
|
|
151
|
+
|
|
152
|
+
expect(wrapper.emitted()['send-message']).toBeFalsy()
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('does not send prompts when loading', async () => {
|
|
156
|
+
const wrapper = mount(ChatBotInput, {
|
|
157
|
+
props: { ...props, userPrompt: 'Hello', loading: true }
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
const sendButton = wrapper.find('.q-chatbot__send')
|
|
161
|
+
await sendButton.trigger('click')
|
|
162
|
+
|
|
163
|
+
expect(wrapper.emitted()['send-message']).toBeFalsy()
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('does not send prompts when disabled', async () => {
|
|
167
|
+
const wrapper = mount(ChatBotInput, {
|
|
168
|
+
props: { ...props, userPrompt: 'Hello', disabled: true }
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const sendButton = wrapper.find('.q-chatbot__send')
|
|
172
|
+
await sendButton.trigger('click')
|
|
173
|
+
|
|
174
|
+
expect(wrapper.emitted()['send-message']).toBeFalsy()
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('sends the prompt when the send button is clicked', async () => {
|
|
178
|
+
const wrapper = mount(ChatBotInput, {
|
|
179
|
+
props: { ...props, userPrompt: 'Hello' }
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const sendButton = wrapper.find('.q-chatbot__send')
|
|
183
|
+
await sendButton.trigger('click')
|
|
184
|
+
|
|
185
|
+
expect(wrapper.emitted()['send-message']).toBeTruthy()
|
|
186
|
+
expect(wrapper.emitted()['send-message'][0]).toEqual(['Hello'])
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
it('sends the prompt when Enter key is pressed', async () => {
|
|
190
|
+
const wrapper = mount(ChatBotInput, {
|
|
191
|
+
props: { ...props, userPrompt: 'Hello' }
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const textArea = wrapper.find('textarea')
|
|
195
|
+
await textArea.trigger('keyup.enter')
|
|
196
|
+
|
|
197
|
+
expect(wrapper.emitted()['send-message']).toBeTruthy()
|
|
198
|
+
expect(wrapper.emitted()['send-message'][0]).toEqual(['Hello'])
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('sends a prompt with image attached', async () => {
|
|
202
|
+
const wrapper = mount(ChatBotInput, {
|
|
203
|
+
props: { ...props, userPrompt: 'Hello' }
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const file = new File(['dummy content'], 'example.png', { type: 'image/png' })
|
|
207
|
+
const dataTransfer = new DataTransfer()
|
|
208
|
+
dataTransfer.items.add(file)
|
|
209
|
+
|
|
210
|
+
// Simulate the drop event
|
|
211
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
212
|
+
await footer.trigger('drop', {
|
|
213
|
+
dataTransfer
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
// Simulate clicking the send button
|
|
217
|
+
const sendButton = wrapper.find('.q-chatbot__send')
|
|
218
|
+
await sendButton.trigger('click')
|
|
219
|
+
|
|
220
|
+
// Assert that the event was emitted with the correct payload
|
|
221
|
+
expect(wrapper.emitted()['send-message']).toBeTruthy()
|
|
222
|
+
expect(wrapper.emitted()['send-message'][0]).toEqual([
|
|
223
|
+
'Hello',
|
|
224
|
+
{
|
|
225
|
+
file: file,
|
|
226
|
+
previewUrl: expect.stringContaining('blob:')
|
|
227
|
+
}
|
|
228
|
+
])
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('removes the selected image when the remove button is clicked', async () => {
|
|
232
|
+
const wrapper = mount(ChatBotInput, {
|
|
233
|
+
props: { ...props, userPrompt: 'Hello' }
|
|
234
|
+
})
|
|
235
|
+
|
|
236
|
+
const file = new File(['dummy content'], 'example.png', { type: 'image/png' })
|
|
237
|
+
const dataTransfer = new DataTransfer()
|
|
238
|
+
dataTransfer.items.add(file)
|
|
239
|
+
|
|
240
|
+
// Simulate the drop event
|
|
241
|
+
const footer = wrapper.find('.q-chatbot__footer')
|
|
242
|
+
await footer.trigger('drop', {
|
|
243
|
+
dataTransfer
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
expect(wrapper.find('img').exists()).toBe(true)
|
|
247
|
+
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(true)
|
|
248
|
+
|
|
249
|
+
// Simulate clicking the remove image button
|
|
250
|
+
const removeButton = wrapper.find('.q-chatbot__remove-image')
|
|
251
|
+
await removeButton.trigger('click')
|
|
252
|
+
|
|
253
|
+
expect(wrapper.find('img').exists()).toBe(false)
|
|
254
|
+
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(false)
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('triggers click on the hidden input when upload button is clicked', async () => {
|
|
258
|
+
const wrapper = mount(ChatBotInput, {
|
|
259
|
+
props: { ...props }
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
const uploadButton = wrapper.find('.q-chatbot__upload')
|
|
263
|
+
const fileInput = wrapper.find<HTMLInputElement>('#image-upload')
|
|
264
|
+
|
|
265
|
+
// Mock the click method on the file input
|
|
266
|
+
const clickMock = vi.fn()
|
|
267
|
+
fileInput.element.click = clickMock
|
|
268
|
+
|
|
269
|
+
// Trigger the upload button click
|
|
270
|
+
await uploadButton.trigger('click')
|
|
271
|
+
|
|
272
|
+
expect(clickMock).toHaveBeenCalled()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
it('handles image upload via the hidden input', async () => {
|
|
276
|
+
const wrapper = mount(ChatBotInput, {
|
|
277
|
+
props: { ...props }
|
|
278
|
+
})
|
|
279
|
+
const fileInput = wrapper.find<HTMLInputElement>('#image-upload')
|
|
280
|
+
const file = new File(['dummy content'], 'example.png', { type: 'image/png' })
|
|
281
|
+
|
|
282
|
+
// Simulate selecting a file
|
|
283
|
+
const dataTransfer = new DataTransfer()
|
|
284
|
+
dataTransfer.items.add(file)
|
|
285
|
+
fileInput.element.files = dataTransfer.files
|
|
286
|
+
|
|
287
|
+
await fileInput.trigger('change')
|
|
288
|
+
|
|
289
|
+
expect(wrapper.find('img').exists()).toBe(true)
|
|
290
|
+
expect(wrapper.find('.q-chatbot__image-preview').exists()).toBe(true)
|
|
291
|
+
})
|
|
292
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChatBotInput > renders correctly with default props 1`] = `
|
|
4
|
+
"<div class="q-label"><label>What can I help with?</label></div>
|
|
5
|
+
<div class="q-chatbot__footer">
|
|
6
|
+
<div class="q-chatbot__input-wrapper">
|
|
7
|
+
<!--v-if-->
|
|
8
|
+
<div class="q-chatbot__input">
|
|
9
|
+
<div class="q-field q-field--block q-text-area">
|
|
10
|
+
<!--v-if-->
|
|
11
|
+
<div class="q-field__control">
|
|
12
|
+
<!--v-if--><textarea id="uid-1" class="q-text-area__input" rows="2" resize="none" wrap="soft"></textarea>
|
|
13
|
+
<!--v-if-->
|
|
14
|
+
</div>
|
|
15
|
+
<!--v-if-->
|
|
16
|
+
</div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
<div class="q-chatbot__send-container"><button type="button" class="q-button q-button--outlined q-button--primary q-chatbot__upload" title="Upload Image">
|
|
20
|
+
<!--v-if--><span class="q-button__content"><span data-test="upload"></span> </span>
|
|
21
|
+
</button><!-- Hidden file input --><input id="image-upload" type="file" accept=".png, .jpeg, .jpg, .svg, .webp" class="hidden-input"><button type="button" class="q-button q-button--bold q-button--primary q-chatbot__send" disabled="" title="Send message" readonly="">
|
|
22
|
+
<!--v-if--><span class="q-button__content"><span data-test="send"></span> </span>
|
|
23
|
+
</button></div>
|
|
24
|
+
</div>"
|
|
25
|
+
`;
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
export type ChatBotInputProps = {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
/**
|
|
3
|
+
* If the footer is disabled or not
|
|
4
|
+
*/
|
|
5
|
+
disabled?: boolean
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
/**
|
|
8
|
+
* If the footer is loading or not
|
|
9
|
+
*/
|
|
10
|
+
loading?: boolean
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
/**
|
|
13
|
+
* The message to be sent
|
|
14
|
+
*/
|
|
15
|
+
userPrompt?: string
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
/**
|
|
18
|
+
* The agent to be used
|
|
19
|
+
*/
|
|
20
|
+
agentId?: string
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export type ChatBotImage = {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
/**
|
|
25
|
+
* The image URL
|
|
26
|
+
*/
|
|
27
|
+
previewUrl: string
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
/**
|
|
30
|
+
* The file object
|
|
31
|
+
*/
|
|
32
|
+
file: File
|
|
33
33
|
}
|
|
@@ -1,139 +1,138 @@
|
|
|
1
1
|
<template>
|
|
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
|
-
@apply-all="applyAllFields" />
|
|
54
|
-
</div>
|
|
2
|
+
<div class="q-chatbot__message-container">
|
|
3
|
+
<q-icon
|
|
4
|
+
type="img"
|
|
5
|
+
:icon="messageImage"
|
|
6
|
+
:alt="texts.senderImage"
|
|
7
|
+
class="q-chatbot__profile" />
|
|
8
|
+
|
|
9
|
+
<div class="q-chatbot__message-wrapper">
|
|
10
|
+
<div
|
|
11
|
+
v-if="props.imagePreviewUrl && props.imagePreviewUrl.length > 0"
|
|
12
|
+
class="q-chatbot__image-preview">
|
|
13
|
+
<img
|
|
14
|
+
:src="props.imagePreviewUrl"
|
|
15
|
+
:alt="texts.imagePreview" />
|
|
16
|
+
</div>
|
|
17
|
+
<div class="q-chatbot__message">
|
|
18
|
+
<pulse-dots v-if="loading" />
|
|
19
|
+
<template v-else-if="props.sender === 'bot' && props.fields.length > 0">
|
|
20
|
+
<field-preview
|
|
21
|
+
v-for="(field, index) in props.fields"
|
|
22
|
+
:key="index"
|
|
23
|
+
class="q-chatbot__text"
|
|
24
|
+
:text="field.text || ''"
|
|
25
|
+
:name="field.name"
|
|
26
|
+
:type="field.type"
|
|
27
|
+
:disabled="loading"
|
|
28
|
+
@regenerate="() => emit('regenerate', field.name)"
|
|
29
|
+
@apply="(text) => applyField(text, field)" />
|
|
30
|
+
</template>
|
|
31
|
+
<template v-else>
|
|
32
|
+
<markdown-render
|
|
33
|
+
v-if="props.sender === 'bot'"
|
|
34
|
+
class="q-chatbot__text"
|
|
35
|
+
:source="props.message || ''" />
|
|
36
|
+
<div
|
|
37
|
+
v-else
|
|
38
|
+
class="q-chatbot__text">
|
|
39
|
+
{{ props.message }}
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<chat-bot-message-buttons
|
|
46
|
+
:show-buttons="isBotMessageAndNotDefault"
|
|
47
|
+
:loading="props.loading"
|
|
48
|
+
:date-format="props.dateFormat"
|
|
49
|
+
@copy-response="copyResponse"
|
|
50
|
+
@submit-feedback="onSubmitFeedback"
|
|
51
|
+
@apply-all="applyAllFields" />
|
|
52
|
+
</div>
|
|
55
53
|
</template>
|
|
56
54
|
|
|
57
55
|
<script setup lang="ts">
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
56
|
+
// Utils
|
|
57
|
+
import { computed } from 'vue'
|
|
58
|
+
import { QIcon } from '@quidgest/ui/components'
|
|
59
|
+
|
|
60
|
+
// Types
|
|
61
|
+
import { AppliedFieldData, FieldData } from '../ChatBot/types'
|
|
62
|
+
import type { ChatBotMessageProps } from './types'
|
|
63
|
+
|
|
64
|
+
// Composables
|
|
65
|
+
import { useTexts } from '@/composables/useTexts'
|
|
66
|
+
import { useChatApi } from '@/composables/useChatApi'
|
|
67
|
+
|
|
68
|
+
// Components
|
|
69
|
+
import { MarkdownRender } from '@/components/MarkdownRender'
|
|
70
|
+
import { FieldPreview } from '../FieldPreview'
|
|
71
|
+
import { ChatBotMessageButtons } from './'
|
|
72
|
+
import PulseDots from '@/components/PulseDots/PulseDots.vue'
|
|
73
|
+
import { parseFieldValue } from '@/utils/parseFieldValue'
|
|
74
|
+
|
|
75
|
+
const props = withDefaults(defineProps<ChatBotMessageProps>(), {
|
|
76
|
+
sender: 'user',
|
|
77
|
+
userImage: undefined,
|
|
78
|
+
date: () => new Date(),
|
|
79
|
+
fields: () => []
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const emit = defineEmits<{
|
|
83
|
+
(e: 'apply-fields', fields: AppliedFieldData[]): void
|
|
84
|
+
(e: 'regenerate', fieldName: string): void
|
|
85
|
+
}>()
|
|
86
|
+
|
|
87
|
+
const texts = useTexts()
|
|
88
|
+
|
|
89
|
+
const { handleFeedback } = useChatApi(props.apiEndpoint)
|
|
90
|
+
|
|
91
|
+
const isBotMessageAndNotDefault = computed(() => {
|
|
92
|
+
return (
|
|
93
|
+
props.sender === 'bot' &&
|
|
94
|
+
!Object.values(texts || {}).includes(props.message || '') &&
|
|
95
|
+
!props.isWelcomeMessage
|
|
96
|
+
)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const messageImage = computed(() =>
|
|
100
|
+
props.sender === 'bot' ? props.chatbotImage : props.userImage
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
function copyResponse() {
|
|
104
|
+
if (!props.message) return
|
|
105
|
+
|
|
106
|
+
navigator.clipboard
|
|
107
|
+
.writeText(props.message)
|
|
108
|
+
.then(() => {
|
|
109
|
+
console.log('Response copied to clipboard')
|
|
110
|
+
})
|
|
111
|
+
.catch((err) => {
|
|
112
|
+
console.error('Failed to copy response: ', err)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function applyField(text: unknown, field: FieldData) {
|
|
117
|
+
emit('apply-fields', [{ id: field.id, text }])
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function applyAllFields() {
|
|
121
|
+
if (!props.fields || props.fields.length === 0) return
|
|
122
|
+
|
|
123
|
+
const fieldsToApply = props.fields.map((field) => {
|
|
124
|
+
return {
|
|
125
|
+
id: field.id,
|
|
126
|
+
text: parseFieldValue(field.type, field.text)
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
emit('apply-fields', fieldsToApply)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function onSubmitFeedback(feedback: number, comment: string) {
|
|
134
|
+
if (feedback === null || comment === null || !props.sessionID) return
|
|
135
|
+
|
|
136
|
+
handleFeedback(feedback, comment, props.sessionID)
|
|
137
|
+
}
|
|
139
138
|
</script>
|