@servicetitan/titan-chat-ui 1.1.2 → 2.0.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/CHANGELOG.md +12 -0
- package/dist/components/chat/__tests-cy__/chat-messages.test.js +69 -1
- package/dist/components/chat/__tests-cy__/chat-messages.test.js.map +1 -1
- package/dist/components/chat/chat-input.js +2 -2
- package/dist/components/chat/chat-input.js.map +1 -1
- package/dist/components/chat/chat-log.d.ts +8 -0
- package/dist/components/chat/chat-log.d.ts.map +1 -0
- package/dist/components/chat/chat-log.js +15 -0
- package/dist/components/chat/chat-log.js.map +1 -0
- package/dist/components/chat/chat-message-template-agent.d.ts.map +1 -1
- package/dist/components/chat/chat-message-template-agent.js +2 -2
- package/dist/components/chat/chat-message-template-agent.js.map +1 -1
- package/dist/components/chat/chat-message-template-user.js +2 -2
- package/dist/components/chat/chat-message-template-user.js.map +1 -1
- package/dist/components/chat/chat-message.d.ts +1 -5
- package/dist/components/chat/chat-message.d.ts.map +1 -1
- package/dist/components/chat/chat-message.js +4 -4
- package/dist/components/chat/chat-message.js.map +1 -1
- package/dist/components/chat/chat-messages.d.ts +3 -0
- package/dist/components/chat/chat-messages.d.ts.map +1 -1
- package/dist/components/chat/chat-messages.js +35 -7
- package/dist/components/chat/chat-messages.js.map +1 -1
- package/dist/components/chat/chat.d.ts.map +1 -1
- package/dist/components/chat/chat.js +3 -1
- package/dist/components/chat/chat.js.map +1 -1
- package/dist/components/messages/__tests-cy__/message-agent.test.js +1 -1
- package/dist/components/messages/__tests-cy__/message-agent.test.js.map +1 -1
- package/dist/components/messages/message-agent.d.ts +1 -0
- package/dist/components/messages/message-agent.d.ts.map +1 -1
- package/dist/components/messages/message-agent.js +2 -4
- package/dist/components/messages/message-agent.js.map +1 -1
- package/dist/components/messages/message-agent.module.less +29 -37
- package/dist/components/messages/message-footer.d.ts +1 -1
- package/dist/components/messages/message-footer.d.ts.map +1 -1
- package/dist/components/messages/message-footer.js +1 -1
- package/dist/components/messages/message-footer.js.map +1 -1
- package/dist/components/messages/message-user.js +2 -2
- package/dist/components/messages/message-user.js.map +1 -1
- package/dist/components/messages/message-user.module.less +3 -6
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/models/chat-customizations.d.ts +14 -6
- package/dist/models/chat-customizations.d.ts.map +1 -1
- package/dist/models/support-chat.d.ts +4 -4
- package/dist/models/support-chat.d.ts.map +1 -1
- package/dist/models/support-chat.js +18 -0
- package/dist/models/support-chat.js.map +1 -1
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts +6 -1
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.d.ts.map +1 -1
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.js +30 -0
- package/dist/stores/__mocks-cy__/chat-ui.store.mock.js.map +1 -1
- package/dist/stores/__tests__/chat-input.store.test.d.ts +2 -0
- package/dist/stores/__tests__/chat-input.store.test.d.ts.map +1 -0
- package/dist/stores/__tests__/chat-input.store.test.js +32 -0
- package/dist/stores/__tests__/chat-input.store.test.js.map +1 -0
- package/dist/stores/chat-ui.store.d.ts +19 -10
- package/dist/stores/chat-ui.store.d.ts.map +1 -1
- package/dist/stores/chat-ui.store.js +47 -48
- package/dist/stores/chat-ui.store.js.map +1 -1
- package/dist/utils/test-utils.d.ts +5 -0
- package/dist/utils/test-utils.d.ts.map +1 -0
- package/dist/utils/test-utils.js +17 -0
- package/dist/utils/test-utils.js.map +1 -0
- package/package.json +2 -2
- package/src/components/chat/__tests-cy__/chat-messages.test.tsx +78 -1
- package/src/components/chat/chat-input.tsx +4 -4
- package/src/components/chat/chat-log.tsx +29 -0
- package/src/components/chat/chat-message-template-agent.tsx +7 -3
- package/src/components/chat/chat-message-template-user.tsx +3 -3
- package/src/components/chat/chat-message.tsx +58 -52
- package/src/components/chat/chat-messages.tsx +55 -11
- package/src/components/chat/chat.tsx +14 -8
- package/src/components/messages/__tests-cy__/message-agent.test.tsx +1 -1
- package/src/components/messages/message-agent.module.less +29 -37
- package/src/components/messages/message-agent.module.less.d.ts +0 -1
- package/src/components/messages/message-agent.tsx +4 -4
- package/src/components/messages/message-footer.tsx +4 -3
- package/src/components/messages/message-user.module.less +3 -6
- package/src/components/messages/message-user.tsx +5 -5
- package/src/index.ts +3 -0
- package/src/models/chat-customizations.ts +17 -6
- package/src/models/support-chat.ts +22 -10
- package/src/stores/__mocks-cy__/chat-ui.store.mock.ts +6 -0
- package/src/stores/__tests__/chat-input.store.test.ts +39 -0
- package/src/stores/chat-ui.store.ts +54 -55
- package/src/utils/test-utils.ts +22 -0
- package/tsconfig.json +1 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,23 +1,67 @@
|
|
|
1
1
|
import { Stack } from '@servicetitan/design-system';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { CHAT_UI_STORE_TOKEN } from '../../stores';
|
|
2
|
+
import { FC, useMemo } from 'react';
|
|
3
|
+
import { ChatMessageModelBase } from '../../models';
|
|
4
|
+
import { formatChatMessageDate } from '../../utils/text-utils';
|
|
6
5
|
import { ChatMessage } from './chat-message';
|
|
7
6
|
import { ChatMessageTyping } from './chat-message-typing';
|
|
8
7
|
|
|
9
8
|
interface IChatMessagesProps {
|
|
10
9
|
className?: string;
|
|
10
|
+
messages: ChatMessageModelBase[];
|
|
11
|
+
showTypingIndicator?: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export const ChatMessages: FC<IChatMessagesProps> =
|
|
14
|
-
|
|
14
|
+
export const ChatMessages: FC<IChatMessagesProps> = ({
|
|
15
|
+
className,
|
|
16
|
+
messages,
|
|
17
|
+
showTypingIndicator,
|
|
18
|
+
}) => {
|
|
19
|
+
// Split messages into chunks divided by participant name and message time (with 1 minute accuracy)
|
|
20
|
+
const messagesChunks = useMemo(
|
|
21
|
+
() =>
|
|
22
|
+
messages.reduce((acc: ChatMessageModelBase[][][], message) => {
|
|
23
|
+
if (!acc.length) {
|
|
24
|
+
acc.push([[message]]);
|
|
25
|
+
return acc;
|
|
26
|
+
}
|
|
27
|
+
const lastMessage = acc.at(-1)!.at(-1)!.at(-1)!;
|
|
28
|
+
if (lastMessage.participant.name !== message.participant.name) {
|
|
29
|
+
acc.push([[message]]);
|
|
30
|
+
} else if (
|
|
31
|
+
formatChatMessageDate(lastMessage.timestamp) !==
|
|
32
|
+
formatChatMessageDate(message.timestamp)
|
|
33
|
+
) {
|
|
34
|
+
acc.at(-1)!.push([message]);
|
|
35
|
+
} else {
|
|
36
|
+
acc.at(-1)!.at(-1)!.push(message);
|
|
37
|
+
}
|
|
38
|
+
return acc;
|
|
39
|
+
}, []),
|
|
40
|
+
[messages]
|
|
41
|
+
);
|
|
42
|
+
|
|
15
43
|
return (
|
|
16
44
|
<Stack direction="column" spacing="2" className={className} data-cy="titan-chat-messages">
|
|
17
|
-
{
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
45
|
+
{messagesChunks
|
|
46
|
+
.map(sameParticipantMessages => {
|
|
47
|
+
// Set omit avatar for all messages except the first one
|
|
48
|
+
const firstParticipantMessage = sameParticipantMessages[0][0];
|
|
49
|
+
return sameParticipantMessages.map(sameDateMessages => {
|
|
50
|
+
const lastTimestampMessage = sameDateMessages.at(-1)!;
|
|
51
|
+
return sameDateMessages.map(message => {
|
|
52
|
+
return (
|
|
53
|
+
<ChatMessage
|
|
54
|
+
key={message.id}
|
|
55
|
+
message={message}
|
|
56
|
+
omitAvatar={message !== firstParticipantMessage}
|
|
57
|
+
omitTimestamp={message !== lastTimestampMessage}
|
|
58
|
+
/>
|
|
59
|
+
);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
})
|
|
63
|
+
.flat(2)}
|
|
64
|
+
{showTypingIndicator && <ChatMessageTyping />}
|
|
21
65
|
</Stack>
|
|
22
66
|
);
|
|
23
|
-
}
|
|
67
|
+
};
|
|
@@ -20,6 +20,7 @@ export const Chat: FC<IChatProps> = observer(({ className, customizations }) =>
|
|
|
20
20
|
const [chatUiStore] = useDependencies(CHAT_UI_STORE_TOKEN);
|
|
21
21
|
|
|
22
22
|
const footerComponent = customizations?.footerComponent;
|
|
23
|
+
const loadingComponent = customizations?.loadingComponent;
|
|
23
24
|
|
|
24
25
|
useEffect(() => {
|
|
25
26
|
chatUiStore.setCustomizationContext(customizations);
|
|
@@ -36,18 +37,23 @@ export const Chat: FC<IChatProps> = observer(({ className, customizations }) =>
|
|
|
36
37
|
return (
|
|
37
38
|
<Stack direction="column" className={className} data-cy="titan-chat">
|
|
38
39
|
{chatUiStore.isStarting ? (
|
|
39
|
-
<ChatConnecting className="p-3" />
|
|
40
|
+
(loadingComponent ?? <ChatConnecting className="p-x-3 p-y-2" />)
|
|
40
41
|
) : (
|
|
41
42
|
<Fragment>
|
|
42
|
-
<div ref={scrollRef} className="flex-grow-1 of-y-auto">
|
|
43
|
-
<ChatMessages
|
|
43
|
+
<div ref={scrollRef} className="p-x-3 p-y-2 flex-grow-1 of-y-auto">
|
|
44
|
+
<ChatMessages
|
|
45
|
+
messages={chatUiStore.messages}
|
|
46
|
+
showTypingIndicator={chatUiStore.isAgentTyping}
|
|
47
|
+
/>
|
|
44
48
|
</div>
|
|
45
49
|
<ChatNotifications />
|
|
46
|
-
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
{!chatUiStore.customizations?.input?.isDisabled && (
|
|
51
|
+
<Stack className="p-x-3 p-y-2" direction="column" spacing="2">
|
|
52
|
+
<ChatInputFile className="box-sizing-border-box border-radius-1 border border-style-dashed" />
|
|
53
|
+
<ChatInput />
|
|
54
|
+
{Boolean(footerComponent) && footerComponent}
|
|
55
|
+
</Stack>
|
|
56
|
+
)}
|
|
51
57
|
</Fragment>
|
|
52
58
|
)}
|
|
53
59
|
</Stack>
|
|
@@ -44,7 +44,7 @@ describe('[MessageAgent]', () => {
|
|
|
44
44
|
<BodyText>message agent content</BodyText>
|
|
45
45
|
</MessageAgent>
|
|
46
46
|
);
|
|
47
|
-
cy.getCy('chat-message-agent-center').should('
|
|
47
|
+
cy.getCy('chat-message-agent-center').should('contain.text', 'message agent content');
|
|
48
48
|
});
|
|
49
49
|
|
|
50
50
|
it('should render with custom data-cy', () => {
|
|
@@ -9,51 +9,43 @@
|
|
|
9
9
|
max-width: 100%;
|
|
10
10
|
column-gap: @spacing-2;
|
|
11
11
|
row-gap: @spacing-1;
|
|
12
|
-
grid-template-areas:
|
|
13
|
-
|
|
14
|
-
'. footer .';
|
|
15
|
-
grid-template-rows: auto auto;
|
|
12
|
+
grid-template-areas: 'avatar content .';
|
|
13
|
+
grid-template-rows: auto;
|
|
16
14
|
grid-template-columns: minmax(@spacing-5, auto) 1fr minmax(@spacing-5, auto);
|
|
17
15
|
|
|
18
16
|
&.fullWidth {
|
|
19
|
-
width: 100%;
|
|
20
|
-
grid-template-areas:
|
|
21
|
-
|
|
22
|
-
'. footer ';
|
|
23
|
-
grid-template-rows: auto auto;
|
|
17
|
+
//width: 100%;
|
|
18
|
+
grid-template-areas: 'avatar content';
|
|
19
|
+
grid-template-rows: auto;
|
|
24
20
|
grid-template-columns: minmax(@spacing-5, auto) 1fr;
|
|
25
21
|
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
.messageFooter {
|
|
29
|
-
grid-area: footer;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.messageAvatar {
|
|
33
|
-
grid-area: avatar;
|
|
34
|
-
}
|
|
35
22
|
|
|
36
|
-
.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
overflow-wrap: anywhere;
|
|
40
|
-
justify-self: flex-start;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.messageBubble {
|
|
44
|
-
color: @color-neutral-200;
|
|
45
|
-
padding: @spacing-2 @spacing-3;
|
|
46
|
-
background-color: @color-blue-100;
|
|
47
|
-
position: relative;
|
|
48
|
-
border-radius: @spacing-0 @spacing-3 @spacing-3 @spacing-3;
|
|
49
|
-
box-sizing: border-box;
|
|
23
|
+
.messageAvatar {
|
|
24
|
+
grid-area: avatar;
|
|
25
|
+
}
|
|
50
26
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
.messageContent {
|
|
28
|
+
max-width: 100%;
|
|
29
|
+
grid-area: content;
|
|
30
|
+
overflow-wrap: anywhere;
|
|
31
|
+
justify-self: flex-start;
|
|
32
|
+
}
|
|
54
33
|
|
|
55
|
-
|
|
56
|
-
|
|
34
|
+
.messageBubble {
|
|
35
|
+
color: @color-neutral-200;
|
|
36
|
+
padding: @spacing-2 @spacing-3;
|
|
37
|
+
background-color: @color-blue-100;
|
|
38
|
+
position: relative;
|
|
39
|
+
border-radius: @spacing-0 @spacing-3 @spacing-3 @spacing-3;
|
|
40
|
+
box-sizing: border-box;
|
|
41
|
+
|
|
42
|
+
&.error {
|
|
43
|
+
color: @color-neutral-400;
|
|
44
|
+
background-color: @color-red-100;
|
|
45
|
+
|
|
46
|
+
&::after {
|
|
47
|
+
background: @color-red-200;
|
|
48
|
+
}
|
|
57
49
|
}
|
|
58
50
|
}
|
|
59
51
|
}
|
|
@@ -11,6 +11,7 @@ export interface IMessageAgentProps extends IDataCyProps {
|
|
|
11
11
|
isError?: boolean;
|
|
12
12
|
fullWidth?: boolean;
|
|
13
13
|
subtle?: boolean;
|
|
14
|
+
omitAvatar?: boolean;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export const MessageAgent: FC<PropsWithChildren<IMessageAgentProps>> = ({
|
|
@@ -19,6 +20,7 @@ export const MessageAgent: FC<PropsWithChildren<IMessageAgentProps>> = ({
|
|
|
19
20
|
fullWidth,
|
|
20
21
|
isError,
|
|
21
22
|
messageFooter,
|
|
23
|
+
omitAvatar,
|
|
22
24
|
subtle,
|
|
23
25
|
...rest
|
|
24
26
|
}) => {
|
|
@@ -33,14 +35,12 @@ export const MessageAgent: FC<PropsWithChildren<IMessageAgentProps>> = ({
|
|
|
33
35
|
data-cy2={dataCy2}
|
|
34
36
|
>
|
|
35
37
|
<div className={Styles.messageAvatar}>
|
|
36
|
-
{avatar ? <MessageAvatar {...avatar} /> : <div />}
|
|
38
|
+
{!omitAvatar && avatar ? <MessageAvatar {...avatar} /> : <div />}
|
|
37
39
|
</div>
|
|
38
40
|
<Stack
|
|
39
41
|
direction="column"
|
|
40
42
|
spacing="1"
|
|
41
|
-
className={classNames(Styles.messageContent
|
|
42
|
-
'w-100': Boolean(fullWidth),
|
|
43
|
-
})}
|
|
43
|
+
className={classNames(Styles.messageContent)}
|
|
44
44
|
data-cy={`${dataCy}-center`}
|
|
45
45
|
>
|
|
46
46
|
{subtle ? (
|
|
@@ -3,15 +3,16 @@ import { FC } from 'react';
|
|
|
3
3
|
import { formatChatMessageDate } from '../../utils/text-utils';
|
|
4
4
|
|
|
5
5
|
export interface IMessageFooterProps {
|
|
6
|
-
timestamp
|
|
6
|
+
timestamp?: Date;
|
|
7
7
|
name?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
export const MessageFooter: FC<IMessageFooterProps> = ({ name, timestamp }) => {
|
|
11
11
|
return (
|
|
12
12
|
<Eyebrow>
|
|
13
|
-
{name &&
|
|
14
|
-
{
|
|
13
|
+
{Boolean(name) && name}
|
|
14
|
+
{name && timestamp && ' • '}
|
|
15
|
+
{timestamp && formatChatMessageDate(timestamp)}
|
|
15
16
|
</Eyebrow>
|
|
16
17
|
);
|
|
17
18
|
};
|
|
@@ -3,18 +3,15 @@
|
|
|
3
3
|
/* stylelint-disable declaration-property-value-no-unknown */
|
|
4
4
|
.messageRoot {
|
|
5
5
|
display: grid;
|
|
6
|
-
grid-template-areas:
|
|
7
|
-
|
|
8
|
-
'. footer';
|
|
9
|
-
grid-template-rows: auto auto;
|
|
6
|
+
grid-template-areas: '. content';
|
|
7
|
+
grid-template-rows: auto;
|
|
10
8
|
grid-template-columns: minmax(@spacing-5, auto) 1fr;
|
|
11
9
|
column-gap: @spacing-2;
|
|
12
10
|
row-gap: @spacing-1;
|
|
13
11
|
}
|
|
14
12
|
|
|
15
13
|
.messageFooter {
|
|
16
|
-
|
|
17
|
-
justify-self: end;
|
|
14
|
+
text-align: right;
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
.messageContent {
|
|
@@ -38,12 +38,12 @@ export const MessageUser: FC<PropsWithChildren<IMessageUserProps>> = ({
|
|
|
38
38
|
>
|
|
39
39
|
{children}
|
|
40
40
|
</div>
|
|
41
|
+
{Boolean(messageFooter) && (
|
|
42
|
+
<div className={Styles.messageFooter} data-cy={`${dataCy}-footer`}>
|
|
43
|
+
{messageFooter}
|
|
44
|
+
</div>
|
|
45
|
+
)}
|
|
41
46
|
</Stack>
|
|
42
|
-
{Boolean(messageFooter) && (
|
|
43
|
-
<div className={Styles.messageFooter} data-cy={`${dataCy}-footer`}>
|
|
44
|
-
{messageFooter}
|
|
45
|
-
</div>
|
|
46
|
-
)}
|
|
47
47
|
</div>
|
|
48
48
|
);
|
|
49
49
|
};
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
export { ChatMessages } from './components/chat/chat-messages';
|
|
2
|
+
export { Chat, IChatProps } from './components/chat/chat';
|
|
3
|
+
export { ChatLog } from './components/chat/chat-log';
|
|
1
4
|
export { MessageAgent, IMessageAgentProps } from './components/messages/message-agent';
|
|
2
5
|
export { MessageUser, IMessageUserProps } from './components/messages/message-user';
|
|
3
6
|
export { MessageSystem, IMessageSystemProps } from './components/messages/message-system';
|
|
@@ -4,11 +4,15 @@ import { ChatMessageModelBase } from './support-chat';
|
|
|
4
4
|
|
|
5
5
|
export interface IChatMessageProps<T extends ChatMessageModelBase = ChatMessageModelBase> {
|
|
6
6
|
message: T;
|
|
7
|
+
omitAvatar?: boolean;
|
|
8
|
+
omitTimestamp?: boolean;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
|
-
export interface ChatMessageTemplateCustomization
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
export interface ChatMessageTemplateCustomization<
|
|
12
|
+
T extends ChatMessageModelBase = ChatMessageModelBase,
|
|
13
|
+
> {
|
|
14
|
+
component: FC<PropsWithChildren<IChatMessageProps<T>>>;
|
|
15
|
+
predicate: (message: T) => boolean;
|
|
12
16
|
}
|
|
13
17
|
|
|
14
18
|
export type ChatMessageTemplateCustomizations = Partial<{
|
|
@@ -16,19 +20,26 @@ export type ChatMessageTemplateCustomizations = Partial<{
|
|
|
16
20
|
user: ChatMessageTemplateCustomization;
|
|
17
21
|
}>;
|
|
18
22
|
|
|
19
|
-
export interface ChatMessageCustomization {
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
export interface ChatMessageCustomization<T extends ChatMessageModelBase = ChatMessageModelBase> {
|
|
24
|
+
predicate: (message: T) => boolean;
|
|
25
|
+
component?: FC<IChatMessageProps<T>>;
|
|
22
26
|
isSystem?: boolean; // System messages are messages that are not sent by the user or the agent and rendered without chat bubbles
|
|
27
|
+
isIgnored?: boolean;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
export interface ChatMessageTypingCustomization {
|
|
26
31
|
component: FC<IMessageTypingProps>;
|
|
27
32
|
}
|
|
28
33
|
|
|
34
|
+
export interface ChatInputCustomization {
|
|
35
|
+
isDisabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
29
38
|
export type ChatCustomizations = Partial<{
|
|
30
39
|
messageTemplates: ChatMessageTemplateCustomizations;
|
|
31
40
|
messages: ChatMessageCustomization[];
|
|
32
41
|
messageTyping: ChatMessageTypingCustomization;
|
|
42
|
+
input: ChatInputCustomization;
|
|
33
43
|
footerComponent: ReactNode;
|
|
44
|
+
loadingComponent: ReactNode;
|
|
34
45
|
}>;
|
|
@@ -28,14 +28,6 @@ export interface ChatParticipantModel {
|
|
|
28
28
|
icon: ChatParticipantIcon;
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
export type ChatMessageModelType =
|
|
32
|
-
| 'welcome'
|
|
33
|
-
| 'message'
|
|
34
|
-
| 'timeout'
|
|
35
|
-
| 'file'
|
|
36
|
-
| 'askSupport'
|
|
37
|
-
| 'askSupportButtons';
|
|
38
|
-
|
|
39
31
|
export interface ChatConfiguration {
|
|
40
32
|
agentName?: string;
|
|
41
33
|
agentIcon?: ChatParticipantIcon;
|
|
@@ -44,7 +36,7 @@ export interface ChatConfiguration {
|
|
|
44
36
|
export interface ChatMessageModelBase {
|
|
45
37
|
id: string;
|
|
46
38
|
timestamp: Date;
|
|
47
|
-
type:
|
|
39
|
+
type: string;
|
|
48
40
|
participant: ChatParticipantModel;
|
|
49
41
|
state: ChatMessageState;
|
|
50
42
|
data?: any;
|
|
@@ -69,7 +61,7 @@ export interface ChatMessageModelFile extends ChatMessageModelBase {
|
|
|
69
61
|
fileName: string;
|
|
70
62
|
}
|
|
71
63
|
|
|
72
|
-
type ChatErrorRecoverStrategy =
|
|
64
|
+
export type ChatErrorRecoverStrategy =
|
|
73
65
|
| {
|
|
74
66
|
recoverButtonTitle: string;
|
|
75
67
|
}
|
|
@@ -94,6 +86,26 @@ export class ChatError<T = any> extends Error {
|
|
|
94
86
|
this.data = options?.data;
|
|
95
87
|
Object.setPrototypeOf(this, ChatError.prototype);
|
|
96
88
|
}
|
|
89
|
+
|
|
90
|
+
toJSON(): Record<string, any> {
|
|
91
|
+
return {
|
|
92
|
+
name: this.name,
|
|
93
|
+
message: this.message,
|
|
94
|
+
title: this.title,
|
|
95
|
+
recoverStrategy: this.recoverStrategy,
|
|
96
|
+
data: this.data,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
fromJSON(json: Record<string, any>): ChatError<T> {
|
|
101
|
+
const error = new ChatError<T>(json.message, {
|
|
102
|
+
title: json.title,
|
|
103
|
+
recoverStrategy: json.recoverStrategy,
|
|
104
|
+
data: json.data,
|
|
105
|
+
});
|
|
106
|
+
error.name = json.name;
|
|
107
|
+
return error;
|
|
108
|
+
}
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
export enum ChatRunState {
|
|
@@ -2,6 +2,7 @@ import { FileDescriptor } from '@servicetitan/form';
|
|
|
2
2
|
import {
|
|
3
3
|
ChatConfiguration,
|
|
4
4
|
ChatCustomizations,
|
|
5
|
+
ChatEndReason,
|
|
5
6
|
ChatError,
|
|
6
7
|
ChatErrorOptions,
|
|
7
8
|
ChatMessageModelBase,
|
|
@@ -84,6 +85,8 @@ export class ChatUiStoreMock implements IChatUiStore {
|
|
|
84
85
|
off: <T>(event: string, listener: ChatUiEventListener<T>) => void = cy.stub();
|
|
85
86
|
on: <T>(event: string, listener: ChatUiEventListener<T>) => void = cy.stub();
|
|
86
87
|
resetError: (runState: ChatRunState) => void = cy.stub();
|
|
88
|
+
reset: (resetState?: boolean) => void = cy.stub();
|
|
89
|
+
resetFile: () => void = cy.stub();
|
|
87
90
|
setAgentTyping: (typing: boolean) => void = cy.stub();
|
|
88
91
|
setChatError: (chatError: ChatError) => void = cy.stub();
|
|
89
92
|
setError: <T = any>(message: string, options?: ChatErrorOptions<T>) => void = cy.stub();
|
|
@@ -104,4 +107,7 @@ export class ChatUiStoreMock implements IChatUiStore {
|
|
|
104
107
|
setStatus: (status: ChatRunState) => void = cy.stub();
|
|
105
108
|
setTimer: (timer?: ChatTimer) => void = cy.stub();
|
|
106
109
|
restartTimers: () => Promise<void> = cy.stub();
|
|
110
|
+
restart: () => Promise<void> = cy.stub();
|
|
111
|
+
cancelChat: () => Promise<void> = cy.stub();
|
|
112
|
+
endChat: (endReason?: ChatEndReason) => Promise<void> = cy.stub();
|
|
107
113
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { expect } from '@jest/globals';
|
|
2
|
+
import { Container } from '@servicetitan/react-ioc';
|
|
3
|
+
import { initTestContainer } from '../../utils/test-utils';
|
|
4
|
+
import { ChatInputStore } from '../chat-input.store';
|
|
5
|
+
|
|
6
|
+
const initContainer = initTestContainer(ChatInputStore, () => {});
|
|
7
|
+
|
|
8
|
+
describe('[ChatInputStore]', () => {
|
|
9
|
+
let container: Container;
|
|
10
|
+
let store: ChatInputStore;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
container = initContainer();
|
|
14
|
+
store = container.get(ChatInputStore);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('should have proper form state', async () => {
|
|
18
|
+
expect(store.formState.$.message).toBeDefined();
|
|
19
|
+
expect(store.formState.$.message.value).toBe('');
|
|
20
|
+
const validateResult = await store.formState.validate();
|
|
21
|
+
expect(validateResult.hasError).toBe(false);
|
|
22
|
+
|
|
23
|
+
store.formState.$.message.onChange('test');
|
|
24
|
+
expect(store.formState.$.message.value).toBe('test');
|
|
25
|
+
const validateResult2 = await store.formState.validate();
|
|
26
|
+
expect(validateResult2.hasError).toBe(false);
|
|
27
|
+
|
|
28
|
+
store.formState.$.message.onChange('a'.repeat(6000));
|
|
29
|
+
expect(store.formState.$.message.value).toBe('a'.repeat(6000));
|
|
30
|
+
const validateResult3 = await store.formState.validate();
|
|
31
|
+
expect(validateResult3.hasError).toBe(false);
|
|
32
|
+
|
|
33
|
+
store.formState.$.message.onChange('a'.repeat(6001));
|
|
34
|
+
expect(store.formState.$.message.value).toBe('a'.repeat(6001));
|
|
35
|
+
const validateResult4 = await store.formState.validate();
|
|
36
|
+
expect(validateResult4.hasError).toBe(true);
|
|
37
|
+
expect(store.formState.$.message.error).toBe('Message character max is 6000.');
|
|
38
|
+
});
|
|
39
|
+
});
|