@cognigy/chat-components-vue 0.1.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/LICENSE +20 -0
- package/README.md +178 -0
- package/dist/assets/svg/ArrowBackIcon.vue.d.ts +9 -0
- package/dist/assets/svg/AudioPauseIcon.vue.d.ts +2 -0
- package/dist/assets/svg/AudioPlayIcon.vue.d.ts +2 -0
- package/dist/assets/svg/CloseIcon.vue.d.ts +2 -0
- package/dist/assets/svg/DownloadIcon.vue.d.ts +2 -0
- package/dist/assets/svg/VideoPlayIcon.vue.d.ts +9 -0
- package/dist/assets/svg/index.d.ts +7 -0
- package/dist/chat-components-vue.css +1 -0
- package/dist/chat-components-vue.js +9858 -0
- package/dist/components/Message.vue.d.ts +11 -0
- package/dist/components/common/ActionButton.vue.d.ts +59 -0
- package/dist/components/common/ActionButtons.vue.d.ts +36 -0
- package/dist/components/common/ChatBubble.vue.d.ts +22 -0
- package/dist/components/common/ChatEvent.vue.d.ts +20 -0
- package/dist/components/common/LinkIcon.vue.d.ts +2 -0
- package/dist/components/common/TypingIndicator.vue.d.ts +21 -0
- package/dist/components/common/Typography.vue.d.ts +38 -0
- package/dist/components/messages/AdaptiveCard.vue.d.ts +2 -0
- package/dist/components/messages/AudioMessage.vue.d.ts +5 -0
- package/dist/components/messages/DatePicker.vue.d.ts +2 -0
- package/dist/components/messages/FileMessage.vue.d.ts +2 -0
- package/dist/components/messages/Gallery.vue.d.ts +2 -0
- package/dist/components/messages/GalleryItem.vue.d.ts +7 -0
- package/dist/components/messages/ImageMessage.vue.d.ts +5 -0
- package/dist/components/messages/List.vue.d.ts +2 -0
- package/dist/components/messages/ListItem.vue.d.ts +16 -0
- package/dist/components/messages/TextMessage.vue.d.ts +15 -0
- package/dist/components/messages/TextWithButtons.vue.d.ts +2 -0
- package/dist/components/messages/VideoMessage.vue.d.ts +5 -0
- package/dist/composables/useChannelPayload.d.ts +47 -0
- package/dist/composables/useCollation.d.ts +47 -0
- package/dist/composables/useImageContext.d.ts +13 -0
- package/dist/composables/useMessageContext.d.ts +18 -0
- package/dist/composables/useSanitize.d.ts +8 -0
- package/dist/index.d.ts +33 -0
- package/dist/types/index.d.ts +275 -0
- package/dist/utils/helpers.d.ts +56 -0
- package/dist/utils/matcher.d.ts +20 -0
- package/dist/utils/sanitize.d.ts +28 -0
- package/dist/utils/theme.d.ts +18 -0
- package/package.json +94 -0
- package/src/assets/svg/ArrowBackIcon.vue +30 -0
- package/src/assets/svg/AudioPauseIcon.vue +20 -0
- package/src/assets/svg/AudioPlayIcon.vue +19 -0
- package/src/assets/svg/CloseIcon.vue +10 -0
- package/src/assets/svg/DownloadIcon.vue +10 -0
- package/src/assets/svg/VideoPlayIcon.vue +25 -0
- package/src/assets/svg/index.ts +7 -0
- package/src/components/Message.vue +152 -0
- package/src/components/common/ActionButton.vue +354 -0
- package/src/components/common/ActionButtons.vue +170 -0
- package/src/components/common/ChatBubble.vue +109 -0
- package/src/components/common/ChatEvent.vue +84 -0
- package/src/components/common/LinkIcon.vue +34 -0
- package/src/components/common/TypingIndicator.vue +202 -0
- package/src/components/common/Typography.vue +196 -0
- package/src/components/messages/AdaptiveCard.vue +292 -0
- package/src/components/messages/AudioMessage.vue +391 -0
- package/src/components/messages/DatePicker.vue +135 -0
- package/src/components/messages/FileMessage.vue +195 -0
- package/src/components/messages/Gallery.vue +296 -0
- package/src/components/messages/GalleryItem.vue +214 -0
- package/src/components/messages/ImageMessage.vue +368 -0
- package/src/components/messages/List.vue +149 -0
- package/src/components/messages/ListItem.vue +344 -0
- package/src/components/messages/TextMessage.vue +203 -0
- package/src/components/messages/TextWithButtons.vue +119 -0
- package/src/components/messages/VideoMessage.vue +343 -0
- package/src/composables/useChannelPayload.ts +101 -0
- package/src/composables/useCollation.ts +163 -0
- package/src/composables/useImageContext.ts +27 -0
- package/src/composables/useMessageContext.ts +41 -0
- package/src/composables/useSanitize.ts +25 -0
- package/src/index.ts +71 -0
- package/src/types/index.ts +373 -0
- package/src/utils/helpers.ts +164 -0
- package/src/utils/matcher.ts +283 -0
- package/src/utils/sanitize.ts +133 -0
- package/src/utils/theme.ts +58 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useCollation composable
|
|
3
|
+
* Collates consecutive bot messages into single messages for streaming output
|
|
4
|
+
*
|
|
5
|
+
* When `collateStreamedOutputs` is enabled in config, consecutive bot messages
|
|
6
|
+
* are grouped together so they appear as a single message with combined text.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { computed, type ComputedRef, toValue, type MaybeRefOrGetter } from 'vue'
|
|
10
|
+
import type { IMessage } from '@cognigy/socket-client'
|
|
11
|
+
import type { ChatConfig } from '../types'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A message that may have been collated from multiple bot messages.
|
|
15
|
+
* When collated, text becomes an array and collatedFrom contains the originals.
|
|
16
|
+
*/
|
|
17
|
+
export type CollatedMessage = IMessage & {
|
|
18
|
+
/**
|
|
19
|
+
* Original messages that were collated into this one (only present if collated)
|
|
20
|
+
*/
|
|
21
|
+
collatedFrom?: IMessage[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface UseCollationReturn {
|
|
25
|
+
/**
|
|
26
|
+
* Messages with consecutive bot messages collated
|
|
27
|
+
*/
|
|
28
|
+
collatedMessages: ComputedRef<CollatedMessage[]>
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Whether collation is currently enabled
|
|
32
|
+
*/
|
|
33
|
+
isCollationEnabled: ComputedRef<boolean>
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Original message count
|
|
37
|
+
*/
|
|
38
|
+
originalCount: ComputedRef<number>
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Collated message count
|
|
42
|
+
*/
|
|
43
|
+
collatedCount: ComputedRef<number>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Check if two messages can be collated together
|
|
48
|
+
*/
|
|
49
|
+
function canCollate(current: IMessage, previous: IMessage): boolean {
|
|
50
|
+
// Must both be bot messages
|
|
51
|
+
if (current.source !== 'bot' || previous.source !== 'bot') {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Must both be simple text messages (no rich content)
|
|
56
|
+
if (current.data?._cognigy?._webchat || previous.data?._cognigy?._webchat) {
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Must both have text
|
|
61
|
+
if (!current.text || !previous.text) {
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Don't collate if either has attachments
|
|
66
|
+
if (current.data?.attachments || previous.data?.attachments) {
|
|
67
|
+
return false
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Don't collate plugin messages
|
|
71
|
+
if (current.data?._plugin || previous.data?._plugin) {
|
|
72
|
+
return false
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return true
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Collate consecutive bot messages into combined messages
|
|
80
|
+
*
|
|
81
|
+
* @param messages - Array of messages (can be ref, getter, or plain value)
|
|
82
|
+
* @param config - Chat configuration (can be ref, getter, or plain value)
|
|
83
|
+
* @returns Reactive collated messages
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* const { collatedMessages, isCollationEnabled } = useCollation(messages, config)
|
|
88
|
+
*
|
|
89
|
+
* // Use in template
|
|
90
|
+
* <Message v-for="msg in collatedMessages" :key="msg.traceId" :message="msg" />
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function useCollation(
|
|
94
|
+
messages: MaybeRefOrGetter<IMessage[]>,
|
|
95
|
+
config?: MaybeRefOrGetter<ChatConfig | undefined>
|
|
96
|
+
): UseCollationReturn {
|
|
97
|
+
const isCollationEnabled = computed(() => {
|
|
98
|
+
const cfg = toValue(config)
|
|
99
|
+
return cfg?.settings?.behavior?.collateStreamedOutputs === true
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
const collatedMessages = computed<CollatedMessage[]>(() => {
|
|
103
|
+
const msgs = toValue(messages)
|
|
104
|
+
const enabled = isCollationEnabled.value
|
|
105
|
+
|
|
106
|
+
if (!enabled || !msgs || msgs.length === 0) {
|
|
107
|
+
return msgs as CollatedMessage[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const result: CollatedMessage[] = []
|
|
111
|
+
|
|
112
|
+
for (let i = 0; i < msgs.length; i++) {
|
|
113
|
+
const current = msgs[i]
|
|
114
|
+
|
|
115
|
+
// Check if we can collate with the last message in result
|
|
116
|
+
if (result.length > 0) {
|
|
117
|
+
const lastCollated = result[result.length - 1]
|
|
118
|
+
const lastOriginal = lastCollated.collatedFrom
|
|
119
|
+
? lastCollated.collatedFrom[lastCollated.collatedFrom.length - 1]
|
|
120
|
+
: lastCollated
|
|
121
|
+
|
|
122
|
+
if (canCollate(current, lastOriginal)) {
|
|
123
|
+
// Collate: combine texts with newline separator
|
|
124
|
+
const existingText = lastCollated.text ?? ''
|
|
125
|
+
const currentText = current.text ?? ''
|
|
126
|
+
|
|
127
|
+
const collatedFrom = lastCollated.collatedFrom
|
|
128
|
+
? [...lastCollated.collatedFrom, current]
|
|
129
|
+
: [lastOriginal, current]
|
|
130
|
+
|
|
131
|
+
// Update the last message with combined text (joined by newline)
|
|
132
|
+
result[result.length - 1] = {
|
|
133
|
+
...lastCollated,
|
|
134
|
+
text: existingText + '\n' + currentText,
|
|
135
|
+
collatedFrom,
|
|
136
|
+
}
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Can't collate, add as new message
|
|
142
|
+
result.push(current as CollatedMessage)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return result
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
const originalCount = computed(() => {
|
|
149
|
+
const msgs = toValue(messages)
|
|
150
|
+
return msgs?.length ?? 0
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
const collatedCount = computed(() => {
|
|
154
|
+
return collatedMessages.value.length
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
return {
|
|
158
|
+
collatedMessages,
|
|
159
|
+
isCollationEnabled,
|
|
160
|
+
originalCount,
|
|
161
|
+
collatedCount,
|
|
162
|
+
}
|
|
163
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { inject, provide, InjectionKey, ComputedRef } from 'vue'
|
|
2
|
+
import type { IWebchatButton } from '../types'
|
|
3
|
+
|
|
4
|
+
export interface ImageContext {
|
|
5
|
+
onExpand: () => void
|
|
6
|
+
onClose: () => void
|
|
7
|
+
url: string
|
|
8
|
+
altText?: string
|
|
9
|
+
button?: IWebchatButton
|
|
10
|
+
isDownloadable: ComputedRef<boolean>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ImageContextKey: InjectionKey<ImageContext> = Symbol('ImageContext')
|
|
14
|
+
|
|
15
|
+
export function provideImageContext(context: ImageContext) {
|
|
16
|
+
provide(ImageContextKey, context)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useImageContext() {
|
|
20
|
+
const context = inject(ImageContextKey)
|
|
21
|
+
|
|
22
|
+
if (!context) {
|
|
23
|
+
throw new Error('useImageContext must be used within an ImageMessage component')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return context
|
|
27
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Message context composable
|
|
3
|
+
* Provides message context to child components using Vue's provide/inject
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { inject, provide, type InjectionKey } from 'vue'
|
|
7
|
+
import type { MessageContext } from '../types'
|
|
8
|
+
|
|
9
|
+
// Injection key for message context
|
|
10
|
+
export const MessageContextKey: InjectionKey<MessageContext> = Symbol('MessageContext')
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Provide message context to child components
|
|
14
|
+
* @param context - Message context to provide
|
|
15
|
+
*/
|
|
16
|
+
export function provideMessageContext(context: MessageContext) {
|
|
17
|
+
provide(MessageContextKey, context)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Use message context in a child component
|
|
22
|
+
* @returns Message context or throws error if not provided
|
|
23
|
+
*/
|
|
24
|
+
export function useMessageContext(): MessageContext {
|
|
25
|
+
const context = inject(MessageContextKey)
|
|
26
|
+
|
|
27
|
+
if (!context) {
|
|
28
|
+
console.error('useMessageContext: Context not provided. Make sure component is wrapped in MessageProvider.')
|
|
29
|
+
throw new Error('useMessageContext must be used within a message context provider')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return context
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Use message context with optional fallback
|
|
37
|
+
* @returns Message context or undefined if not provided
|
|
38
|
+
*/
|
|
39
|
+
export function useMessageContextOptional(): MessageContext | undefined {
|
|
40
|
+
return inject(MessageContextKey, undefined)
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTML sanitization composable
|
|
3
|
+
* Uses message context for config
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { computed } from 'vue'
|
|
7
|
+
import { useMessageContext } from './useMessageContext'
|
|
8
|
+
import { sanitizeHTMLWithConfig } from '../utils/sanitize'
|
|
9
|
+
|
|
10
|
+
export function useSanitize() {
|
|
11
|
+
const { config } = useMessageContext()
|
|
12
|
+
|
|
13
|
+
const isSanitizeEnabled = computed(() => !config?.settings?.layout?.disableHtmlContentSanitization)
|
|
14
|
+
const customAllowedHtmlTags = computed(() => config?.settings?.widgetSettings?.customAllowedHtmlTags)
|
|
15
|
+
|
|
16
|
+
const processHTML = (text: string): string => {
|
|
17
|
+
if (!isSanitizeEnabled.value) return text
|
|
18
|
+
return sanitizeHTMLWithConfig(text, customAllowedHtmlTags.value)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
processHTML,
|
|
23
|
+
isSanitizeEnabled,
|
|
24
|
+
}
|
|
25
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main entry point for @cognigy/chat-components-vue
|
|
3
|
+
* Exports all public components, composables, and utilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Main Component
|
|
7
|
+
export { default as Message } from './components/Message.vue'
|
|
8
|
+
|
|
9
|
+
// Common Components
|
|
10
|
+
export { default as TypingIndicator } from './components/common/TypingIndicator.vue'
|
|
11
|
+
export { default as ChatEvent } from './components/common/ChatEvent.vue'
|
|
12
|
+
export { default as Typography } from './components/common/Typography.vue'
|
|
13
|
+
export { default as ActionButtons } from './components/common/ActionButtons.vue'
|
|
14
|
+
export { default as ActionButton } from './components/common/ActionButton.vue'
|
|
15
|
+
export { default as ChatBubble } from './components/common/ChatBubble.vue'
|
|
16
|
+
|
|
17
|
+
// Message Types
|
|
18
|
+
export { default as TextMessage } from './components/messages/TextMessage.vue'
|
|
19
|
+
export { default as ImageMessage } from './components/messages/ImageMessage.vue'
|
|
20
|
+
export { default as VideoMessage } from './components/messages/VideoMessage.vue'
|
|
21
|
+
export { default as AudioMessage } from './components/messages/AudioMessage.vue'
|
|
22
|
+
export { default as TextWithButtons } from './components/messages/TextWithButtons.vue'
|
|
23
|
+
export { default as Gallery } from './components/messages/Gallery.vue'
|
|
24
|
+
export { default as List } from './components/messages/List.vue'
|
|
25
|
+
export { default as FileMessage } from './components/messages/FileMessage.vue'
|
|
26
|
+
export { default as DatePicker } from './components/messages/DatePicker.vue'
|
|
27
|
+
export { default as AdaptiveCard } from './components/messages/AdaptiveCard.vue'
|
|
28
|
+
|
|
29
|
+
// Components to be exported as they are created:
|
|
30
|
+
// export { default as Message } from './components/Message.vue'
|
|
31
|
+
|
|
32
|
+
// Composables
|
|
33
|
+
export { useMessageContext, useMessageContextOptional, provideMessageContext, MessageContextKey } from './composables/useMessageContext'
|
|
34
|
+
export { useSanitize } from './composables/useSanitize'
|
|
35
|
+
export { useImageContext, provideImageContext, ImageContextKey } from './composables/useImageContext'
|
|
36
|
+
export { useChannelPayload, type UseChannelPayloadReturn } from './composables/useChannelPayload'
|
|
37
|
+
export { useCollation, type UseCollationReturn, type CollatedMessage } from './composables/useCollation'
|
|
38
|
+
|
|
39
|
+
// SVG Icons
|
|
40
|
+
export { DownloadIcon, CloseIcon, VideoPlayIcon, AudioPlayIcon, AudioPauseIcon, ArrowBackIcon, LinkIcon } from './assets/svg'
|
|
41
|
+
|
|
42
|
+
// Utilities
|
|
43
|
+
export { match, getChannelPayload } from './utils/matcher'
|
|
44
|
+
export { sanitizeHTMLWithConfig, sanitizeContent } from './utils/sanitize'
|
|
45
|
+
export { configColorsToCssVariables } from './utils/theme'
|
|
46
|
+
export { getWebchatButtonLabel, interpolateString, getRandomId, moveFocusToMessageFocusTarget, replaceUrlsWithHTMLanchorElem, getBackgroundImage, getFileName, getFileExtension, getSizeLabel, isImageAttachment, VALID_IMAGE_MIME_TYPES } from './utils/helpers'
|
|
47
|
+
|
|
48
|
+
// Types
|
|
49
|
+
export type {
|
|
50
|
+
ChatConfig,
|
|
51
|
+
ChatSettings,
|
|
52
|
+
ChatTheme,
|
|
53
|
+
MessageProps,
|
|
54
|
+
MessageSender,
|
|
55
|
+
MessagePlugin,
|
|
56
|
+
MessagePluginOptions,
|
|
57
|
+
MessageContext,
|
|
58
|
+
IMessage,
|
|
59
|
+
IWebchatButton,
|
|
60
|
+
IWebchatQuickReply,
|
|
61
|
+
IWebchatTemplateAttachment,
|
|
62
|
+
IWebchatAttachmentElement,
|
|
63
|
+
IWebchatAudioAttachment,
|
|
64
|
+
IWebchatImageAttachment,
|
|
65
|
+
IWebchatVideoAttachment,
|
|
66
|
+
IUploadFileAttachmentData,
|
|
67
|
+
IDatePickerData,
|
|
68
|
+
IWebchatChannelPayload,
|
|
69
|
+
} from './types'
|
|
70
|
+
|
|
71
|
+
export type { TagVariant } from './components/common/Typography.vue'
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Vue chat components
|
|
3
|
+
* These mirror the React version types but adapted for Vue
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { IMessage } from '@cognigy/socket-client'
|
|
7
|
+
import type { Component } from 'vue'
|
|
8
|
+
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Extended Socket Client Types
|
|
11
|
+
// =============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extended IMessage with optional runtime properties not in base interface.
|
|
15
|
+
* The socket-client IMessage doesn't include 'id', but it may be present at runtime.
|
|
16
|
+
*/
|
|
17
|
+
export interface IMessageWithId extends IMessage {
|
|
18
|
+
id?: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Type guard to check if a message has an id property
|
|
23
|
+
*/
|
|
24
|
+
export function hasMessageId(message: IMessage): message is IMessageWithId & { id: string } {
|
|
25
|
+
return 'id' in message && typeof (message as IMessageWithId).id === 'string'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get message ID with fallback to timestamp-based ID
|
|
30
|
+
*/
|
|
31
|
+
export function getMessageId(message: IMessage): string {
|
|
32
|
+
if (hasMessageId(message)) {
|
|
33
|
+
return message.id
|
|
34
|
+
}
|
|
35
|
+
return `message-${message.timestamp ?? Date.now()}`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Adaptive card payload structure.
|
|
40
|
+
* Used for type narrowing when accessing adaptive card data from message payloads.
|
|
41
|
+
*/
|
|
42
|
+
export interface IAdaptiveCardPayload {
|
|
43
|
+
adaptiveCard: unknown
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Type guard to check if a payload contains an adaptive card
|
|
48
|
+
*/
|
|
49
|
+
export function isAdaptiveCardPayload(
|
|
50
|
+
payload: unknown
|
|
51
|
+
): payload is IAdaptiveCardPayload {
|
|
52
|
+
return (
|
|
53
|
+
typeof payload === 'object' &&
|
|
54
|
+
payload !== null &&
|
|
55
|
+
'adaptiveCard' in payload
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// =============================================================================
|
|
60
|
+
// Window Interface Extension (for testing)
|
|
61
|
+
// =============================================================================
|
|
62
|
+
|
|
63
|
+
declare global {
|
|
64
|
+
interface Window {
|
|
65
|
+
/** Test-only: Message ID for component testing */
|
|
66
|
+
__TEST_MESSAGE_ID__?: string
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Configuration for the chat components
|
|
72
|
+
* Mirrors IWebchatConfig from React version
|
|
73
|
+
*/
|
|
74
|
+
export interface ChatConfig {
|
|
75
|
+
active?: boolean
|
|
76
|
+
URLToken?: string
|
|
77
|
+
initialSessionId?: string
|
|
78
|
+
settings?: ChatSettings
|
|
79
|
+
isConfigLoaded?: boolean
|
|
80
|
+
isTimedOut?: boolean
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface ChatSettings {
|
|
84
|
+
layout?: {
|
|
85
|
+
title?: string
|
|
86
|
+
logoUrl?: string
|
|
87
|
+
botAvatarName?: string
|
|
88
|
+
botLogoUrl?: string
|
|
89
|
+
agentAvatarName?: string
|
|
90
|
+
agentLogoUrl?: string
|
|
91
|
+
disableHtmlContentSanitization?: boolean
|
|
92
|
+
enableGenericHTMLStyling?: boolean
|
|
93
|
+
disableBotOutputBorder?: boolean
|
|
94
|
+
botOutputMaxWidthPercentage?: number
|
|
95
|
+
disableUrlButtonSanitization?: boolean
|
|
96
|
+
dynamicImageAspectRatio?: boolean
|
|
97
|
+
showEngagementInChat?: boolean
|
|
98
|
+
}
|
|
99
|
+
colors?: {
|
|
100
|
+
// Primary action colors
|
|
101
|
+
primaryColor?: string
|
|
102
|
+
primaryColorHover?: string
|
|
103
|
+
primaryColorFocus?: string
|
|
104
|
+
primaryColorDisabled?: string
|
|
105
|
+
primaryContrastColor?: string
|
|
106
|
+
secondaryColor?: string
|
|
107
|
+
|
|
108
|
+
// Message bubble colors
|
|
109
|
+
botMessageColor?: string
|
|
110
|
+
botMessageContrastColor?: string
|
|
111
|
+
userMessageColor?: string
|
|
112
|
+
userMessageContrastColor?: string
|
|
113
|
+
agentMessageColor?: string
|
|
114
|
+
agentMessageContrastColor?: string
|
|
115
|
+
|
|
116
|
+
// Message bubble borders
|
|
117
|
+
borderBotMessage?: string
|
|
118
|
+
borderUserMessage?: string
|
|
119
|
+
borderAgentMessage?: string
|
|
120
|
+
|
|
121
|
+
// Link color
|
|
122
|
+
textLinkColor?: string
|
|
123
|
+
}
|
|
124
|
+
behavior?: {
|
|
125
|
+
renderMarkdown?: boolean
|
|
126
|
+
enableTypingIndicator?: boolean
|
|
127
|
+
messageDelay?: number
|
|
128
|
+
collateStreamedOutputs?: boolean
|
|
129
|
+
focusInputAfterPostback?: boolean
|
|
130
|
+
}
|
|
131
|
+
widgetSettings?: {
|
|
132
|
+
enableDefaultPreview?: boolean
|
|
133
|
+
enableStrictMessengerSync?: boolean
|
|
134
|
+
customAllowedHtmlTags?: string[]
|
|
135
|
+
enableAutoFocus?: boolean
|
|
136
|
+
disableRenderURLsAsLinks?: boolean
|
|
137
|
+
disableTextInputSanitization?: boolean
|
|
138
|
+
sourceDirectionMapping?: {
|
|
139
|
+
user?: 'incoming' | 'outgoing'
|
|
140
|
+
bot?: 'incoming' | 'outgoing'
|
|
141
|
+
agent?: 'incoming' | 'outgoing'
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
customTranslations?: {
|
|
145
|
+
ariaLabels?: {
|
|
146
|
+
messageHeader?: {
|
|
147
|
+
user?: string
|
|
148
|
+
bot?: string
|
|
149
|
+
timestamp?: string
|
|
150
|
+
}
|
|
151
|
+
opensInNewTab?: string
|
|
152
|
+
actionButtonPositionText?: string
|
|
153
|
+
viewImageInFullsize?: string
|
|
154
|
+
fullSizeImageViewerTitle?: string
|
|
155
|
+
downloadFullsizeImage?: string
|
|
156
|
+
closeFullsizeImageModal?: string
|
|
157
|
+
playAudio?: string
|
|
158
|
+
pauseAudio?: string
|
|
159
|
+
audioPlaybackProgress?: string
|
|
160
|
+
audioTimeRemaining?: string
|
|
161
|
+
downloadTranscript?: string
|
|
162
|
+
playVideo?: string
|
|
163
|
+
slide?: string
|
|
164
|
+
// Extensibility: consumers can add custom aria labels with any key
|
|
165
|
+
[key: string]: any
|
|
166
|
+
}
|
|
167
|
+
// Extensibility: consumers can add custom translation sections
|
|
168
|
+
[key: string]: any
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Theme configuration
|
|
174
|
+
*/
|
|
175
|
+
export interface ChatTheme {
|
|
176
|
+
primaryColor?: string
|
|
177
|
+
primaryColorHover?: string
|
|
178
|
+
secondaryColor?: string
|
|
179
|
+
backgroundBotMessage?: string
|
|
180
|
+
backgroundUserMessage?: string
|
|
181
|
+
textDark?: string
|
|
182
|
+
textLight?: string
|
|
183
|
+
fontFamily?: string
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Message sender callback type.
|
|
188
|
+
* The data parameter uses Record<string, any> because it passes through
|
|
189
|
+
* unchanged to @cognigy/socket-client - shape is determined by backend.
|
|
190
|
+
*/
|
|
191
|
+
export type MessageSender = (
|
|
192
|
+
text?: string,
|
|
193
|
+
data?: Record<string, any> | null,
|
|
194
|
+
options?: Partial<SendMessageOptions>
|
|
195
|
+
) => void
|
|
196
|
+
|
|
197
|
+
export interface SendMessageOptions {
|
|
198
|
+
label: string
|
|
199
|
+
collate: boolean
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// =============================================================================
|
|
203
|
+
// Component Prop Types
|
|
204
|
+
// =============================================================================
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Custom icon for action buttons.
|
|
208
|
+
* Can be a Vue component or a string identifier.
|
|
209
|
+
*/
|
|
210
|
+
export type CustomIcon = Component | string
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Analytics event data passed to analytics callbacks.
|
|
214
|
+
* Shape varies by event type, so we use Record<string, any> for flexibility.
|
|
215
|
+
*/
|
|
216
|
+
export type AnalyticsEventData = Record<string, any>
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Callback for emitting analytics events from components.
|
|
220
|
+
*/
|
|
221
|
+
export type AnalyticsEventCallback = (event: string, data?: AnalyticsEventData) => void
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Props for the Message component
|
|
225
|
+
*/
|
|
226
|
+
export interface MessageProps {
|
|
227
|
+
message: IMessage
|
|
228
|
+
action?: MessageSender
|
|
229
|
+
config?: ChatConfig
|
|
230
|
+
theme?: ChatTheme
|
|
231
|
+
prevMessage?: IMessage
|
|
232
|
+
disableHeader?: boolean
|
|
233
|
+
plugins?: MessagePlugin[]
|
|
234
|
+
onEmitAnalytics?: (event: string, payload?: unknown) => void
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Options for match rules and plugins
|
|
239
|
+
*/
|
|
240
|
+
export interface MatchRuleOptions {
|
|
241
|
+
/** If true, continue matching after this rule (allows multiple components) */
|
|
242
|
+
passthrough?: boolean
|
|
243
|
+
/** Render component in fullscreen mode */
|
|
244
|
+
fullscreen?: boolean
|
|
245
|
+
/** Render component at full width */
|
|
246
|
+
fullwidth?: boolean
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Internal match rule used by the matcher system.
|
|
251
|
+
* These define which message types map to which internal components.
|
|
252
|
+
* Components are resolved by name lookup in Message.vue.
|
|
253
|
+
*/
|
|
254
|
+
export interface MatchRule {
|
|
255
|
+
/** Unique name identifying this rule (maps to component in Message.vue) */
|
|
256
|
+
name: string
|
|
257
|
+
/** Function to determine if this rule matches a message */
|
|
258
|
+
match: (message: IMessage, config?: ChatConfig) => boolean
|
|
259
|
+
/** Optional rule behavior settings */
|
|
260
|
+
options?: MatchRuleOptions
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* External message plugin for custom message types.
|
|
265
|
+
* Users provide these to handle custom message formats.
|
|
266
|
+
*/
|
|
267
|
+
export interface MessagePlugin extends MatchRule {
|
|
268
|
+
/** Vue component to render for matched messages (required for external plugins) */
|
|
269
|
+
component: Component
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Union type for matcher results - can be internal rule or external plugin
|
|
274
|
+
*/
|
|
275
|
+
export type MatchResult = MatchRule | MessagePlugin
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Type guard to check if a match result is an external plugin with a component
|
|
279
|
+
*/
|
|
280
|
+
export function isMessagePlugin(rule: MatchResult): rule is MessagePlugin {
|
|
281
|
+
return 'component' in rule && isValidComponent(rule.component)
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Check if a value is a valid Vue component (not null/undefined/empty)
|
|
286
|
+
*/
|
|
287
|
+
export function isValidComponent(component: unknown): component is Component {
|
|
288
|
+
if (!component) return false
|
|
289
|
+
if (typeof component === 'function') return true
|
|
290
|
+
if (typeof component === 'object' && Object.keys(component).length > 0) return true
|
|
291
|
+
return false
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** @deprecated Use MatchRuleOptions instead */
|
|
295
|
+
export type MessagePluginOptions = MatchRuleOptions
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Context provided to message components
|
|
299
|
+
*/
|
|
300
|
+
export interface MessageContext {
|
|
301
|
+
message: IMessage
|
|
302
|
+
config?: ChatConfig
|
|
303
|
+
theme?: ChatTheme
|
|
304
|
+
action?: MessageSender
|
|
305
|
+
onEmitAnalytics?: (event: string, payload?: unknown) => void
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* DatePicker plugin data interface
|
|
310
|
+
*/
|
|
311
|
+
export interface IDatePickerData {
|
|
312
|
+
openPickerButtonText?: string
|
|
313
|
+
submitButtonText?: string
|
|
314
|
+
eventName?: string
|
|
315
|
+
dateFormat?: string
|
|
316
|
+
defaultDate?: string
|
|
317
|
+
minDate?: string
|
|
318
|
+
maxDate?: string
|
|
319
|
+
mode?: 'single' | 'multiple' | 'range'
|
|
320
|
+
enableTime?: boolean
|
|
321
|
+
time_24hr?: boolean
|
|
322
|
+
noCalendar?: boolean
|
|
323
|
+
weekNumbers?: boolean
|
|
324
|
+
locale?: string
|
|
325
|
+
enable_disable?: string[]
|
|
326
|
+
function_enable_disable?: string
|
|
327
|
+
wantDisable?: boolean
|
|
328
|
+
defaultHour?: number
|
|
329
|
+
defaultMinute?: number
|
|
330
|
+
hourIncrement?: number
|
|
331
|
+
minuteIncrement?: number
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Export types from @cognigy/socket-client for convenience
|
|
336
|
+
*/
|
|
337
|
+
export type { IMessage } from '@cognigy/socket-client'
|
|
338
|
+
export type {
|
|
339
|
+
IWebchatButton,
|
|
340
|
+
IWebchatQuickReply,
|
|
341
|
+
IWebchatTemplateAttachment,
|
|
342
|
+
IWebchatAttachmentElement,
|
|
343
|
+
IWebchatAudioAttachment,
|
|
344
|
+
IWebchatImageAttachment,
|
|
345
|
+
IWebchatVideoAttachment,
|
|
346
|
+
IUploadFileAttachmentData,
|
|
347
|
+
} from '@cognigy/socket-client'
|
|
348
|
+
|
|
349
|
+
// =============================================================================
|
|
350
|
+
// Webchat Channel Payload Types
|
|
351
|
+
// =============================================================================
|
|
352
|
+
|
|
353
|
+
// Import the types we need for the interface
|
|
354
|
+
import type {
|
|
355
|
+
IWebchatQuickReply as QuickReply,
|
|
356
|
+
IWebchatTemplateAttachment as TemplateAttachment,
|
|
357
|
+
IWebchatAudioAttachment as AudioAttachment,
|
|
358
|
+
IWebchatImageAttachment as ImageAttachment,
|
|
359
|
+
IWebchatVideoAttachment as VideoAttachment,
|
|
360
|
+
} from '@cognigy/socket-client'
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Webchat channel payload structure
|
|
364
|
+
* This is the shape of _webchat, _facebook, or _defaultPreview in message.data._cognigy
|
|
365
|
+
*/
|
|
366
|
+
export interface IWebchatChannelPayload {
|
|
367
|
+
message?: {
|
|
368
|
+
text?: string
|
|
369
|
+
quick_replies?: QuickReply[]
|
|
370
|
+
attachment?: TemplateAttachment | AudioAttachment | ImageAttachment | VideoAttachment
|
|
371
|
+
}
|
|
372
|
+
adaptiveCard?: unknown
|
|
373
|
+
}
|