@servicetitan/titan-chat-ui 1.1.1 → 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 +24 -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/__mocks__/support-chat.mock.d.ts +9 -0
- package/dist/models/__mocks__/support-chat.mock.d.ts.map +1 -0
- package/dist/models/__mocks__/support-chat.mock.js +82 -0
- package/dist/models/__mocks__/support-chat.mock.js.map +1 -0
- package/dist/models/chat-customizations.d.ts +14 -6
- package/dist/models/chat-customizations.d.ts.map +1 -1
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.d.ts.map +1 -1
- package/dist/models/index.js +1 -0
- package/dist/models/index.js.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/__mocks__/support-chat.mock.ts +105 -0
- package/src/models/chat-customizations.ts +17 -6
- package/src/models/index.ts +1 -0
- 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
|
@@ -2,8 +2,8 @@ import { BodyText } from '@servicetitan/design-system';
|
|
|
2
2
|
import { useDependencies } from '@servicetitan/react-ioc';
|
|
3
3
|
import { observer } from 'mobx-react';
|
|
4
4
|
import { FC, PropsWithChildren, ReactNode } from 'react';
|
|
5
|
+
import { IChatMessageProps } from '../../models';
|
|
5
6
|
import {
|
|
6
|
-
ChatMessageModelBase,
|
|
7
7
|
ChatMessageModelFile,
|
|
8
8
|
ChatMessageModelText,
|
|
9
9
|
ChatMessageModelWelcome,
|
|
@@ -14,65 +14,71 @@ import { MessageContentText } from '../message-content/message-content-text';
|
|
|
14
14
|
import { ChatMessageTemplateAgent } from './chat-message-template-agent';
|
|
15
15
|
import { ChatMessageTemplateUser } from './chat-message-template-user';
|
|
16
16
|
|
|
17
|
-
interface IChatMessageProps {
|
|
18
|
-
message: ChatMessageModelBase;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
17
|
/**
|
|
22
18
|
* ChatMessage component provides a default way to render chat messages in the chat UI and contains only most generic
|
|
23
19
|
* components and templates/customizations rendering
|
|
24
20
|
*/
|
|
25
|
-
export const ChatMessage: FC<IChatMessageProps> = observer(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
export const ChatMessage: FC<IChatMessageProps> = observer(
|
|
22
|
+
({ message, omitAvatar, omitTimestamp }) => {
|
|
23
|
+
const [chatUiStore] = useDependencies(CHAT_UI_STORE_TOKEN);
|
|
24
|
+
const isAgent = message.participant.isAgent;
|
|
25
|
+
const { messageTemplates, messages } = chatUiStore.customizations;
|
|
26
|
+
const messageCustomization = messages?.find(c => c.predicate(message));
|
|
27
|
+
const isSystem = Boolean(messageCustomization?.isSystem);
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
29
|
+
let messageContentNode: ReactNode | undefined;
|
|
30
|
+
if (messageCustomization?.component) {
|
|
31
|
+
messageContentNode = <messageCustomization.component message={message} />;
|
|
32
|
+
} else {
|
|
33
|
+
// Default message content rendering
|
|
34
|
+
switch (message.type) {
|
|
35
|
+
case 'message':
|
|
36
|
+
case 'welcome':
|
|
37
|
+
messageContentNode = (
|
|
38
|
+
<MessageContentText
|
|
39
|
+
message={message as ChatMessageModelText | ChatMessageModelWelcome}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
break;
|
|
43
|
+
case 'file':
|
|
44
|
+
messageContentNode = (
|
|
45
|
+
<MessageContentFile message={message as ChatMessageModelFile} />
|
|
46
|
+
);
|
|
47
|
+
break;
|
|
48
|
+
default: {
|
|
49
|
+
messageContentNode = <BodyText>Unknown message type: {message.type}</BodyText>;
|
|
50
|
+
}
|
|
53
51
|
}
|
|
54
52
|
}
|
|
55
|
-
}
|
|
56
53
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
if (!messageContentNode || messageCustomization?.isIgnored) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
60
57
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
if (isSystem) {
|
|
59
|
+
return messageContentNode;
|
|
60
|
+
}
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
62
|
+
let ComponentTemplate: FC<PropsWithChildren<IChatMessageProps>>;
|
|
63
|
+
if (isAgent) {
|
|
64
|
+
const template = messageTemplates?.agent;
|
|
65
|
+
ComponentTemplate = template?.predicate(message)
|
|
66
|
+
? template.component
|
|
67
|
+
: ChatMessageTemplateAgent;
|
|
68
|
+
} else {
|
|
69
|
+
const template = messageTemplates?.user;
|
|
70
|
+
ComponentTemplate = template?.predicate(message)
|
|
71
|
+
? template.component
|
|
72
|
+
: ChatMessageTemplateUser;
|
|
73
|
+
}
|
|
74
|
+
return (
|
|
75
|
+
<ComponentTemplate
|
|
76
|
+
message={message}
|
|
77
|
+
omitAvatar={omitAvatar}
|
|
78
|
+
omitTimestamp={omitTimestamp}
|
|
79
|
+
>
|
|
80
|
+
{messageContentNode}
|
|
81
|
+
</ComponentTemplate>
|
|
82
|
+
);
|
|
76
83
|
}
|
|
77
|
-
|
|
78
|
-
});
|
|
84
|
+
);
|
|
@@ -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';
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChatMessageModelFile,
|
|
3
|
+
ChatMessageModelText,
|
|
4
|
+
ChatMessageModelTimeout,
|
|
5
|
+
ChatMessageModelWelcome,
|
|
6
|
+
ChatMessageState,
|
|
7
|
+
ChatParticipantIcon,
|
|
8
|
+
ChatParticipantModel,
|
|
9
|
+
} from '../support-chat';
|
|
10
|
+
|
|
11
|
+
export function mockChatAgent(overrides?: Partial<ChatParticipantModel>): ChatParticipantModel {
|
|
12
|
+
return {
|
|
13
|
+
isAgent: true,
|
|
14
|
+
name: 'agent name',
|
|
15
|
+
icon: ChatParticipantIcon.Bot,
|
|
16
|
+
...overrides,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function mockChatUser(overrides?: Partial<ChatParticipantModel>): ChatParticipantModel {
|
|
21
|
+
return {
|
|
22
|
+
isAgent: false,
|
|
23
|
+
name: 'user name',
|
|
24
|
+
icon: ChatParticipantIcon.Initials,
|
|
25
|
+
...overrides,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function mockChatMessageModelText(
|
|
30
|
+
isAgent: boolean,
|
|
31
|
+
overrides?: Partial<ChatMessageModelText>
|
|
32
|
+
): ChatMessageModelText {
|
|
33
|
+
return {
|
|
34
|
+
id: 'id',
|
|
35
|
+
type: 'message',
|
|
36
|
+
message: 'message',
|
|
37
|
+
timestamp: new Date('2000-01-01T00:00:00Z'),
|
|
38
|
+
state: ChatMessageState.Delivered,
|
|
39
|
+
data: {},
|
|
40
|
+
participant: isAgent ? mockChatAgent() : mockChatUser(),
|
|
41
|
+
...overrides,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function mockChatMessageModelWelcome(
|
|
46
|
+
overrides?: Partial<ChatMessageModelWelcome>
|
|
47
|
+
): ChatMessageModelWelcome {
|
|
48
|
+
return {
|
|
49
|
+
id: 'id',
|
|
50
|
+
type: 'welcome',
|
|
51
|
+
message: 'message welcome',
|
|
52
|
+
timestamp: new Date('2000-01-01T00:00:00Z'),
|
|
53
|
+
state: ChatMessageState.Delivered,
|
|
54
|
+
data: {},
|
|
55
|
+
participant: mockChatAgent(),
|
|
56
|
+
...overrides,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function mockChatMessageModelFile(
|
|
61
|
+
overrides?: Partial<ChatMessageModelFile>
|
|
62
|
+
): ChatMessageModelFile {
|
|
63
|
+
return {
|
|
64
|
+
id: 'id',
|
|
65
|
+
type: 'file',
|
|
66
|
+
timestamp: new Date('2000-01-01T00:00:00Z'),
|
|
67
|
+
state: ChatMessageState.Delivered,
|
|
68
|
+
fileName: 'file name',
|
|
69
|
+
data: {},
|
|
70
|
+
participant: mockChatUser(),
|
|
71
|
+
...overrides,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function mockChatMessageModelTimeout(
|
|
76
|
+
overrides?: Partial<ChatMessageModelTimeout>
|
|
77
|
+
): ChatMessageModelTimeout {
|
|
78
|
+
return {
|
|
79
|
+
id: 'id',
|
|
80
|
+
type: 'timeout',
|
|
81
|
+
timestamp: new Date('2000-01-01T00:00:00Z'),
|
|
82
|
+
state: ChatMessageState.Delivered,
|
|
83
|
+
data: {},
|
|
84
|
+
participant: mockChatAgent(),
|
|
85
|
+
...overrides,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function mockChatConversation() {
|
|
90
|
+
return [
|
|
91
|
+
mockChatMessageModelWelcome(),
|
|
92
|
+
mockChatMessageModelText(false, {
|
|
93
|
+
message: 'question 1',
|
|
94
|
+
}),
|
|
95
|
+
mockChatMessageModelText(true, {
|
|
96
|
+
message: 'answer 1',
|
|
97
|
+
}),
|
|
98
|
+
mockChatMessageModelText(false, {
|
|
99
|
+
message: 'question 2',
|
|
100
|
+
}),
|
|
101
|
+
mockChatMessageModelText(true, {
|
|
102
|
+
message: 'answer 2',
|
|
103
|
+
}),
|
|
104
|
+
];
|
|
105
|
+
}
|
|
@@ -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
|
}>;
|
package/src/models/index.ts
CHANGED