@quidgest/chatbot 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/ChatBot/ChatBot.vue.d.ts +53 -0
- package/dist/components/ChatBot/types.d.ts +48 -0
- package/dist/components/ChatBotInput/ChatBotInput.vue.d.ts +17 -0
- package/dist/components/ChatBotInput/index.d.ts +5 -0
- package/dist/components/ChatBotInput/types.d.ts +28 -0
- package/dist/components/ChatBotMessage/ChatBotMessage.vue.d.ts +41 -0
- package/dist/components/ChatBotMessage/ChatBotMessageButtons.vue.d.ts +21 -0
- package/dist/components/ChatBotMessage/index.d.ts +6 -0
- package/dist/components/ChatBotMessage/types.d.ts +46 -0
- package/dist/components/ChatToolBar/ChatToolBar.vue.d.ts +39 -0
- package/dist/components/ChatToolBar/index.d.ts +5 -0
- package/dist/components/ChatToolBar/types.d.ts +16 -0
- package/dist/components/FieldPreview/FieldPreview.vue.d.ts +17 -0
- package/dist/components/FieldPreview/index.d.ts +5 -0
- package/dist/components/FieldPreview/types.d.ts +7 -0
- package/dist/components/MarkdownRender/MarkdownRender.vue.d.ts +13 -0
- package/dist/components/MarkdownRender/index.d.ts +5 -0
- package/dist/components/MarkdownRender/types.d.ts +7 -0
- package/dist/composables/useChatApi.d.ts +23 -0
- package/dist/composables/useChatMessages.d.ts +11 -0
- package/dist/composables/useSSE.d.ts +10 -0
- package/dist/composables/useTexts.d.ts +26 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -47
- package/dist/index.mjs +2651 -8663
- package/dist/style.css +1 -1
- package/dist/utils/helper.d.ts +1 -0
- package/package.json +5 -5
- package/src/assets/chatbot_profile.svg +1 -0
- package/src/assets/styles/styles.scss +10 -42
- package/src/components/ChatBot/ChatBot.vue +375 -0
- package/src/components/ChatBot/types.ts +55 -0
- package/src/components/ChatBotInput/ChatBotInput.vue +195 -0
- package/src/components/ChatBotInput/index.ts +5 -0
- package/src/components/ChatBotInput/types.ts +33 -0
- package/src/components/ChatBotMessage/ChatBotMessage.vue +139 -0
- package/src/components/ChatBotMessage/ChatBotMessageButtons.vue +169 -0
- package/src/components/ChatBotMessage/index.ts +8 -0
- package/src/components/ChatBotMessage/types.ts +70 -0
- package/src/components/ChatToolBar/ChatToolBar.vue +82 -0
- package/src/components/ChatToolBar/index.ts +5 -0
- package/src/components/ChatToolBar/types.ts +18 -0
- package/src/components/FieldPreview/FieldPreview.vue +78 -0
- package/src/components/FieldPreview/field-preview.scss +34 -0
- package/src/components/FieldPreview/index.ts +5 -0
- package/src/components/FieldPreview/types.ts +7 -0
- package/src/components/MarkdownRender/MarkdownRender.vue +25 -0
- package/src/components/MarkdownRender/index.ts +5 -0
- package/src/components/MarkdownRender/markdown-render.scss +24 -0
- package/src/components/MarkdownRender/types.ts +7 -0
- package/src/components/PulseDots/PulseDots.vue +24 -0
- package/src/components/PulseDots/pulse-dots.scss +37 -0
- package/src/composables/useChatApi.ts +156 -0
- package/src/composables/useChatMessages.ts +58 -0
- package/src/composables/useSSE.ts +90 -0
- package/src/composables/useTexts.ts +32 -0
- package/src/index.ts +1 -1
- package/src/utils/helper.ts +12 -0
- package/dist/components/CBMessage.vue.d.ts +0 -95
- package/dist/components/ChatBot.vue.d.ts +0 -65
- package/dist/components/index.d.ts +0 -4
- package/dist/types/chatbot.type.d.ts +0 -14
- package/dist/types/message.type.d.ts +0 -34
- package/dist/types/texts.type.d.ts +0 -3
- package/src/assets/chatbot.png +0 -0
- package/src/components/CBMessage.vue +0 -276
- package/src/components/ChatBot.vue +0 -496
- package/src/components/PulseDots.vue +0 -15
- package/src/components/index.ts +0 -4
- package/src/types/chatbot.type.ts +0 -15
- package/src/types/message.type.ts +0 -55
- package/src/types/texts.type.ts +0 -3
- /package/dist/components/{PulseDots.vue.d.ts → PulseDots/PulseDots.vue.d.ts} +0 -0
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<q-label :label="texts.inputLabel" />
|
|
3
|
+
<div
|
|
4
|
+
class="q-chatbot__footer"
|
|
5
|
+
@dragover.prevent="onDragOver"
|
|
6
|
+
@dragleave.prevent="onDragLeave"
|
|
7
|
+
@drop.prevent="onDrop"
|
|
8
|
+
:class="chatBotFooterClasses">
|
|
9
|
+
<div class="q-chatbot__input-wrapper">
|
|
10
|
+
<div
|
|
11
|
+
v-if="imagePreviewUrl"
|
|
12
|
+
class="q-chatbot__image-preview">
|
|
13
|
+
<img
|
|
14
|
+
:src="imagePreviewUrl"
|
|
15
|
+
tabindex="0"
|
|
16
|
+
alt="Image preview" />
|
|
17
|
+
<q-button
|
|
18
|
+
class="q-chatbot__remove-image"
|
|
19
|
+
tabindex="0"
|
|
20
|
+
flat
|
|
21
|
+
round
|
|
22
|
+
@click="removeImage">
|
|
23
|
+
<q-icon icon="bin" />
|
|
24
|
+
</q-button>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="q-chatbot__input">
|
|
27
|
+
<q-text-area
|
|
28
|
+
v-model="userPrompt"
|
|
29
|
+
size="block"
|
|
30
|
+
autosize
|
|
31
|
+
resize="none"
|
|
32
|
+
:rows="2"
|
|
33
|
+
:disabled="props.disabled"
|
|
34
|
+
@keyup.enter="sendMessage" />
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="q-chatbot__send-container">
|
|
38
|
+
<q-button
|
|
39
|
+
:title="texts.imageUpload"
|
|
40
|
+
class="q-chatbot__upload"
|
|
41
|
+
:disabled="props.disabled || props.loading || hasSelectedImage"
|
|
42
|
+
@click="triggerImageUpload">
|
|
43
|
+
<q-icon icon="upload" />
|
|
44
|
+
</q-button>
|
|
45
|
+
|
|
46
|
+
<!-- Hidden file input -->
|
|
47
|
+
<input
|
|
48
|
+
id="image-upload"
|
|
49
|
+
type="file"
|
|
50
|
+
ref="imageInput"
|
|
51
|
+
@change="handleImageUpload"
|
|
52
|
+
:accept="acceptedImageTypes"
|
|
53
|
+
class="hidden-input" />
|
|
54
|
+
|
|
55
|
+
<q-button
|
|
56
|
+
:title="texts.sendMessage"
|
|
57
|
+
variant="bold"
|
|
58
|
+
class="q-chatbot__send"
|
|
59
|
+
:disabled="isSendButtonDisabled"
|
|
60
|
+
:readonly="isSendButtonDisabled"
|
|
61
|
+
@click="sendMessage">
|
|
62
|
+
<q-icon icon="send" />
|
|
63
|
+
</q-button>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</template>
|
|
67
|
+
|
|
68
|
+
<script setup lang="ts">
|
|
69
|
+
// Components
|
|
70
|
+
import { QLabel } from '@quidgest/ui/components'
|
|
71
|
+
|
|
72
|
+
// Composables
|
|
73
|
+
import { useTexts } from '@/composables/useTexts'
|
|
74
|
+
|
|
75
|
+
// Utils
|
|
76
|
+
import { ref, computed } from 'vue'
|
|
77
|
+
|
|
78
|
+
// Types
|
|
79
|
+
import { ChatBotInputProps, ChatBotImage } from './types'
|
|
80
|
+
|
|
81
|
+
const texts = useTexts()
|
|
82
|
+
|
|
83
|
+
const props = defineProps<ChatBotInputProps>()
|
|
84
|
+
|
|
85
|
+
const imageInput = ref<HTMLInputElement | null>(null)
|
|
86
|
+
const imagePreviewUrl = ref<string>('')
|
|
87
|
+
const acceptedImageTypes = computed(() => '.png, .jpeg, .jpg, .svg, .webp')
|
|
88
|
+
const hasSelectedImage = ref<boolean>(false)
|
|
89
|
+
|
|
90
|
+
const isDragging = ref(false)
|
|
91
|
+
const userPrompt = ref(props.userPrompt ?? '')
|
|
92
|
+
|
|
93
|
+
const emits = defineEmits<{
|
|
94
|
+
(e: 'sendMessage', prompt: string, image?: ChatBotImage): void
|
|
95
|
+
}>()
|
|
96
|
+
|
|
97
|
+
const isSendButtonDisabled = computed(() => {
|
|
98
|
+
return (
|
|
99
|
+
props.disabled ||
|
|
100
|
+
props.loading ||
|
|
101
|
+
userPrompt.value.trim().length === 0
|
|
102
|
+
)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
function onDragOver(event: DragEvent) {
|
|
106
|
+
event.preventDefault()
|
|
107
|
+
if (!props.disabled) {
|
|
108
|
+
isDragging.value = true
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const chatBotFooterClasses = computed(() => {
|
|
113
|
+
return {
|
|
114
|
+
'q-chatbot__footer-disabled': props.disabled,
|
|
115
|
+
'drag-over': isDragging.value && !hasSelectedImage.value
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
function onDragLeave(event: DragEvent) {
|
|
120
|
+
event.preventDefault()
|
|
121
|
+
isDragging.value = false
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function onDrop(event: DragEvent) {
|
|
125
|
+
event.preventDefault()
|
|
126
|
+
isDragging.value = false
|
|
127
|
+
|
|
128
|
+
if (props.disabled || hasSelectedImage.value) return
|
|
129
|
+
|
|
130
|
+
const files = event.dataTransfer?.files
|
|
131
|
+
if (!files) return
|
|
132
|
+
|
|
133
|
+
const file = files[0]
|
|
134
|
+
if (file.type.startsWith('image/')) {
|
|
135
|
+
if (!imageInput.value) return
|
|
136
|
+
|
|
137
|
+
// Create a new FileList-like object
|
|
138
|
+
const dataTransfer = new DataTransfer()
|
|
139
|
+
dataTransfer.items.add(file)
|
|
140
|
+
imageInput.value.files = dataTransfer.files
|
|
141
|
+
|
|
142
|
+
imagePreviewUrl.value = URL.createObjectURL(file)
|
|
143
|
+
hasSelectedImage.value = true
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function sendMessage() {
|
|
148
|
+
if (userPrompt.value.trim() === '' || props.loading || props.disabled)
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
// Check if an image is selected
|
|
152
|
+
if (
|
|
153
|
+
imageInput.value &&
|
|
154
|
+
imageInput.value.files &&
|
|
155
|
+
imageInput.value.files.length > 0
|
|
156
|
+
) {
|
|
157
|
+
const file = imageInput.value.files[0]
|
|
158
|
+
|
|
159
|
+
const fileData: ChatBotImage = {
|
|
160
|
+
file: file,
|
|
161
|
+
previewUrl: imagePreviewUrl.value
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
emits('sendMessage', userPrompt.value, fileData)
|
|
165
|
+
removeImage()
|
|
166
|
+
} else emits('sendMessage', userPrompt.value)
|
|
167
|
+
|
|
168
|
+
userPrompt.value = ''
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function removeImage() {
|
|
172
|
+
imagePreviewUrl.value = ''
|
|
173
|
+
|
|
174
|
+
if (imageInput.value) {
|
|
175
|
+
imageInput.value.value = ''
|
|
176
|
+
imageInput.value.files = null
|
|
177
|
+
}
|
|
178
|
+
hasSelectedImage.value = false
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function triggerImageUpload() {
|
|
182
|
+
imageInput.value?.click()
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function handleImageUpload(event: Event) {
|
|
186
|
+
// Store the selected image in imageInput
|
|
187
|
+
const target = event.target as HTMLInputElement
|
|
188
|
+
imageInput.value = target
|
|
189
|
+
|
|
190
|
+
if (target.files && target.files[0]) {
|
|
191
|
+
imagePreviewUrl.value = URL.createObjectURL(target.files[0])
|
|
192
|
+
hasSelectedImage.value = true
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
</script>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type ChatBotInputProps = {
|
|
2
|
+
/**
|
|
3
|
+
* If the footer is disabled or not
|
|
4
|
+
*/
|
|
5
|
+
disabled?: boolean
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* If the footer is loading or not
|
|
9
|
+
*/
|
|
10
|
+
loading?: boolean
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The message to be sent
|
|
14
|
+
*/
|
|
15
|
+
userPrompt?: string
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The agent to be used
|
|
19
|
+
*/
|
|
20
|
+
agentId?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ChatBotImage = {
|
|
24
|
+
/**
|
|
25
|
+
* The image URL
|
|
26
|
+
*/
|
|
27
|
+
previewUrl: string
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The file object
|
|
31
|
+
*/
|
|
32
|
+
file: File
|
|
33
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
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>
|
|
55
|
+
</template>
|
|
56
|
+
|
|
57
|
+
<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
|
+
}
|
|
139
|
+
</script>
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<q-dialog
|
|
3
|
+
v-model="showDialog"
|
|
4
|
+
:buttons="commentButtons">
|
|
5
|
+
<template #body>
|
|
6
|
+
<div class="q-chatbot__dialog-title">
|
|
7
|
+
{{ texts.commentDialogTitle }}
|
|
8
|
+
</div>
|
|
9
|
+
<q-text-field
|
|
10
|
+
v-model="feedbackComment"
|
|
11
|
+
:maxLength="150"
|
|
12
|
+
size="large"
|
|
13
|
+
:placeholder="texts.commentPlaceholder" />
|
|
14
|
+
</template>
|
|
15
|
+
</q-dialog>
|
|
16
|
+
|
|
17
|
+
<div
|
|
18
|
+
v-if="showButtons"
|
|
19
|
+
class="q-chatbot__feedback-buttons">
|
|
20
|
+
<q-button-group>
|
|
21
|
+
<q-button
|
|
22
|
+
:title="texts.goodResponse"
|
|
23
|
+
borderless
|
|
24
|
+
:disabled="props.loading"
|
|
25
|
+
@click="openFeedbackDialog(1)">
|
|
26
|
+
<q-icon icon="thumb-up" />
|
|
27
|
+
</q-button>
|
|
28
|
+
<q-button
|
|
29
|
+
:title="texts.badResponse"
|
|
30
|
+
borderless
|
|
31
|
+
:disabled="props.loading"
|
|
32
|
+
@click="openFeedbackDialog(0)">
|
|
33
|
+
<q-icon icon="thumb-down" />
|
|
34
|
+
</q-button>
|
|
35
|
+
<q-button
|
|
36
|
+
:title="texts.copyResponse"
|
|
37
|
+
borderless
|
|
38
|
+
:disabled="props.loading"
|
|
39
|
+
@click="copyResponse">
|
|
40
|
+
<q-icon icon="copy-content" />
|
|
41
|
+
</q-button>
|
|
42
|
+
<q-button
|
|
43
|
+
v-if="showApplyAll"
|
|
44
|
+
:title="texts.applyAll"
|
|
45
|
+
borderless
|
|
46
|
+
:disabled="blockApplyAllButton"
|
|
47
|
+
:readonly="blockApplyAllButton"
|
|
48
|
+
@click="onApplyAll">
|
|
49
|
+
<q-icon icon="apply-all" />
|
|
50
|
+
</q-button>
|
|
51
|
+
</q-button-group>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="q-chatbot__sender">
|
|
54
|
+
{{ messageDate }}
|
|
55
|
+
</div>
|
|
56
|
+
</template>
|
|
57
|
+
|
|
58
|
+
<script setup lang="ts">
|
|
59
|
+
import { computed, ref } from 'vue'
|
|
60
|
+
import { QDialog } from '@quidgest/ui/components'
|
|
61
|
+
import type { ChatBotMessageButtonsProps } from './'
|
|
62
|
+
|
|
63
|
+
// Composables
|
|
64
|
+
import { useTexts } from '@/composables/useTexts'
|
|
65
|
+
import { useChatMessages } from '@/composables/useChatMessages'
|
|
66
|
+
|
|
67
|
+
const texts = useTexts()
|
|
68
|
+
const { getLastMessage } = useChatMessages()
|
|
69
|
+
|
|
70
|
+
const props = defineProps<ChatBotMessageButtonsProps>()
|
|
71
|
+
const showDialog = ref(false)
|
|
72
|
+
const feedbackComment = ref('')
|
|
73
|
+
const currentFeedback = ref<number | null>(null)
|
|
74
|
+
const blockApplyAll = ref(false)
|
|
75
|
+
const blockApplyAllButton = computed(() => {
|
|
76
|
+
return props.loading || blockApplyAll.value
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
const date = props.date || new Date()
|
|
80
|
+
|
|
81
|
+
const emits = defineEmits<{
|
|
82
|
+
(e: 'submit-feedback', feedback: number, comment: string): void
|
|
83
|
+
(e: 'copy-response'): void
|
|
84
|
+
(e: 'apply-all'): void
|
|
85
|
+
}>()
|
|
86
|
+
|
|
87
|
+
const lastMessage = getLastMessage()
|
|
88
|
+
|
|
89
|
+
const showApplyAll = computed(() => {
|
|
90
|
+
if (!lastMessage) return false
|
|
91
|
+
|
|
92
|
+
return lastMessage.fields && lastMessage.fields.length > 1
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const commentButtons = [
|
|
96
|
+
{
|
|
97
|
+
id: 'confirm-btn',
|
|
98
|
+
action: submitFeedback,
|
|
99
|
+
props: {
|
|
100
|
+
label: texts.submitButton
|
|
101
|
+
},
|
|
102
|
+
icon: {
|
|
103
|
+
icon: 'submit'
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'cancel-btn',
|
|
108
|
+
props: {
|
|
109
|
+
label: texts.cancelButton
|
|
110
|
+
},
|
|
111
|
+
icon: {
|
|
112
|
+
icon: 'cancel'
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
|
|
117
|
+
const getLocaleDate = computed(() => {
|
|
118
|
+
if (!props.dateFormat) return date.toLocaleString()
|
|
119
|
+
|
|
120
|
+
return formatDate(date, 'HH:mm')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
const messageDate = computed(() => {
|
|
124
|
+
return `${getLocaleDate.value}`
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
function formatDate(date: Date, format: string) {
|
|
128
|
+
const day = date.getDate().toString().padStart(2, '0')
|
|
129
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
130
|
+
const year = date.getFullYear().toString().padStart(2, '0')
|
|
131
|
+
const hours = date.getHours().toString().padStart(2, '0')
|
|
132
|
+
const minutes = date.getMinutes().toString().padStart(2, '0')
|
|
133
|
+
const seconds = date.getSeconds().toString().padStart(2, '0')
|
|
134
|
+
|
|
135
|
+
return format
|
|
136
|
+
.replace('dd', day)
|
|
137
|
+
.replace('MM', month)
|
|
138
|
+
.replace('yyyy', year)
|
|
139
|
+
.replace('HH', hours)
|
|
140
|
+
.replace('mm', minutes)
|
|
141
|
+
.replace('ss', seconds)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function openFeedbackDialog(feedback: number) {
|
|
145
|
+
showDialog.value = true
|
|
146
|
+
feedbackComment.value = ''
|
|
147
|
+
currentFeedback.value = feedback
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function onApplyAll() {
|
|
151
|
+
if (blockApplyAll.value) return
|
|
152
|
+
blockApplyAll.value = true
|
|
153
|
+
|
|
154
|
+
emits('apply-all')
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function submitFeedback() {
|
|
158
|
+
if (currentFeedback.value === null) return
|
|
159
|
+
|
|
160
|
+
emits('submit-feedback', currentFeedback.value, feedbackComment.value)
|
|
161
|
+
showDialog.value = false
|
|
162
|
+
feedbackComment.value = ''
|
|
163
|
+
currentFeedback.value = null
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function copyResponse() {
|
|
167
|
+
emits('copy-response')
|
|
168
|
+
}
|
|
169
|
+
</script>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import ChatBotMessage from './ChatBotMessage.vue'
|
|
2
|
+
import type { ChatBotMessageProps } from './types'
|
|
3
|
+
|
|
4
|
+
import ChatBotMessageButtons from './ChatBotMessageButtons.vue'
|
|
5
|
+
import type { ChatBotMessageButtonsProps } from './types'
|
|
6
|
+
|
|
7
|
+
export { ChatBotMessage, ChatBotMessageButtons }
|
|
8
|
+
export type { ChatBotMessageProps, ChatBotMessageButtonsProps }
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { ChatBotMessageSender, FieldData } from '@/components/ChatBot/types'
|
|
2
|
+
|
|
3
|
+
export type ChatBotMessageProps = {
|
|
4
|
+
/*
|
|
5
|
+
* Sender of the message
|
|
6
|
+
*/
|
|
7
|
+
sender?: ChatBotMessageSender
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* Message to be displayed
|
|
11
|
+
*/
|
|
12
|
+
message?: string
|
|
13
|
+
|
|
14
|
+
/*
|
|
15
|
+
* Date of when the message was sent
|
|
16
|
+
*/
|
|
17
|
+
date?: Date
|
|
18
|
+
|
|
19
|
+
/*
|
|
20
|
+
* If the message is loading
|
|
21
|
+
*/
|
|
22
|
+
loading?: boolean
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Project locale
|
|
26
|
+
*/
|
|
27
|
+
dateFormat: string
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Image preview URL
|
|
31
|
+
*/
|
|
32
|
+
imagePreviewUrl?: string
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Default api endpoint
|
|
36
|
+
*/
|
|
37
|
+
apiEndpoint: string
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Session ID
|
|
41
|
+
*/
|
|
42
|
+
sessionID?: string
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* User image
|
|
46
|
+
*/
|
|
47
|
+
userImage: string
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Chatbot image
|
|
51
|
+
*/
|
|
52
|
+
chatbotImage: string
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Flag to mark welcome messages
|
|
56
|
+
*/
|
|
57
|
+
isWelcomeMessage?: boolean
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Additional fields for the message
|
|
61
|
+
*/
|
|
62
|
+
fields?: FieldData[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type ChatBotMessageButtonsProps = {
|
|
66
|
+
loading: boolean
|
|
67
|
+
showButtons: boolean
|
|
68
|
+
dateFormat: string
|
|
69
|
+
date?: Date
|
|
70
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
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
|
+
size="medium"
|
|
8
|
+
:items="availableChats"
|
|
9
|
+
@update:model-value="changeChat" />
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<q-button
|
|
13
|
+
:title="clearChat"
|
|
14
|
+
:disabled="props.disabled"
|
|
15
|
+
borderless
|
|
16
|
+
@click="clear">
|
|
17
|
+
<q-icon icon="bin" />
|
|
18
|
+
</q-button>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup lang="ts">
|
|
23
|
+
import { useTexts } from '@/composables/useTexts'
|
|
24
|
+
import { ChatToolBarProps } from './types'
|
|
25
|
+
import { computed, ref, watch } from 'vue'
|
|
26
|
+
|
|
27
|
+
const { clearChat } = useTexts()
|
|
28
|
+
|
|
29
|
+
const defaultChat = {
|
|
30
|
+
key: '',
|
|
31
|
+
value: 'Default Chat',
|
|
32
|
+
formId: ''
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const props = withDefaults(defineProps<ChatToolBarProps>(), {
|
|
36
|
+
availableAgents: () => []
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
const emit = defineEmits<{
|
|
40
|
+
(e: 'clear'): void
|
|
41
|
+
(e: 'change-chat', value: { key: string; formId: string }): void
|
|
42
|
+
}>()
|
|
43
|
+
|
|
44
|
+
const hasAgents = computed(() => {
|
|
45
|
+
return props.availableAgents && props.availableAgents.length > 0
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
const selectedChat = ref(props.selectedAgentKey || defaultChat.key)
|
|
49
|
+
watch(
|
|
50
|
+
() => props.selectedAgentKey,
|
|
51
|
+
(newValue) => {
|
|
52
|
+
selectedChat.value = newValue || defaultChat.key
|
|
53
|
+
}
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
const availableChats = computed(() => {
|
|
57
|
+
const chats = props.availableAgents?.map((agent) => ({
|
|
58
|
+
key: agent.key,
|
|
59
|
+
value: agent.value,
|
|
60
|
+
formId: agent.formId
|
|
61
|
+
}))
|
|
62
|
+
|
|
63
|
+
chats?.push(defaultChat)
|
|
64
|
+
return chats || [defaultChat]
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
function clear() {
|
|
68
|
+
emit('clear')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function changeChat(value: string) {
|
|
72
|
+
const item = availableChats.value.find((chat) => chat.key === value)
|
|
73
|
+
if (!item) return
|
|
74
|
+
|
|
75
|
+
if (item.key === defaultChat.key) {
|
|
76
|
+
emit('change-chat', { key: '', formId: '' })
|
|
77
|
+
return
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
emit('change-chat', item)
|
|
81
|
+
}
|
|
82
|
+
</script>
|