@quidgest/chatbot 0.5.3-dev.0 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ChatBot/__tests__/ChatBot.spec.d.ts +1 -0
- package/dist/components/ChatBot/types.d.ts +3 -1
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +3 -3
- 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/types.d.ts +3 -2
- package/dist/composables/useChatMessages.d.ts +2 -1
- package/dist/composables/useTexts.d.ts +1 -0
- package/dist/index.js +18 -18
- package/dist/index.mjs +1200 -1165
- package/dist/style.css +1 -1
- package/package.json +2 -1
- package/src/assets/styles/preview-file.scss +70 -0
- package/src/assets/styles/styles.scss +9 -33
- package/src/components/ChatBot/ChatBot.vue +10 -14
- package/src/components/ChatBot/__tests__/ChatBot.spec.ts +52 -0
- package/src/components/ChatBot/__tests__/__snapshots__/ChatBot.spec.ts.snap +39 -0
- package/src/components/ChatBot/types.ts +3 -1
- package/src/components/ChatBotInput/ChatBotInput.vue +81 -74
- package/src/components/ChatBotInput/__tests__/ChatBotInput.spec.ts +29 -42
- package/src/components/ChatBotInput/__tests__/__snapshots__/ChatBotInput.spec.ts.snap +5 -5
- package/src/components/ChatBotInput/index.ts +2 -2
- package/src/components/ChatBotInput/types.ts +4 -4
- package/src/components/ChatBotMessage/ChatBotMessage.vue +31 -3
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +1 -0
- package/src/components/ChatBotMessage/__tests__/ChatBotMessage.spec.ts +260 -0
- package/src/components/ChatBotMessage/__tests__/__snapshots__/ChatBotMessage.spec.ts.snap +35 -0
- package/src/components/ChatBotMessage/types.ts +4 -3
- package/src/components/FieldPreview/__tests__/__snapshots__/FieldPreview.spec.ts.snap +1 -1
- package/src/composables/useChatMessages.ts +3 -2
- package/src/composables/useTexts.ts +2 -1
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.markdown-renderer pre,.markdown-renderer code{white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:auto}.markdown-renderer pre{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.markdown-renderer code{padding:.2rem .4rem;border-radius:4px;font-size:.875rem}.q-field-preview{position:relative;display:flex;flex-direction:column;margin:1rem .25rem}.q-field-preview__toolbar{z-index:1;display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.1rem .2rem}.q-field-preview__content{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.q-field-preview__content.preserve-whitespace{white-space:pre-wrap}.q-field-preview__footer{display:flex;flex-direction:row;margin-top:.25rem}.q-field-preview:first-child{margin:0 1rem .25rem .25rem}.pulsing-dots{display:flex;align-items:center;justify-content:center;flex-direction:row;gap:.25rem}.generating-text{font-size:.9rem;color:var(--q-theme-primary)}.dots-container{display:flex;align-items:center;gap:.1rem}.dot{font-size:16px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}.q-chatbot{width:100%;height:100%;display:flex;flex-direction:column}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__text p{margin:0}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;flex-direction:column;gap:.75rem;overflow:hidden}.q-chatbot__footer-container{padding:.8rem 0 0}.q-chatbot__input-wrapper{display:flex;flex-direction:column;position:relative}.q-
|
|
1
|
+
.markdown-renderer pre,.markdown-renderer code{white-space:pre-wrap;overflow-wrap:anywhere;overflow-x:auto}.markdown-renderer pre{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.markdown-renderer code{padding:.2rem .4rem;border-radius:4px;font-size:.875rem}.q-field-preview{position:relative;display:flex;flex-direction:column;margin:1rem .25rem}.q-field-preview__toolbar{z-index:1;display:flex;flex-direction:row;align-items:center;justify-content:space-between;padding:.1rem .2rem}.q-field-preview__content{position:relative;background-color:#f5f5f5;border:1px solid #e0e0e0;border-radius:6px;padding:.75rem 1rem;font-size:.875rem}.q-field-preview__content.preserve-whitespace{white-space:pre-wrap}.q-field-preview__footer{display:flex;flex-direction:row;margin-top:.25rem}.q-field-preview:first-child{margin:0 1rem .25rem .25rem}.pulsing-dots{display:flex;align-items:center;justify-content:center;flex-direction:row;gap:.25rem}.generating-text{font-size:.9rem;color:var(--q-theme-primary)}.dots-container{display:flex;align-items:center;gap:.1rem}.dot{font-size:16px;line-height:1;animation:pulse 1s infinite;color:var(--q-theme-primary)}@keyframes pulse{0%,to{transform:scale(.8);opacity:.6}50%{transform:scale(1);opacity:1}}.q-chatbot__file-preview img,.q-chatbot__image-preview img{width:60px;height:60px;object-fit:cover;border-radius:4px;margin-right:.25rem;border:1px solid #eaebec;overflow:hidden}.q-chatbot__file-preview{display:inline-flex;align-items:center;position:relative;margin-top:.5rem;gap:.25rem;width:fit-content}.q-chatbot__file-preview img:focus{outline:solid rgb(var(--q-theme-info-rgb)/50%)}.q-chatbot__file-preview-container{display:flex;border-radius:.5rem;padding:.25rem .5rem;max-width:320px;align-items:center;justify-content:center;border:1px solid var(--q-theme-primary-light)}.q-chatbot__file-icon-container{display:flex;align-items:center;justify-content:center;border-radius:8px;width:36px;height:36px;flex-shrink:0;margin-right:10px;background:var(--q-theme-primary-light)}.q-chatbot__file-icon-container .q-icon{width:20px;height:20px}.q-chatbot__file-info{display:flex;flex-direction:column;overflow:hidden}.q-chatbot__file-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.q-chatbot__file-extension{font-size:10px}.q-chatbot{width:100%;height:100%;display:flex;flex-direction:column}.q-chatbot input{line-height:1.5rem}.q-chatbot .q-input-group .i-text__field{border-radius:0;flex:1}.q-chatbot__remove-file{position:absolute;top:-8px;right:-8px}.q-chatbot__text p{margin:0}.q-chatbot__content{background-color:#fff;border:1px solid #eaebec;height:100%;width:100%;display:flex;flex-direction:column;gap:.75rem;overflow:hidden}.q-chatbot__footer-container{padding:.8rem 0 0}.q-chatbot__input-wrapper{display:flex;flex-direction:column;position:relative}.q-chatbot .q-button.q-chatbot__remove-file{position:absolute;top:-5px;right:-5px;background-color:#00000080;color:#fff;border-radius:50%;padding:5px;font-size:10px;border:none}.q-chatbot .q-button.q-chatbot__remove-file:hover,.q-chatbot .q-button.q-chatbot__remove-file:focus{opacity:1;pointer-events:auto}.q-chatbot__send-container{padding-bottom:.25rem;display:flex;justify-content:space-between;width:100%}.q-chatbot__send-container .q-chatbot__send,.q-chatbot__send-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__send-container .spacer{flex-grow:1}.q-chatbot__footer{position:sticky;padding:0 .5rem;border:1px solid #eaebec;border-radius:.25rem;bottom:0;width:100%;display:flex;flex-direction:column;gap:.25rem}.q-chatbot__footer-disabled{background-color:rgb(var(--q-theme-neutral-light-rgb)/25%);cursor:not-allowed}.q-chatbot__footer.drag-over{border:2px dashed rgb(var(--q-theme-primary-rgb)/25%);background-color:#018bd20d}.q-chatbot__footer .q-chatbot__input{min-height:50px;max-height:100px;border-bottom:1px solid #eaebec;overflow-y:auto}.q-chatbot__footer .q-text-area{max-height:100%;overflow-y:auto}.q-chatbot__footer .q-text-area .q-field__control{border:none}.q-chatbot__upload-container{display:flex;justify-content:flex-start;padding:.25rem 0}.q-chatbot__upload-container .q-chatbot__upload{border-radius:1rem}.q-chatbot__messages-container{display:flex;flex-direction:column;flex-grow:1;padding:0 1rem 2rem;gap:1.5rem;overflow-y:auto}.q-chatbot__messages-wrapper{display:flex;max-width:100%;gap:.2rem}.q-chatbot__tools{display:flex;flex-direction:row;justify-content:space-between;max-width:100%;padding:.25rem .5rem}.q-chatbot__message-wrapper{display:flex;flex-direction:column;gap:.2rem}.q-chatbot__message-container{display:flex;flex-direction:column;gap:.25rem}.q-chatbot__messages-wrapper_right{justify-content:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-container{align-items:flex-end}.q-chatbot__messages-wrapper_right .q-chatbot__message-wrapper{display:flex;align-items:flex-end}.q-chatbot__profile.q-icon__img{border-radius:50%;height:2rem;width:2rem}.q-chatbot__message{padding:.3rem .5rem;background-color:#eaebec;width:fit-content;min-height:2rem;white-space:normal;border-radius:0 .5rem .5rem}.q-chatbot__messages-wrapper_right .q-chatbot__message{background-color:#018bd233;border-radius:.5rem 0 .5rem .5rem;white-space:normal}.q-chatbot__sender{white-space:nowrap;color:#7c858d;font-size:.7rem}.q-chatbot__retry-button{align-items:center;display:flex}.q-chatbot__dialog-title{margin:.5rem 0}.hidden-input{display:none}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quidgest/chatbot",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.4",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "UNLICENSED",
|
|
7
7
|
"main": "dist/index.cjs",
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"prettier": "^3.3.3",
|
|
58
58
|
"rimraf": "^5.0.10",
|
|
59
59
|
"sass": "^1.77.8",
|
|
60
|
+
"semver": "^7.7.2",
|
|
60
61
|
"stylelint": "^16.23.1",
|
|
61
62
|
"stylelint-config-standard-scss": "^15.0.1",
|
|
62
63
|
"typescript": "^5.5.4",
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
.q-chatbot__file-preview img,
|
|
2
|
+
.q-chatbot__image-preview img {
|
|
3
|
+
width: 60px;
|
|
4
|
+
height: 60px;
|
|
5
|
+
object-fit: cover;
|
|
6
|
+
border-radius: 4px;
|
|
7
|
+
margin-right: 0.25rem;
|
|
8
|
+
border: 1px solid #eaebec;
|
|
9
|
+
overflow: hidden;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.q-chatbot__file {
|
|
13
|
+
&-preview {
|
|
14
|
+
display: inline-flex;
|
|
15
|
+
align-items: center;
|
|
16
|
+
position: relative;
|
|
17
|
+
margin-top: 0.5rem;
|
|
18
|
+
gap: 0.25rem;
|
|
19
|
+
width: fit-content;
|
|
20
|
+
|
|
21
|
+
img:focus {
|
|
22
|
+
outline: solid rgb(var(--q-theme-info-rgb) / 50%);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
&-container {
|
|
26
|
+
display: flex;
|
|
27
|
+
border-radius: 0.5rem;
|
|
28
|
+
padding: 0.25rem 0.5rem;
|
|
29
|
+
max-width: 320px;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
border: 1px solid var(--q-theme-primary-light);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&-icon-container {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: center;
|
|
39
|
+
justify-content: center;
|
|
40
|
+
border-radius: 8px;
|
|
41
|
+
width: 36px;
|
|
42
|
+
height: 36px;
|
|
43
|
+
flex-shrink: 0;
|
|
44
|
+
margin-right: 10px;
|
|
45
|
+
background: var(--q-theme-primary-light);
|
|
46
|
+
|
|
47
|
+
.q-icon {
|
|
48
|
+
width: 20px;
|
|
49
|
+
height: 20px;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
&-info {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
overflow: hidden;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
&-name {
|
|
60
|
+
font-size: 12px;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
white-space: nowrap;
|
|
63
|
+
overflow: hidden;
|
|
64
|
+
text-overflow: ellipsis;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
&-extension {
|
|
68
|
+
font-size: 10px;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@import '../../components/MarkdownRender/markdown-render';
|
|
2
2
|
@import '../../components/FieldPreview/field-preview';
|
|
3
3
|
@import '../../components/PulseDots/pulse-dots';
|
|
4
|
+
@import '../styles/preview-file';
|
|
4
5
|
|
|
5
6
|
.q-chatbot {
|
|
6
7
|
width: 100%;
|
|
@@ -17,6 +18,12 @@
|
|
|
17
18
|
flex: 1;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
&__remove-file {
|
|
22
|
+
position: absolute;
|
|
23
|
+
top: -8px;
|
|
24
|
+
right: -8px;
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
&__text {
|
|
21
28
|
p {
|
|
22
29
|
margin: 0;
|
|
@@ -44,35 +51,7 @@
|
|
|
44
51
|
position: relative;
|
|
45
52
|
}
|
|
46
53
|
|
|
47
|
-
|
|
48
|
-
display: inline-flex;
|
|
49
|
-
align-items: center;
|
|
50
|
-
position: relative;
|
|
51
|
-
margin-top: 0.5rem;
|
|
52
|
-
width: fit-content;
|
|
53
|
-
|
|
54
|
-
img {
|
|
55
|
-
width: 60px;
|
|
56
|
-
height: 60px;
|
|
57
|
-
object-fit: cover;
|
|
58
|
-
border-radius: 4px;
|
|
59
|
-
margin-right: 0.25rem;
|
|
60
|
-
border: 1px solid #eaebec;
|
|
61
|
-
overflow: hidden;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
&__image-preview img:hover + .q-chatbot__remove-image,
|
|
66
|
-
&__image-preview img:focus + .q-chatbot__remove-image {
|
|
67
|
-
opacity: 1;
|
|
68
|
-
pointer-events: auto;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
&__image-preview img:focus {
|
|
72
|
-
outline: solid rgb(var(--q-theme-info-rgb) / 50%);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
.q-button.q-chatbot__remove-image {
|
|
54
|
+
.q-button.q-chatbot__remove-file {
|
|
76
55
|
position: absolute;
|
|
77
56
|
top: -5px;
|
|
78
57
|
right: -5px;
|
|
@@ -80,11 +59,8 @@
|
|
|
80
59
|
color: white;
|
|
81
60
|
border-radius: 50%;
|
|
82
61
|
padding: 5px;
|
|
83
|
-
font-size:
|
|
62
|
+
font-size: 10px;
|
|
84
63
|
border: none;
|
|
85
|
-
opacity: 0;
|
|
86
|
-
pointer-events: none;
|
|
87
|
-
transition: opacity 0.2s ease-in-out;
|
|
88
64
|
|
|
89
65
|
&:hover,
|
|
90
66
|
&:focus {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
:user-image="props.userImage"
|
|
23
23
|
:chatbot-image="props.chatbotImage"
|
|
24
24
|
:loading="isLoading && !message.message"
|
|
25
|
-
:
|
|
25
|
+
:file="message.file"
|
|
26
26
|
:api-endpoint="props.apiEndpoint"
|
|
27
27
|
:session-i-d="message.sessionID"
|
|
28
28
|
:fields="message.fields"
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
FieldData,
|
|
60
60
|
AppliedFieldData
|
|
61
61
|
} from './types'
|
|
62
|
-
import type {
|
|
62
|
+
import type { ChatBotFile } from '../ChatBotInput'
|
|
63
63
|
|
|
64
64
|
// Assets
|
|
65
65
|
import ChatBotIcon from '@/assets/chatbot_profile.svg'
|
|
@@ -150,14 +150,10 @@
|
|
|
150
150
|
sendInitialMessage()
|
|
151
151
|
|
|
152
152
|
data.history.forEach(async (message: ChatBotMessageContent) => {
|
|
153
|
-
const imgUrl = message.imageUrl
|
|
154
|
-
? props.controllerEndpoint + message.imageUrl
|
|
155
|
-
: undefined
|
|
156
|
-
|
|
157
153
|
addChatMessage(
|
|
158
154
|
message.content,
|
|
159
155
|
message.type === 'ai' ? 'bot' : 'user',
|
|
160
|
-
|
|
156
|
+
undefined, // At this point we don't have preview URLs for past messages
|
|
161
157
|
message.sessionID
|
|
162
158
|
)
|
|
163
159
|
})
|
|
@@ -165,7 +161,7 @@
|
|
|
165
161
|
|
|
166
162
|
function sendInitialMessage() {
|
|
167
163
|
const message = texts.initialMessage
|
|
168
|
-
addChatMessage(message, 'bot',
|
|
164
|
+
addChatMessage(message, 'bot', undefined, undefined, true)
|
|
169
165
|
}
|
|
170
166
|
|
|
171
167
|
function resetChat() {
|
|
@@ -183,7 +179,7 @@
|
|
|
183
179
|
}
|
|
184
180
|
}
|
|
185
181
|
|
|
186
|
-
async function sendMessage(prompt: string,
|
|
182
|
+
async function sendMessage(prompt: string, file?: ChatBotFile) {
|
|
187
183
|
if (messagesContainer.value) {
|
|
188
184
|
messagesContainer.value.scrollTo({
|
|
189
185
|
top: messagesContainer.value.scrollHeight,
|
|
@@ -191,8 +187,8 @@
|
|
|
191
187
|
})
|
|
192
188
|
}
|
|
193
189
|
|
|
194
|
-
addChatMessage(prompt, 'user',
|
|
195
|
-
setChatPrompt(prompt,
|
|
190
|
+
addChatMessage(prompt, 'user', file)
|
|
191
|
+
setChatPrompt(prompt, file?.fileData)
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
async function getAgentJob(jobId: string) {
|
|
@@ -239,13 +235,13 @@
|
|
|
239
235
|
)
|
|
240
236
|
}
|
|
241
237
|
|
|
242
|
-
async function setChatPrompt(prompt: string,
|
|
238
|
+
async function setChatPrompt(prompt: string, file?: File) {
|
|
243
239
|
// Add an empty bot message marked as streaming to trigger bouncing dots animation
|
|
244
240
|
const currentSessionID = uuidv4()
|
|
245
241
|
|
|
246
242
|
const formData = new FormData()
|
|
247
|
-
if (
|
|
248
|
-
formData.append('
|
|
243
|
+
if (file) {
|
|
244
|
+
formData.append('file', file)
|
|
249
245
|
}
|
|
250
246
|
|
|
251
247
|
formData.append('message', prompt)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
import ChatBot from '../ChatBot.vue'
|
|
3
|
+
|
|
4
|
+
// Utils
|
|
5
|
+
import { mount } from '@vue/test-utils'
|
|
6
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
7
|
+
import { ref } from 'vue'
|
|
8
|
+
|
|
9
|
+
// composables
|
|
10
|
+
import { useTexts } from '@/composables/useTexts'
|
|
11
|
+
|
|
12
|
+
// Types
|
|
13
|
+
import type { ChatBotProps } from '../types'
|
|
14
|
+
|
|
15
|
+
// Mock UseChatApi composable to avoid real API calls during tests
|
|
16
|
+
vi.mock('@/composables/useChatApi', () => {
|
|
17
|
+
return {
|
|
18
|
+
useChatApi: vi.fn(() => ({
|
|
19
|
+
isLoading: ref(false),
|
|
20
|
+
lastError: { value: null },
|
|
21
|
+
getChatData: vi.fn().mockResolvedValue({
|
|
22
|
+
data: { success: true, history: [] },
|
|
23
|
+
error: null
|
|
24
|
+
}),
|
|
25
|
+
clearChatData: vi.fn().mockResolvedValue({ data: { success: true }, error: null }),
|
|
26
|
+
getJobResultData: vi.fn(),
|
|
27
|
+
sendPrompt: vi.fn(),
|
|
28
|
+
handleFeedback: vi.fn()
|
|
29
|
+
}))
|
|
30
|
+
}
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
describe('ChatBot', () => {
|
|
34
|
+
const props: ChatBotProps = {
|
|
35
|
+
apiEndpoint: 'https://api.example.com/chat',
|
|
36
|
+
username: 'TestUser',
|
|
37
|
+
projectPath: 'quidgest',
|
|
38
|
+
dateFormat: 'HH:mm'
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
it('renders the component with default props', () => {
|
|
42
|
+
const wrapper = mount(ChatBot, { props })
|
|
43
|
+
expect(wrapper.html()).toMatchSnapshot()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('shows the initial message after loading', () => {
|
|
47
|
+
const wrapper = mount(ChatBot, { props })
|
|
48
|
+
const { initialMessage } = useTexts()
|
|
49
|
+
|
|
50
|
+
expect(wrapper.text()).toContain(initialMessage)
|
|
51
|
+
})
|
|
52
|
+
})
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ChatBot > renders the component with default props 1`] = `
|
|
4
|
+
"<div class="q-chatbot">
|
|
5
|
+
<div class="q-chatbot__content">
|
|
6
|
+
<div class="q-chatbot__tools">
|
|
7
|
+
<div class="q-chatbot__tools__select">
|
|
8
|
+
<!--v-if-->
|
|
9
|
+
</div><button type="button" class="q-button q-button--outlined q-button--primary q-button--borderless q-chatbot__tools-clear" disabled="" title="Clear chat">
|
|
10
|
+
<!--v-if--><span class="q-button__content"><span data-test="bin"></span> </span>
|
|
11
|
+
</button>
|
|
12
|
+
</div>
|
|
13
|
+
<div class="q-chatbot__messages-container"></div>
|
|
14
|
+
</div>
|
|
15
|
+
<div class="q-chatbot__footer-container">
|
|
16
|
+
<div class="q-label"><label>What can I help with?</label></div>
|
|
17
|
+
<div class="q-chatbot__footer q-chatbot__footer-disabled">
|
|
18
|
+
<div class="q-chatbot__input-wrapper">
|
|
19
|
+
<!--v-if-->
|
|
20
|
+
<div class="q-chatbot__input">
|
|
21
|
+
<div class="q-field q-field--block q-field--disabled q-text-area">
|
|
22
|
+
<!--v-if-->
|
|
23
|
+
<div class="q-field__control">
|
|
24
|
+
<!--v-if--><textarea id="uid-1" class="q-text-area__input" disabled="" rows="2" resize="none" wrap="soft"></textarea>
|
|
25
|
+
<!--v-if-->
|
|
26
|
+
</div>
|
|
27
|
+
<!--v-if-->
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="q-chatbot__send-container"><button type="button" class="q-button q-button--outlined q-button--primary q-chatbot__upload" disabled="" title="Upload Image">
|
|
31
|
+
<!--v-if--><span class="q-button__content"><span data-test="upload"></span> </span>
|
|
32
|
+
</button><!-- Hidden file input --><input id="file-upload" type="file" accept=".png,.jpeg,.jpg,.svg,.webp,.pdf,.doc,.docx" class="hidden-input"><button type="button" class="q-button q-button--bold q-button--primary q-chatbot__send" disabled="" title="Send message" readonly="">
|
|
33
|
+
<!--v-if--><span class="q-button__content"><span data-test="send"></span> </span>
|
|
34
|
+
</button></div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>"
|
|
39
|
+
`;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ChatBotFile } from '../ChatBotInput'
|
|
2
|
+
|
|
1
3
|
export type ChatBotProps = {
|
|
2
4
|
apiEndpoint?: string
|
|
3
5
|
controllerEndpoint?: string
|
|
@@ -41,7 +43,7 @@ export type ChatMessage = {
|
|
|
41
43
|
date: Date
|
|
42
44
|
sender: ChatBotMessageSender
|
|
43
45
|
sessionID: string
|
|
44
|
-
|
|
46
|
+
file?: ChatBotFile
|
|
45
47
|
isWelcomeMessage?: boolean
|
|
46
48
|
fields?: FieldData[]
|
|
47
49
|
}
|
|
@@ -8,19 +8,30 @@
|
|
|
8
8
|
@drop.prevent="onDrop">
|
|
9
9
|
<div class="q-chatbot__input-wrapper">
|
|
10
10
|
<div
|
|
11
|
-
v-if="
|
|
12
|
-
class="q-
|
|
11
|
+
v-if="selectedFile"
|
|
12
|
+
class="q-chatbot__file-preview">
|
|
13
13
|
<img
|
|
14
|
-
|
|
14
|
+
v-if="isImageFile"
|
|
15
|
+
class="q-chatbot__image-preview"
|
|
16
|
+
:src="filePreviewUrl"
|
|
15
17
|
tabindex="0"
|
|
16
18
|
:alt="texts.imagePreview" />
|
|
19
|
+
<div
|
|
20
|
+
v-else
|
|
21
|
+
class="q-chatbot__file-preview"
|
|
22
|
+
tabindex="0">
|
|
23
|
+
<q-icon
|
|
24
|
+
icon="file"
|
|
25
|
+
class="q-chatbot__file-icon" />
|
|
26
|
+
<span class="q-chatbot__file-name">{{ selectedFile.name }}</span>
|
|
27
|
+
</div>
|
|
17
28
|
<q-button
|
|
18
|
-
class="q-chatbot__remove-
|
|
29
|
+
class="q-chatbot__remove-file"
|
|
19
30
|
tabindex="0"
|
|
20
31
|
flat
|
|
21
32
|
round
|
|
22
|
-
@click="
|
|
23
|
-
<q-icon icon="
|
|
33
|
+
@click="removeFile">
|
|
34
|
+
<q-icon icon="remove" />
|
|
24
35
|
</q-button>
|
|
25
36
|
</div>
|
|
26
37
|
<div class="q-chatbot__input">
|
|
@@ -33,34 +44,34 @@
|
|
|
33
44
|
:disabled="props.disabled"
|
|
34
45
|
@keyup.enter="sendMessage" />
|
|
35
46
|
</div>
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</
|
|
47
|
+
<div class="q-chatbot__send-container">
|
|
48
|
+
<q-button
|
|
49
|
+
:title="texts.imageUpload"
|
|
50
|
+
class="q-chatbot__upload"
|
|
51
|
+
:disabled="props.disabled || props.loading || !!selectedFile"
|
|
52
|
+
@click="triggerImageUpload">
|
|
53
|
+
<q-icon icon="upload" />
|
|
54
|
+
</q-button>
|
|
55
|
+
|
|
56
|
+
<!-- Hidden file input -->
|
|
57
|
+
<input
|
|
58
|
+
id="file-upload"
|
|
59
|
+
ref="fileInput"
|
|
60
|
+
type="file"
|
|
61
|
+
:accept="acceptedFileTypes"
|
|
62
|
+
class="hidden-input"
|
|
63
|
+
@change="handleFileUpload" />
|
|
64
|
+
|
|
65
|
+
<q-button
|
|
66
|
+
:title="texts.sendMessage"
|
|
67
|
+
variant="bold"
|
|
68
|
+
class="q-chatbot__send"
|
|
69
|
+
:disabled="isSendButtonDisabled"
|
|
70
|
+
:readonly="isSendButtonDisabled"
|
|
71
|
+
@click="sendMessage">
|
|
72
|
+
<q-icon icon="send" />
|
|
73
|
+
</q-button>
|
|
74
|
+
</div>
|
|
64
75
|
</div>
|
|
65
76
|
</div>
|
|
66
77
|
</template>
|
|
@@ -76,22 +87,28 @@
|
|
|
76
87
|
import { ref, computed } from 'vue'
|
|
77
88
|
|
|
78
89
|
// Types
|
|
79
|
-
import { ChatBotInputProps,
|
|
90
|
+
import type { ChatBotInputProps, ChatBotFile } from './'
|
|
80
91
|
|
|
81
92
|
const props = defineProps<ChatBotInputProps>()
|
|
82
93
|
|
|
83
94
|
const emit = defineEmits<{
|
|
84
|
-
|
|
95
|
+
'send-message': [prompt: string, file?: ChatBotFile]
|
|
85
96
|
}>()
|
|
86
97
|
|
|
87
98
|
const texts = useTexts()
|
|
88
99
|
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const
|
|
92
|
-
const hasSelectedImage = ref<boolean>(false)
|
|
93
|
-
|
|
100
|
+
const fileInput = ref<HTMLInputElement | null>(null)
|
|
101
|
+
const selectedFile = ref<File | null>(null)
|
|
102
|
+
const filePreviewUrl = ref<string>('')
|
|
94
103
|
const isDragging = ref(false)
|
|
104
|
+
|
|
105
|
+
const acceptedFileTypes = computed(() => '.png,.jpeg,.jpg,.svg,.webp,.pdf,.doc,.docx')
|
|
106
|
+
const isImageFile = computed(() => {
|
|
107
|
+
if (!selectedFile.value) return false
|
|
108
|
+
|
|
109
|
+
return selectedFile.value.type.startsWith('image/') ?? false
|
|
110
|
+
})
|
|
111
|
+
|
|
95
112
|
const userPrompt = ref(props.userPrompt ?? '')
|
|
96
113
|
|
|
97
114
|
const isSendButtonDisabled = computed(() => {
|
|
@@ -108,7 +125,7 @@
|
|
|
108
125
|
const chatBotFooterClasses = computed(() => {
|
|
109
126
|
return {
|
|
110
127
|
'q-chatbot__footer-disabled': props.disabled,
|
|
111
|
-
'drag-over': isDragging.value && !
|
|
128
|
+
'drag-over': isDragging.value && !selectedFile.value
|
|
112
129
|
}
|
|
113
130
|
})
|
|
114
131
|
|
|
@@ -121,22 +138,19 @@
|
|
|
121
138
|
event.preventDefault()
|
|
122
139
|
isDragging.value = false
|
|
123
140
|
|
|
124
|
-
if (props.disabled ||
|
|
141
|
+
if (props.disabled || selectedFile.value) return
|
|
125
142
|
|
|
126
143
|
const files = event.dataTransfer?.files
|
|
127
144
|
if (!files) return
|
|
145
|
+
handleFileSelection(files[0])
|
|
146
|
+
}
|
|
128
147
|
|
|
129
|
-
|
|
148
|
+
function handleFileSelection(file: File) {
|
|
149
|
+
selectedFile.value = file
|
|
130
150
|
if (file.type.startsWith('image/')) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const dataTransfer = new DataTransfer()
|
|
135
|
-
dataTransfer.items.add(file)
|
|
136
|
-
imageInput.value.files = dataTransfer.files
|
|
137
|
-
|
|
138
|
-
imagePreviewUrl.value = URL.createObjectURL(file)
|
|
139
|
-
hasSelectedImage.value = true
|
|
151
|
+
filePreviewUrl.value = URL.createObjectURL(file)
|
|
152
|
+
} else {
|
|
153
|
+
filePreviewUrl.value = ''
|
|
140
154
|
}
|
|
141
155
|
}
|
|
142
156
|
|
|
@@ -144,43 +158,36 @@
|
|
|
144
158
|
if (userPrompt.value.trim() === '' || props.loading || props.disabled) return
|
|
145
159
|
|
|
146
160
|
// Check if an image is selected
|
|
147
|
-
if (
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
file: file,
|
|
152
|
-
previewUrl: imagePreviewUrl.value
|
|
161
|
+
if (selectedFile.value) {
|
|
162
|
+
const fileData: ChatBotFile = {
|
|
163
|
+
fileData: selectedFile.value,
|
|
164
|
+
previewUrl: filePreviewUrl.value ?? ''
|
|
153
165
|
}
|
|
154
166
|
|
|
155
167
|
emit('send-message', userPrompt.value, fileData)
|
|
156
|
-
|
|
168
|
+
removeFile()
|
|
157
169
|
} else emit('send-message', userPrompt.value)
|
|
158
170
|
|
|
159
171
|
userPrompt.value = ''
|
|
160
172
|
}
|
|
161
173
|
|
|
162
|
-
function
|
|
163
|
-
|
|
174
|
+
function removeFile() {
|
|
175
|
+
selectedFile.value = null
|
|
176
|
+
filePreviewUrl.value = ''
|
|
164
177
|
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
178
|
+
if (fileInput.value) {
|
|
179
|
+
fileInput.value.value = ''
|
|
180
|
+
fileInput.value.files = null
|
|
168
181
|
}
|
|
169
|
-
hasSelectedImage.value = false
|
|
170
182
|
}
|
|
171
183
|
|
|
172
184
|
function triggerImageUpload() {
|
|
173
|
-
|
|
185
|
+
fileInput.value?.click()
|
|
174
186
|
}
|
|
175
187
|
|
|
176
|
-
function
|
|
177
|
-
// Store the selected image in imageInput
|
|
188
|
+
function handleFileUpload(event: Event) {
|
|
178
189
|
const target = event.target as HTMLInputElement
|
|
179
|
-
imageInput.value = target
|
|
180
190
|
|
|
181
|
-
if (target.files
|
|
182
|
-
imagePreviewUrl.value = URL.createObjectURL(target.files[0])
|
|
183
|
-
hasSelectedImage.value = true
|
|
184
|
-
}
|
|
191
|
+
if (target.files?.[0]) handleFileSelection(target.files[0])
|
|
185
192
|
}
|
|
186
193
|
</script>
|