@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
@@ -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
- * If the footer is disabled or not
4
- */
5
- disabled?: boolean
2
+ /**
3
+ * If the footer is disabled or not
4
+ */
5
+ disabled?: boolean
6
6
 
7
- /**
8
- * If the footer is loading or not
9
- */
10
- loading?: boolean
7
+ /**
8
+ * If the footer is loading or not
9
+ */
10
+ loading?: boolean
11
11
 
12
- /**
13
- * The message to be sent
14
- */
15
- userPrompt?: string
12
+ /**
13
+ * The message to be sent
14
+ */
15
+ userPrompt?: string
16
16
 
17
- /**
18
- * The agent to be used
19
- */
20
- agentId?: string
17
+ /**
18
+ * The agent to be used
19
+ */
20
+ agentId?: string
21
21
  }
22
22
 
23
23
  export type ChatBotImage = {
24
- /**
25
- * The image URL
26
- */
27
- previewUrl: string
24
+ /**
25
+ * The image URL
26
+ */
27
+ previewUrl: string
28
28
 
29
- /**
30
- * The file object
31
- */
32
- file: File
29
+ /**
30
+ * The file object
31
+ */
32
+ file: File
33
33
  }
@@ -1,139 +1,138 @@
1
1
  <template>
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
20
- v-else-if="
21
- props.sender === 'bot' && props.fields.length > 0
22
- ">
23
- <field-preview
24
- v-for="(field, index) in props.fields"
25
- class="q-chatbot__text"
26
- :key="index"
27
- :text="field.text || ''"
28
- :name="field.name"
29
- :type="field.type"
30
- :disabled="loading"
31
- @apply="(text) => applyField(text, field)" />
32
- </template>
33
- <template v-else>
34
- <markdown-render
35
- v-if="props.sender === 'bot'"
36
- class="q-chatbot__text"
37
- :source="props.message || ''" />
38
- <div
39
- v-else
40
- class="q-chatbot__text">
41
- {{ props.message }}
42
- </div>
43
- </template>
44
- </div>
45
- </div>
46
-
47
- <chat-bot-message-buttons
48
- :showButtons="isBotMessageAndNotDefault"
49
- :loading="props.loading"
50
- :date-format="props.dateFormat"
51
- @copy-response="copyResponse"
52
- @submit-feedback="onSubmitFeedback"
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
- // Utils
59
- import { computed } from 'vue'
60
- import { QIcon } from '@quidgest/ui/components'
61
-
62
- // Types
63
- import type { ChatBotMessageProps } from './'
64
- import { AppliedFieldData, FieldData } from '../ChatBot/types'
65
-
66
- // Composables
67
- import { useTexts } from '@/composables/useTexts'
68
- import { useChatApi } from '@/composables/useChatApi'
69
-
70
- // Components
71
- import { MarkdownRender } from '../MarkdownRender'
72
- import { FieldPreview } from '../FieldPreview'
73
- import { ChatBotMessageButtons } from './'
74
- import PulseDots from '@/components/PulseDots/PulseDots.vue'
75
- import { parseFieldValue } from '@/utils/helper'
76
-
77
- const emits = defineEmits<{
78
- (e: 'apply-fields', fields: AppliedFieldData[]): void
79
- }>()
80
-
81
- const texts = useTexts()
82
-
83
- const props = withDefaults(defineProps<ChatBotMessageProps>(), {
84
- sender: 'user',
85
- userImage: undefined,
86
- date: () => new Date(),
87
- fields: () => []
88
- })
89
-
90
- const { handleFeedback } = useChatApi(props.apiEndpoint)
91
-
92
- const isBotMessageAndNotDefault = computed(() => {
93
- return (
94
- props.sender === 'bot' &&
95
- !Object.values(texts || {}).includes(props.message || '') &&
96
- !props.isWelcomeMessage
97
- )
98
- })
99
-
100
- const messageImage = computed(() =>
101
- props.sender === 'bot' ? props.chatbotImage : props.userImage
102
- )
103
-
104
- function copyResponse() {
105
- if (!props.message) return
106
-
107
- navigator.clipboard
108
- .writeText(props.message)
109
- .then(() => {
110
- console.log('Response copied to clipboard')
111
- })
112
- .catch((err) => {
113
- console.error('Failed to copy response: ', err)
114
- })
115
- }
116
-
117
- function applyField(text: unknown, field: FieldData) {
118
- emits('apply-fields', [{ name: field.name, text }])
119
- }
120
-
121
- function applyAllFields() {
122
- if (!props.fields || props.fields.length === 0) return
123
-
124
- const fieldsToApply = props.fields.map((field) => {
125
- return {
126
- name: field.name,
127
- text: parseFieldValue(field.type, field.text)
128
- }
129
- })
130
-
131
- emits('apply-fields', fieldsToApply)
132
- }
133
-
134
- function onSubmitFeedback(feedback: number, comment: string) {
135
- if (feedback === null || comment === null || !props.sessionID) return
136
-
137
- handleFeedback(feedback, comment, props.sessionID)
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>