@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.
- package/README.md +1 -2
- package/dist/components/ChatBot/types.d.ts +3 -1
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +3 -3
- package/dist/components/ChatBotInput/__tests__/ChatBotInput.spec.d.ts +1 -0
- package/dist/components/ChatBotInput/index.d.ts +2 -2
- package/dist/components/ChatBotInput/types.d.ts +4 -4
- package/dist/components/ChatBotMessage/__tests__/ChatBotMessage.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.d.ts +1 -0
- package/dist/components/ChatBotMessage/types.d.ts +3 -2
- package/dist/components/ChatToolBar/__tests__/ChatToolBar.spec.d.ts +1 -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/useChatMessages.d.ts +2 -1
- package/dist/composables/useSSE.d.ts +1 -2
- package/dist/composables/useTexts.d.ts +2 -0
- package/dist/index.js +16 -16
- package/dist/index.mjs +2924 -1770
- 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 +27 -5
- package/src/assets/styles/preview-file.scss +70 -0
- package/src/assets/styles/styles.scss +190 -222
- package/src/components/ChatBot/ChatBot.vue +345 -368
- package/src/components/ChatBot/types.ts +35 -33
- package/src/components/ChatBotInput/ChatBotInput.vue +188 -190
- package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +279 -0
- package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +25 -0
- package/src/components/ChatBotInput/index.ts +2 -2
- package/src/components/ChatBotInput/types.ts +25 -25
- package/src/components/ChatBotMessage/ChatBotMessage.vue +159 -134
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +179 -164
- package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +256 -0
- package/src/components/ChatBotMessage/__tests__/ChatBotMessageButtons.spec.ts +199 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +35 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap +25 -0
- package/src/components/ChatBotMessage/types.ts +54 -53
- package/src/components/ChatToolBar/ChatToolBar.vue +68 -64
- package/src/components/ChatToolBar/__tests__/ChatToolBar.spec.ts +118 -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 +56 -58
- package/src/components/FieldPreview/__tests__/FieldPreview.spec.ts +72 -0
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +25 -0
- package/src/components/FieldPreview/field-preview.scss +26 -26
- package/src/components/FieldPreview/types.ts +5 -5
- package/src/components/MarkdownRender/MarkdownRender.vue +15 -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 +51 -0
- package/src/composables/__tests__/useSSE.spec.ts +132 -0
- package/src/composables/useChatApi.ts +128 -134
- package/src/composables/useChatMessages.ts +46 -48
- package/src/composables/useSSE.ts +75 -76
- package/src/composables/useTexts.ts +30 -30
- package/src/test/setup.ts +36 -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,199 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import { ChatBotMessageButtons } from '..'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { describe, expect, it, beforeEach, vi, afterEach } from 'vitest'
|
|
6
|
+
import { mount } from '@vue/test-utils'
|
|
7
|
+
import { useChatMessages } from '@/composables/useChatMessages'
|
|
8
|
+
|
|
9
|
+
// Types
|
|
10
|
+
import type { ChatBotMessageButtonsProps } from '..'
|
|
11
|
+
|
|
12
|
+
describe('ChatBotMessageButtons', () => {
|
|
13
|
+
const fixedDate = new Date('2025-01-01T11:47:00')
|
|
14
|
+
const { addChatMessage, clearMessages } = useChatMessages()
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
clearMessages()
|
|
18
|
+
|
|
19
|
+
vi.useFakeTimers()
|
|
20
|
+
vi.setSystemTime(fixedDate)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
vi.useRealTimers()
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const props: ChatBotMessageButtonsProps = {
|
|
28
|
+
loading: false,
|
|
29
|
+
showButtons: true,
|
|
30
|
+
dateFormat: 'MM/DD/YYYY',
|
|
31
|
+
date: fixedDate
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
it('renders correctly with default props', () => {
|
|
35
|
+
const wrapper = mount(ChatBotMessageButtons, { props })
|
|
36
|
+
|
|
37
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('does not render buttons when showButtons is false', () => {
|
|
41
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
42
|
+
props: { ...props, showButtons: false }
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const buttons = wrapper.find('.q-chatbot__feedback-buttons')
|
|
46
|
+
expect(buttons.exists()).toBe(false)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('sets loading state correctly to all buttons', () => {
|
|
50
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
51
|
+
props: { ...props, loading: true }
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const buttons = wrapper.findAll('q.-chatbot__feedback-buttons button')
|
|
55
|
+
|
|
56
|
+
buttons.forEach((button) => {
|
|
57
|
+
expect(button.attributes('disabled')).toBeDefined()
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('sets the hour date on the bottom of the component', () => {
|
|
62
|
+
const testDate = new Date('2024-01-01T12:00:00Z')
|
|
63
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
64
|
+
props: {
|
|
65
|
+
...props,
|
|
66
|
+
date: testDate
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
const dateElement = wrapper.find('.q-chatbot__sender')
|
|
71
|
+
expect(dateElement.text()).toBe('12:00')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('emits the copy event when copy button is clicked', async () => {
|
|
75
|
+
const wrapper = mount(ChatBotMessageButtons, { props })
|
|
76
|
+
const copyButton = wrapper.find('.q-chatbot__copy-button')
|
|
77
|
+
|
|
78
|
+
await copyButton.trigger('click')
|
|
79
|
+
|
|
80
|
+
expect(wrapper.emitted()['copy-response']).toBeTruthy()
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('emits the apply-all event when apply all button is clicked', async () => {
|
|
84
|
+
// Add one message with fields to enable the button
|
|
85
|
+
const message = addChatMessage('Test message 1', 'bot')
|
|
86
|
+
|
|
87
|
+
message.fields = [
|
|
88
|
+
{ name: 'field1', text: 'value1', type: 'text' },
|
|
89
|
+
{ name: 'field2', text: 'value2', type: 'text' }
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
93
|
+
props: { ...props, showButtons: true }
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const applyAllButton = wrapper.find('.q-chatbot__apply-all-button')
|
|
97
|
+
await applyAllButton.trigger('click')
|
|
98
|
+
|
|
99
|
+
expect(wrapper.emitted()['apply-all']).toBeTruthy()
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('does not emit apply-all if the button has already been clicked', async () => {
|
|
103
|
+
// Add one message with fields to enable the button
|
|
104
|
+
const message = addChatMessage('Test message 1', 'bot')
|
|
105
|
+
|
|
106
|
+
message.fields = [
|
|
107
|
+
{ name: 'field1', text: 'value1', type: 'text' },
|
|
108
|
+
{ name: 'field2', text: 'value2', type: 'text' }
|
|
109
|
+
]
|
|
110
|
+
|
|
111
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
112
|
+
props: { ...props, showButtons: true }
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const applyAllButton = wrapper.find('.q-chatbot__apply-all-button')
|
|
116
|
+
await applyAllButton.trigger('click')
|
|
117
|
+
await applyAllButton.trigger('click') // Click again
|
|
118
|
+
|
|
119
|
+
expect(wrapper.emitted()['apply-all']).toBeTruthy()
|
|
120
|
+
expect(wrapper.emitted()['apply-all'].length).toBe(1) // Should only be emitted once
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
it('does not render apply all button if there is no message, and the message does not have fields', () => {
|
|
124
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
125
|
+
props: { ...props, showButtons: true }
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
const applyAllButton = wrapper.find('.q-chatbot__apply-all-button')
|
|
129
|
+
expect(applyAllButton.exists()).toBe(false)
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
it('opens the dialog when clicking the thumbs up button', async () => {
|
|
133
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
134
|
+
props: { ...props, showButtons: true }
|
|
135
|
+
})
|
|
136
|
+
const goodResponseButton = wrapper.find('.q-chatbot__good-response-button')
|
|
137
|
+
await goodResponseButton.trigger('click')
|
|
138
|
+
|
|
139
|
+
const dialog = wrapper.findComponent({ name: 'QDialog' })
|
|
140
|
+
expect(dialog.exists()).toBe(true)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('opens the dialog when clicking the thumbs down button', async () => {
|
|
144
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
145
|
+
props: { ...props, showButtons: true }
|
|
146
|
+
})
|
|
147
|
+
const badResponseButton = wrapper.find('.q-chatbot__bad-response-button')
|
|
148
|
+
await badResponseButton.trigger('click')
|
|
149
|
+
|
|
150
|
+
const dialog = wrapper.findComponent({ name: 'QDialog' })
|
|
151
|
+
expect(dialog.exists()).toBe(true)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('emits submit-feedback event with comment when submitting feedback', async () => {
|
|
155
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
156
|
+
props: { ...props, showButtons: true }
|
|
157
|
+
})
|
|
158
|
+
const goodResponseButton = wrapper.find('.q-chatbot__good-response-button')
|
|
159
|
+
await goodResponseButton.trigger('click')
|
|
160
|
+
|
|
161
|
+
const dialog = wrapper.findComponent({ name: 'QDialog' })
|
|
162
|
+
expect(dialog.exists()).toBe(true)
|
|
163
|
+
|
|
164
|
+
const commentInput = dialog.find('input')
|
|
165
|
+
expect(commentInput.exists()).toBe(true)
|
|
166
|
+
await commentInput.setValue('Great response!')
|
|
167
|
+
|
|
168
|
+
const overlay = wrapper.find('.q-overlay__content')
|
|
169
|
+
const submitButton = overlay.find('.q-chatbot__dialog-confirm-button')
|
|
170
|
+
|
|
171
|
+
expect(submitButton.exists()).toBe(true)
|
|
172
|
+
await submitButton.trigger('click')
|
|
173
|
+
expect(wrapper.emitted()['submit-feedback']).toBeTruthy()
|
|
174
|
+
expect(wrapper.emitted()['submit-feedback'][0]).toEqual([1, 'Great response!'])
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
it('uses a default date if no date prop is provided', () => {
|
|
178
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
179
|
+
props: { ...props, date: undefined }
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const dateElement = wrapper.find('.q-chatbot__sender')
|
|
183
|
+
|
|
184
|
+
// The default date is the current date, so we just check if it renders something
|
|
185
|
+
expect(dateElement.text().length).toBeGreaterThan(0)
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
it('uses the current data format if no dateFormat prop is provided', () => {
|
|
189
|
+
const wrapper = mount(ChatBotMessageButtons, {
|
|
190
|
+
props: { ...props, dateFormat: '' }
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
const dateElement = wrapper.find('.q-chatbot__sender')
|
|
194
|
+
const expectedFormat = fixedDate.toLocaleString()
|
|
195
|
+
|
|
196
|
+
// The date should be in the current locale format
|
|
197
|
+
expect(dateElement.text()).toBe(expectedFormat)
|
|
198
|
+
})
|
|
199
|
+
})
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChatBotMessage > renders the component with default props 1`] = `
|
|
4
|
+
"<div class="q-chatbot__message-container"><span data-test="" type="img" alt="Sender Image" class="q-chatbot__profile"></span>
|
|
5
|
+
<div class="q-chatbot__message-wrapper">
|
|
6
|
+
<!--v-if-->
|
|
7
|
+
<div class="q-chatbot__message">
|
|
8
|
+
<div class="markdown-renderer q-chatbot__text">
|
|
9
|
+
<p>Hello, this is a test message.</p>
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
<!--teleport start-->
|
|
14
|
+
<transition-stub name="fade" appear="true" persisted="false" css="true">
|
|
15
|
+
<!--v-if-->
|
|
16
|
+
</transition-stub>
|
|
17
|
+
<transition-stub name="fade" appear="true" persisted="false" css="true">
|
|
18
|
+
<!--v-if-->
|
|
19
|
+
</transition-stub>
|
|
20
|
+
<!--teleport end-->
|
|
21
|
+
<!--v-if-->
|
|
22
|
+
<div class="q-chatbot__feedback-buttons">
|
|
23
|
+
<div class="q-button-group" role="group"><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__good-response-button" title="Good response">
|
|
24
|
+
<!--v-if--><span class="q-button__content"><span data-test="thumb-up"></span> </span>
|
|
25
|
+
</button><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__bad-response-button" title="Bad response">
|
|
26
|
+
<!--v-if--><span class="q-button__content"><span data-test="thumb-down"></span> </span>
|
|
27
|
+
</button><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__copy-button" title="Copy response">
|
|
28
|
+
<!--v-if--><span class="q-button__content"><span data-test="copy-content"></span> </span>
|
|
29
|
+
</button>
|
|
30
|
+
<!--v-if-->
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="q-chatbot__sender">11:47</div>
|
|
34
|
+
</div>"
|
|
35
|
+
`;
|
package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessageButtons.spec.ts.snap
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChatBotMessageButtons > renders correctly with default props 1`] = `
|
|
4
|
+
"<!--teleport start-->
|
|
5
|
+
<transition-stub name="fade" appear="true" persisted="false" css="true">
|
|
6
|
+
<!--v-if-->
|
|
7
|
+
</transition-stub>
|
|
8
|
+
<transition-stub name="fade" appear="true" persisted="false" css="true">
|
|
9
|
+
<!--v-if-->
|
|
10
|
+
</transition-stub>
|
|
11
|
+
<!--teleport end-->
|
|
12
|
+
<!--v-if-->
|
|
13
|
+
<div class="q-chatbot__feedback-buttons">
|
|
14
|
+
<div class="q-button-group" role="group"><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__good-response-button" title="Good response">
|
|
15
|
+
<!--v-if--><span class="q-button__content"><span data-test="thumb-up"></span> </span>
|
|
16
|
+
</button><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__bad-response-button" title="Bad response">
|
|
17
|
+
<!--v-if--><span class="q-button__content"><span data-test="thumb-down"></span> </span>
|
|
18
|
+
</button><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__copy-button" title="Copy response">
|
|
19
|
+
<!--v-if--><span class="q-button__content"><span data-test="copy-content"></span> </span>
|
|
20
|
+
</button>
|
|
21
|
+
<!--v-if-->
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="q-chatbot__sender">11:47</div>"
|
|
25
|
+
`;
|
|
@@ -1,70 +1,71 @@
|
|
|
1
|
-
import { ChatBotMessageSender, FieldData } from '@/components/ChatBot/types'
|
|
1
|
+
import type { ChatBotMessageSender, FieldData } from '@/components/ChatBot/types'
|
|
2
|
+
import type { ChatBotFile } from '../ChatBotInput'
|
|
2
3
|
|
|
3
4
|
export type ChatBotMessageProps = {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
/*
|
|
6
|
+
* Sender of the message
|
|
7
|
+
*/
|
|
8
|
+
sender?: ChatBotMessageSender
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
/*
|
|
11
|
+
* Message to be displayed
|
|
12
|
+
*/
|
|
13
|
+
message?: string
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
/*
|
|
16
|
+
* Date of when the message was sent
|
|
17
|
+
*/
|
|
18
|
+
date?: Date
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
/*
|
|
21
|
+
* If the message is loading
|
|
22
|
+
*/
|
|
23
|
+
loading?: boolean
|
|
23
24
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Project locale
|
|
27
|
+
*/
|
|
28
|
+
dateFormat: string
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
/**
|
|
31
|
+
* File Preview
|
|
32
|
+
*/
|
|
33
|
+
file?: ChatBotFile
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Default api endpoint
|
|
37
|
+
*/
|
|
38
|
+
apiEndpoint: string
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Session ID
|
|
42
|
+
*/
|
|
43
|
+
sessionID?: string
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
/**
|
|
46
|
+
* User image
|
|
47
|
+
*/
|
|
48
|
+
userImage: string
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Chatbot image
|
|
52
|
+
*/
|
|
53
|
+
chatbotImage: string
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Flag to mark welcome messages
|
|
57
|
+
*/
|
|
58
|
+
isWelcomeMessage?: boolean
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
/**
|
|
61
|
+
* Additional fields for the message
|
|
62
|
+
*/
|
|
63
|
+
fields?: FieldData[]
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
export type ChatBotMessageButtonsProps = {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
loading: boolean
|
|
68
|
+
showButtons: boolean
|
|
69
|
+
dateFormat: string
|
|
70
|
+
date?: Date
|
|
70
71
|
}
|
|
@@ -1,82 +1,86 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
<div class="q-chatbot__tools">
|
|
3
|
+
<div class="q-chatbot__tools__select">
|
|
4
|
+
<q-select
|
|
5
|
+
v-if="hasAgents"
|
|
6
|
+
v-model="selectedChat"
|
|
7
|
+
class="q-chatbot__tools-select-input"
|
|
8
|
+
size="medium"
|
|
9
|
+
:items="availableChats"
|
|
10
|
+
@update:model-value="changeChat" />
|
|
11
|
+
</div>
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
<q-button
|
|
14
|
+
:title="clearChat"
|
|
15
|
+
:disabled="props.disabled"
|
|
16
|
+
class="q-chatbot__tools-clear"
|
|
17
|
+
borderless
|
|
18
|
+
@click="clear">
|
|
19
|
+
<q-icon icon="bin" />
|
|
20
|
+
</q-button>
|
|
21
|
+
</div>
|
|
20
22
|
</template>
|
|
21
23
|
|
|
22
24
|
<script setup lang="ts">
|
|
23
|
-
|
|
24
|
-
import { ChatToolBarProps } from './types'
|
|
25
|
-
import { computed, ref, watch } from 'vue'
|
|
25
|
+
import { QButton, QSelect, QIcon } from '@quidgest/ui/components'
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
import { useTexts } from '@/composables/useTexts'
|
|
28
|
+
import { ChatToolBarProps } from './types'
|
|
29
|
+
import { computed, ref, watch } from 'vue'
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
formId: ''
|
|
33
|
-
}
|
|
31
|
+
const props = withDefaults(defineProps<ChatToolBarProps>(), {
|
|
32
|
+
availableAgents: () => []
|
|
33
|
+
})
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
const emit = defineEmits<{
|
|
36
|
+
(e: 'clear'): void
|
|
37
|
+
(e: 'change-chat', value: { key: string; formId: string }): void
|
|
38
|
+
}>()
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
(e: 'clear'): void
|
|
41
|
-
(e: 'change-chat', value: { key: string; formId: string }): void
|
|
42
|
-
}>()
|
|
40
|
+
const { clearChat } = useTexts()
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
const defaultChat = {
|
|
43
|
+
key: '',
|
|
44
|
+
value: 'Default Chat',
|
|
45
|
+
formId: ''
|
|
46
|
+
}
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
(newValue) => {
|
|
52
|
-
selectedChat.value = newValue || defaultChat.key
|
|
53
|
-
}
|
|
54
|
-
)
|
|
48
|
+
const hasAgents = computed(() => {
|
|
49
|
+
return props.availableAgents && props.availableAgents.length > 0
|
|
50
|
+
})
|
|
55
51
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
52
|
+
const selectedChat = ref(props.selectedAgentKey || defaultChat.key)
|
|
53
|
+
watch(
|
|
54
|
+
() => props.selectedAgentKey,
|
|
55
|
+
(newValue) => {
|
|
56
|
+
selectedChat.value = newValue || defaultChat.key
|
|
57
|
+
}
|
|
58
|
+
)
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
60
|
+
const availableChats = computed(() => {
|
|
61
|
+
const chats = props.availableAgents?.map((agent) => ({
|
|
62
|
+
key: agent.key,
|
|
63
|
+
value: agent.value,
|
|
64
|
+
formId: agent.formId
|
|
65
|
+
}))
|
|
66
66
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
67
|
+
chats?.push(defaultChat)
|
|
68
|
+
return chats || [defaultChat]
|
|
69
|
+
})
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
function clear() {
|
|
72
|
+
emit('clear')
|
|
73
|
+
}
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
75
|
+
function changeChat(value: string) {
|
|
76
|
+
const item = availableChats.value.find((chat) => chat.key === value)
|
|
77
|
+
if (!item) return
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
79
|
+
if (item.key === defaultChat.key) {
|
|
80
|
+
emit('change-chat', { key: '', formId: '' })
|
|
81
|
+
return
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
emit('change-chat', item)
|
|
85
|
+
}
|
|
82
86
|
</script>
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import { ChatToolBar } from '..'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { describe, it, expect } from 'vitest'
|
|
6
|
+
import { mount } from '@vue/test-utils'
|
|
7
|
+
|
|
8
|
+
// Types
|
|
9
|
+
import type { ChatToolBarProps } from '../'
|
|
10
|
+
|
|
11
|
+
describe('ChatToolBar', () => {
|
|
12
|
+
const props: ChatToolBarProps = {
|
|
13
|
+
disabled: false,
|
|
14
|
+
availableAgents: [],
|
|
15
|
+
selectedAgentKey: ''
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
it('should render the component', () => {
|
|
19
|
+
const wrapper = mount(ChatToolBar, { props })
|
|
20
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should emit clear event when clear button is clicked', async () => {
|
|
24
|
+
const wrapper = mount(ChatToolBar, { props })
|
|
25
|
+
const button = wrapper.find('.q-chatbot__tools-clear')
|
|
26
|
+
|
|
27
|
+
await button.trigger('click')
|
|
28
|
+
expect(wrapper.emitted()['clear']).toBeTruthy()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('should disable the clear button when disabled prop is true', () => {
|
|
32
|
+
const wrapper = mount(ChatToolBar, { props: { ...props, disabled: true } })
|
|
33
|
+
const button = wrapper.find<HTMLButtonElement>('.q-chatbot__tools-clear')
|
|
34
|
+
expect(button.element.disabled).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should not render the select when no agents are provided', () => {
|
|
38
|
+
const wrapper = mount(ChatToolBar, { props })
|
|
39
|
+
expect(wrapper.find('.q-chatbot__tools-select-input').exists()).toBe(false)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('should render the select when agents are provided', () => {
|
|
43
|
+
const wrapper = mount(ChatToolBar, {
|
|
44
|
+
props: {
|
|
45
|
+
...props,
|
|
46
|
+
availableAgents: [{ key: 'agent1', value: 'Agent 1', formId: 'key' }]
|
|
47
|
+
}
|
|
48
|
+
})
|
|
49
|
+
expect(wrapper.find('.q-chatbot__tools-select-input').exists()).toBe(true)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should emit change-agent event when a new agent is selected', async () => {
|
|
53
|
+
const wrapper = mount(ChatToolBar, {
|
|
54
|
+
props: {
|
|
55
|
+
...props,
|
|
56
|
+
availableAgents: [
|
|
57
|
+
{ key: 'agent1', value: 'Agent 1', formId: 'key' },
|
|
58
|
+
{ key: 'agent2', value: 'Agent 2', formId: 'key' }
|
|
59
|
+
],
|
|
60
|
+
selectedAgentKey: 'agent1'
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const select = wrapper.findComponent({ name: 'QSelect' })
|
|
65
|
+
// Simulate selecting a new agent
|
|
66
|
+
await select.vm.$emit('update:modelValue', 'agent2')
|
|
67
|
+
|
|
68
|
+
expect(wrapper.emitted()['change-chat']).toBeTruthy()
|
|
69
|
+
expect(wrapper.emitted()['change-chat']?.[0]).toEqual([
|
|
70
|
+
{
|
|
71
|
+
key: 'agent2',
|
|
72
|
+
formId: 'key',
|
|
73
|
+
value: 'Agent 2'
|
|
74
|
+
}
|
|
75
|
+
])
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should not emit change-agent if it receive a non-existing agent key', async () => {
|
|
79
|
+
const wrapper = mount(ChatToolBar, {
|
|
80
|
+
props: {
|
|
81
|
+
...props,
|
|
82
|
+
availableAgents: [
|
|
83
|
+
{ key: 'agent1', value: 'Agent 1', formId: 'key' },
|
|
84
|
+
{ key: 'agent2', value: 'Agent 2', formId: 'key' }
|
|
85
|
+
],
|
|
86
|
+
selectedAgentKey: 'agent1'
|
|
87
|
+
}
|
|
88
|
+
})
|
|
89
|
+
const select = wrapper.findComponent({ name: 'QSelect' })
|
|
90
|
+
// Simulate selecting a new agent
|
|
91
|
+
await select.vm.$emit('update:modelValue', 'agent3')
|
|
92
|
+
expect(wrapper.emitted()['change-chat']).toBeFalsy()
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('shoudld emit change-agent when selecting the default option', async () => {
|
|
96
|
+
const wrapper = mount(ChatToolBar, {
|
|
97
|
+
props: {
|
|
98
|
+
...props,
|
|
99
|
+
availableAgents: [
|
|
100
|
+
{ key: 'agent1', value: 'Agent 1', formId: 'key' },
|
|
101
|
+
{ key: 'agent2', value: 'Agent 2', formId: 'key' }
|
|
102
|
+
],
|
|
103
|
+
selectedAgentKey: 'agent1'
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
const select = wrapper.findComponent({ name: 'QSelect' })
|
|
107
|
+
// Simulate selecting a new agent
|
|
108
|
+
await select.vm.$emit('update:modelValue', '')
|
|
109
|
+
|
|
110
|
+
expect(wrapper.emitted()['change-chat']).toBeTruthy()
|
|
111
|
+
expect(wrapper.emitted()['change-chat']?.[0]).toEqual([
|
|
112
|
+
{
|
|
113
|
+
key: '',
|
|
114
|
+
formId: ''
|
|
115
|
+
}
|
|
116
|
+
])
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChatToolBar > should render the component 1`] = `
|
|
4
|
+
"<div class="q-chatbot__tools">
|
|
5
|
+
<div class="q-chatbot__tools__select">
|
|
6
|
+
<!--v-if-->
|
|
7
|
+
</div><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__tools-clear" title="Clear chat">
|
|
8
|
+
<!--v-if--><span class="q-button__content"><span data-test="bin"></span> </span>
|
|
9
|
+
</button>
|
|
10
|
+
</div>"
|
|
11
|
+
`;
|