@quidgest/chatbot 0.5.1 → 0.5.3

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