@servicetitan/titan-chat-ui-anvil2 3.1.0 → 3.1.1
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-error.test.d.ts +2 -0
- package/dist/components/chat/__tests-cy__/chat-error.test.d.ts.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-error.test.js +6 -0
- package/dist/components/chat/__tests-cy__/chat-error.test.js.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-input-file.test.d.ts +2 -0
- package/dist/components/chat/__tests-cy__/chat-input-file.test.d.ts.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-input-file.test.js +6 -0
- package/dist/components/chat/__tests-cy__/chat-input-file.test.js.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-input.test.d.ts +2 -0
- package/dist/components/chat/__tests-cy__/chat-input.test.d.ts.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-input.test.js +6 -0
- package/dist/components/chat/__tests-cy__/chat-input.test.js.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-log.test.d.ts +2 -0
- package/dist/components/chat/__tests-cy__/chat-log.test.d.ts.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-log.test.js +6 -0
- package/dist/components/chat/__tests-cy__/chat-log.test.js.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-messages.test.js +2 -91
- package/dist/components/chat/__tests-cy__/chat-messages.test.js.map +1 -1
- package/dist/components/chat/__tests-cy__/chat-notifications.test.d.ts +2 -0
- package/dist/components/chat/__tests-cy__/chat-notifications.test.d.ts.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-notifications.test.js +6 -0
- package/dist/components/chat/__tests-cy__/chat-notifications.test.js.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-timer.test.d.ts +2 -0
- package/dist/components/chat/__tests-cy__/chat-timer.test.d.ts.map +1 -0
- package/dist/components/chat/__tests-cy__/chat-timer.test.js +6 -0
- package/dist/components/chat/__tests-cy__/chat-timer.test.js.map +1 -0
- package/dist/components/chat/__tests-cy__/chat.test.js +3 -127
- package/dist/components/chat/__tests-cy__/chat.test.js.map +1 -1
- package/dist/components/chat/chat-connecting.js +1 -1
- package/dist/components/chat/chat-connecting.js.map +1 -1
- package/dist/components/chat/chat-error.d.ts +3 -1
- package/dist/components/chat/chat-error.d.ts.map +1 -1
- package/dist/components/chat/chat-error.js +3 -4
- package/dist/components/chat/chat-error.js.map +1 -1
- package/dist/components/chat/chat-input-file.d.ts.map +1 -1
- package/dist/components/chat/chat-input-file.js +19 -15
- package/dist/components/chat/chat-input-file.js.map +1 -1
- package/dist/components/chat/chat-input.d.ts.map +1 -1
- package/dist/components/chat/chat-input.js +5 -6
- package/dist/components/chat/chat-input.js.map +1 -1
- package/dist/components/chat/chat-input.module.less +4 -0
- package/dist/components/chat/chat-message-template-user.d.ts.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-timer.d.ts.map +1 -1
- package/dist/components/chat/chat-timer.js +2 -3
- package/dist/components/chat/chat-timer.js.map +1 -1
- package/dist/components/chat/chat.d.ts.map +1 -1
- package/dist/components/chat/chat.js +7 -2
- package/dist/components/chat/chat.js.map +1 -1
- package/dist/components/chat/chat.module.less +9 -0
- package/dist/components/message-content/__tests-cy__/message-content-file.test.d.ts +2 -0
- package/dist/components/message-content/__tests-cy__/message-content-file.test.d.ts.map +1 -0
- package/dist/components/message-content/__tests-cy__/message-content-file.test.js +6 -0
- package/dist/components/message-content/__tests-cy__/message-content-file.test.js.map +1 -0
- package/dist/components/messages/__tests-cy__/message-agent.test.js +11 -77
- package/dist/components/messages/__tests-cy__/message-agent.test.js.map +1 -1
- package/dist/components/messages/__tests-cy__/message-system.test.js +6 -15
- package/dist/components/messages/__tests-cy__/message-system.test.js.map +1 -1
- package/dist/components/messages/__tests-cy__/message-timeout.test.js +2 -21
- package/dist/components/messages/__tests-cy__/message-timeout.test.js.map +1 -1
- package/dist/components/messages/__tests-cy__/message-typing.test.js +3 -45
- package/dist/components/messages/__tests-cy__/message-typing.test.js.map +1 -1
- package/dist/components/messages/__tests-cy__/message-user.test.js +3 -23
- package/dist/components/messages/__tests-cy__/message-user.test.js.map +1 -1
- package/dist/components/messages/message-timeout.d.ts.map +1 -1
- package/dist/components/messages/message-timeout.js +2 -1
- package/dist/components/messages/message-timeout.js.map +1 -1
- package/dist/components/messages/message-timeout.module.less +3 -0
- package/dist/components/messages/message-typing.js +1 -1
- package/dist/components/messages/message-typing.js.map +1 -1
- package/dist/components/messages/message-typing.module.less +4 -0
- package/package.json +4 -4
- package/src/components/chat/__tests-cy__/chat-error.test.tsx +6 -0
- package/src/components/chat/__tests-cy__/chat-input-file.test.tsx +6 -0
- package/src/components/chat/__tests-cy__/chat-input.test.tsx +6 -0
- package/src/components/chat/__tests-cy__/chat-log.test.tsx +6 -0
- package/src/components/chat/__tests-cy__/chat-messages.test.tsx +2 -107
- package/src/components/chat/__tests-cy__/chat-notifications.test.tsx +6 -0
- package/src/components/chat/__tests-cy__/chat-timer.test.tsx +6 -0
- package/src/components/chat/__tests-cy__/chat.test.tsx +3 -161
- package/src/components/chat/chat-connecting.tsx +1 -1
- package/src/components/chat/chat-error.tsx +18 -21
- package/src/components/chat/chat-input-file.tsx +66 -31
- package/src/components/chat/chat-input.module.less +4 -0
- package/src/components/chat/chat-input.module.less.d.ts +1 -0
- package/src/components/chat/chat-input.tsx +31 -26
- package/src/components/chat/chat-message-template-user.tsx +11 -8
- package/src/components/chat/chat-timer.tsx +3 -9
- package/src/components/chat/chat.module.less +9 -0
- package/src/components/chat/chat.module.less.d.ts +4 -0
- package/src/components/chat/chat.tsx +33 -20
- package/src/components/message-content/__tests-cy__/message-content-file.test.tsx +6 -0
- package/src/components/messages/__tests-cy__/message-agent.test.tsx +15 -136
- package/src/components/messages/__tests-cy__/message-system.test.tsx +10 -28
- package/src/components/messages/__tests-cy__/message-timeout.test.tsx +2 -25
- package/src/components/messages/__tests-cy__/message-typing.test.tsx +3 -54
- package/src/components/messages/__tests-cy__/message-user.test.tsx +3 -37
- package/src/components/messages/message-timeout.module.less +3 -0
- package/src/components/messages/message-timeout.module.less.d.ts +3 -0
- package/src/components/messages/message-timeout.tsx +3 -2
- package/src/components/messages/message-typing.module.less +4 -0
- package/src/components/messages/message-typing.module.less.d.ts +1 -0
- package/src/components/messages/message-typing.tsx +1 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/components/chat/chat-error.module.less +0 -6
- package/dist/components/chat/chat-timer.module.less +0 -5
- package/src/components/chat/chat-error.module.less +0 -6
- package/src/components/chat/chat-error.module.less.d.ts +0 -3
- package/src/components/chat/chat-timer.module.less +0 -5
- package/src/components/chat/chat-timer.module.less.d.ts +0 -3
|
@@ -1,111 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CHAT_UI_STORE_TOKEN, mockChatMessageModelText } from '@servicetitan/titan-chat-ui-common';
|
|
3
|
-
import { ChatUiSelectors, CypressMocks } from '@servicetitan/titan-chatbot-ui-cypress';
|
|
4
|
-
import { mount } from 'cypress/react';
|
|
1
|
+
import { runChatMessagesSharedTests } from '@servicetitan/titan-chatbot-ui-cypress';
|
|
5
2
|
import { ChatMessages } from '../chat-messages';
|
|
6
3
|
|
|
7
4
|
describe('[ChatMessages]', () => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
beforeEach(() => {
|
|
11
|
-
cy.viewport(780, 600);
|
|
12
|
-
cy.clock(new Date('2023-10-01T10:10:00Z').getTime());
|
|
13
|
-
storeMock = new CypressMocks.ChatUiStoreMock();
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
const render = () => {
|
|
17
|
-
return mount(
|
|
18
|
-
<Provider
|
|
19
|
-
singletons={[
|
|
20
|
-
{
|
|
21
|
-
provide: CHAT_UI_STORE_TOKEN,
|
|
22
|
-
useValue: storeMock,
|
|
23
|
-
},
|
|
24
|
-
]}
|
|
25
|
-
>
|
|
26
|
-
<ChatMessages messages={storeMock.messages} />
|
|
27
|
-
</Provider>
|
|
28
|
-
);
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
it('should render default chat', () => {
|
|
32
|
-
render();
|
|
33
|
-
|
|
34
|
-
ChatUiSelectors.chatMessages.should('be.visible');
|
|
35
|
-
ChatUiSelectors.chatMessage.should('have.length', 2);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('should render several consecutive agent messages without extra avatars', () => {
|
|
39
|
-
storeMock.messages = [
|
|
40
|
-
mockChatMessageModelText(true, {
|
|
41
|
-
id: 'id1',
|
|
42
|
-
timestamp: new Date('2023-01-01T10:10:00Z'),
|
|
43
|
-
message: 'Hello, this is the first message',
|
|
44
|
-
}),
|
|
45
|
-
mockChatMessageModelText(true, {
|
|
46
|
-
id: 'id2',
|
|
47
|
-
message: 'Hello, this is the second message. '.repeat(5).trim(),
|
|
48
|
-
timestamp: new Date('2023-01-01T10:10:59.999Z'),
|
|
49
|
-
}),
|
|
50
|
-
mockChatMessageModelText(true, {
|
|
51
|
-
id: 'id3',
|
|
52
|
-
message: 'Hello, this is the third message',
|
|
53
|
-
timestamp: new Date('2023-01-01T10:11:00Z'),
|
|
54
|
-
}),
|
|
55
|
-
mockChatMessageModelText(true, {
|
|
56
|
-
id: 'id4',
|
|
57
|
-
message: 'Hello, this is the forth message',
|
|
58
|
-
timestamp: new Date('2023-01-01T10:11:01Z'),
|
|
59
|
-
}),
|
|
60
|
-
mockChatMessageModelText(false, {
|
|
61
|
-
id: 'id11',
|
|
62
|
-
timestamp: new Date('2023-01-01T11:10:00Z'),
|
|
63
|
-
message: 'Hello, this is the first message',
|
|
64
|
-
}),
|
|
65
|
-
mockChatMessageModelText(false, {
|
|
66
|
-
id: 'id22',
|
|
67
|
-
message: 'Hello, this is the second message',
|
|
68
|
-
timestamp: new Date('2023-01-01T11:11:00Z'),
|
|
69
|
-
}),
|
|
70
|
-
mockChatMessageModelText(false, {
|
|
71
|
-
id: 'id33',
|
|
72
|
-
message: 'Hello, this is the third message',
|
|
73
|
-
timestamp: new Date('2023-01-01T11:12:00Z'),
|
|
74
|
-
}),
|
|
75
|
-
mockChatMessageModelText(false, {
|
|
76
|
-
id: 'id44',
|
|
77
|
-
message: 'Hello, this is the forth message',
|
|
78
|
-
timestamp: new Date('2023-01-01T11:12:01Z'),
|
|
79
|
-
}),
|
|
80
|
-
];
|
|
81
|
-
render();
|
|
82
|
-
|
|
83
|
-
const getTimestamp = (i: number) =>
|
|
84
|
-
ChatUiSelectors.chatMessage
|
|
85
|
-
.eq(i)
|
|
86
|
-
.find(`[data-cy="${ChatUiSelectors.cy.chatMessageFooter}"]`);
|
|
87
|
-
|
|
88
|
-
ChatUiSelectors.chatMessageAgent.should('have.length', 4);
|
|
89
|
-
ChatUiSelectors.chatMessageUser.should('have.length', 4);
|
|
90
|
-
|
|
91
|
-
// Agent avatar should be visible only for the first message in group
|
|
92
|
-
const getAvatar = (i: number) =>
|
|
93
|
-
ChatUiSelectors.chatMessageAgent
|
|
94
|
-
.find(`[data-cy="${ChatUiSelectors.cy.chatAvatar}"]`)
|
|
95
|
-
.eq(i);
|
|
96
|
-
getAvatar(0).should('be.visible');
|
|
97
|
-
getAvatar(1).should('not.exist');
|
|
98
|
-
getAvatar(2).should('not.exist');
|
|
99
|
-
getAvatar(3).should('not.exist');
|
|
100
|
-
|
|
101
|
-
// Footer with date should be visible only for the different formatted timestamps
|
|
102
|
-
getTimestamp(0).should('not.exist');
|
|
103
|
-
getTimestamp(1).should('be.visible').should('contain.text', 'agent • 10:10 AM');
|
|
104
|
-
getTimestamp(2).should('not.exist');
|
|
105
|
-
getTimestamp(3).should('be.visible').should('contain.text', 'agent • 10:11 AM');
|
|
106
|
-
getTimestamp(4).should('be.visible').should('contain.text', '11:10 AM');
|
|
107
|
-
getTimestamp(5).should('be.visible').should('contain.text', '11:11 AM');
|
|
108
|
-
getTimestamp(6).should('not.exist');
|
|
109
|
-
getTimestamp(7).should('be.visible').should('contain.text', '11:12 AM');
|
|
110
|
-
});
|
|
5
|
+
runChatMessagesSharedTests(ChatMessages);
|
|
111
6
|
});
|
|
@@ -1,164 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Container, provide, useDependencies } from '@servicetitan/react-ioc';
|
|
3
|
-
import {
|
|
4
|
-
CHAT_UI_BACKEND_STORE_TOKEN,
|
|
5
|
-
CHAT_UI_STORE_TOKEN,
|
|
6
|
-
ChatParticipantIcon,
|
|
7
|
-
ChatUiBackendEchoStore,
|
|
8
|
-
ChatUiStore,
|
|
9
|
-
IChatUiBackendStore,
|
|
10
|
-
IChatUiStore,
|
|
11
|
-
} from '@servicetitan/titan-chat-ui-common';
|
|
12
|
-
import { ChatUiSelectors } from '@servicetitan/titan-chatbot-ui-cypress';
|
|
13
|
-
import { mount as cyMount } from 'cypress/react';
|
|
14
|
-
import { ReactNode, useEffect } from 'react';
|
|
1
|
+
import { runChatSharedTests } from '@servicetitan/titan-chatbot-ui-cypress';
|
|
15
2
|
import { Chat } from '../chat';
|
|
16
3
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
const initContainer = () => {
|
|
22
|
-
const rootContainer = new Container();
|
|
23
|
-
const container = new Container();
|
|
24
|
-
container.parent = rootContainer;
|
|
25
|
-
container.bind<IChatUiStore>(CHAT_UI_STORE_TOKEN).to(ChatUiStore).inSingletonScope();
|
|
26
|
-
container
|
|
27
|
-
.bind<IChatUiBackendStore>(CHAT_UI_BACKEND_STORE_TOKEN)
|
|
28
|
-
.to(ChatUiBackendEchoStore)
|
|
29
|
-
.inSingletonScope();
|
|
30
|
-
return container;
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
describe('[Chat]', () => {
|
|
34
|
-
let container: Container;
|
|
35
|
-
let chatUiStore: IChatUiStore;
|
|
36
|
-
let chatUiBackendStore: ChatUiBackendEchoStore;
|
|
37
|
-
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
container = initContainer();
|
|
40
|
-
chatUiStore = container.get<IChatUiStore>(CHAT_UI_STORE_TOKEN);
|
|
41
|
-
chatUiBackendStore = container.get<ChatUiBackendEchoStore>(CHAT_UI_BACKEND_STORE_TOKEN);
|
|
42
|
-
cy.viewport(780, 800);
|
|
43
|
-
cy.clock(Date.parse('2023-10-01T00:00:00Z'));
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
const render = () => {
|
|
47
|
-
const ChatWrapper = provide({
|
|
48
|
-
singletons: [
|
|
49
|
-
{
|
|
50
|
-
provide: CHAT_UI_STORE_TOKEN,
|
|
51
|
-
useValue: chatUiStore,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
provide: CHAT_UI_BACKEND_STORE_TOKEN,
|
|
55
|
-
useValue: chatUiBackendStore,
|
|
56
|
-
},
|
|
57
|
-
],
|
|
58
|
-
})(() => {
|
|
59
|
-
const [chatUiStore, chatUiBackendStore] = useDependencies(
|
|
60
|
-
CHAT_UI_STORE_TOKEN,
|
|
61
|
-
CHAT_UI_BACKEND_STORE_TOKEN
|
|
62
|
-
);
|
|
63
|
-
useEffect(() => {
|
|
64
|
-
const init = async () => {
|
|
65
|
-
chatUiBackendStore.subscribe();
|
|
66
|
-
await chatUiStore.run({
|
|
67
|
-
agentName: 'EchoBot',
|
|
68
|
-
agentIcon: ChatParticipantIcon.Bot,
|
|
69
|
-
});
|
|
70
|
-
};
|
|
71
|
-
init().then(() => {});
|
|
72
|
-
return () => chatUiBackendStore.unsubscribe();
|
|
73
|
-
}, [chatUiStore, chatUiBackendStore]);
|
|
74
|
-
return <Chat className="h-100vh max-h-100vh" />;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
cy.spy(chatUiStore, 'run').as('runSpy');
|
|
78
|
-
mount(<ChatWrapper />);
|
|
79
|
-
|
|
80
|
-
ChatUiSelectors.chatConnecting.should('be.visible');
|
|
81
|
-
cy.tick(1000);
|
|
82
|
-
return cy.wrap(
|
|
83
|
-
new Promise(resolve => {
|
|
84
|
-
cy.get('@runSpy')
|
|
85
|
-
.should('have.been.calledOnce')
|
|
86
|
-
.then((invocation: any) => {
|
|
87
|
-
const initPromise = invocation.firstCall.returnValue as ReturnType<
|
|
88
|
-
IChatUiStore['run']
|
|
89
|
-
>;
|
|
90
|
-
initPromise.then(resolve);
|
|
91
|
-
});
|
|
92
|
-
})
|
|
93
|
-
);
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
const ask = (message: string) => {
|
|
97
|
-
ChatUiSelectors.chatInput.type(`${message}{enter}`);
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
it('should render default chat', () => {
|
|
101
|
-
render().then(() => {
|
|
102
|
-
ChatUiSelectors.chatMessages.should('be.visible');
|
|
103
|
-
ChatUiSelectors.chatMessageAgent.should('have.length', 1).should('be.visible');
|
|
104
|
-
ChatUiSelectors.chatMessageContentAgent
|
|
105
|
-
.should('be.visible')
|
|
106
|
-
.should(
|
|
107
|
-
'contain.text',
|
|
108
|
-
"Hello! I'm generic echo bot. I can echo your messages. Try it out!"
|
|
109
|
-
);
|
|
110
|
-
ChatUiSelectors.chatNotifications.should('exist');
|
|
111
|
-
ChatUiSelectors.chatSend.should('be.visible').should('be.disabled');
|
|
112
|
-
ChatUiSelectors.chatInput.should('be.visible').should('not.be.disabled');
|
|
113
|
-
|
|
114
|
-
ChatUiSelectors.chatInput.type('Hello');
|
|
115
|
-
ChatUiSelectors.chatSend.click();
|
|
116
|
-
|
|
117
|
-
ChatUiSelectors.chatMessageUser
|
|
118
|
-
.should('be.visible')
|
|
119
|
-
.should('have.length', 1)
|
|
120
|
-
.should('contain.text', 'Hello');
|
|
121
|
-
ChatUiSelectors.chatMessageTyping.should('be.visible');
|
|
122
|
-
ChatUiSelectors.chatMessageAgent.should('have.length', 1);
|
|
123
|
-
|
|
124
|
-
cy.tick(1000);
|
|
125
|
-
|
|
126
|
-
ChatUiSelectors.chatMessageTyping.should('not.exist');
|
|
127
|
-
ChatUiSelectors.chatMessageAgent.should('have.length', 2);
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it('should render chat error message', () => {
|
|
132
|
-
render().then(() => {
|
|
133
|
-
chatUiStore.setTimer({ secondsTotal: 100, secondsLeft: 10 });
|
|
134
|
-
chatUiStore.setError('error message', {
|
|
135
|
-
title: 'Custom Error',
|
|
136
|
-
recoverStrategy: {
|
|
137
|
-
recoverButtonTitle: 'Recover button title',
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
ChatUiSelectors.chatTimer.should('not.exist');
|
|
142
|
-
ChatUiSelectors.chatError.should('be.visible');
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it('should handle send message error and retry', () => {
|
|
147
|
-
render().then(() => {
|
|
148
|
-
ask('[Error]Custom error message');
|
|
149
|
-
cy.tick(1000);
|
|
150
|
-
|
|
151
|
-
// Check error message
|
|
152
|
-
ChatUiSelectors.chatMessageError
|
|
153
|
-
.should('be.visible')
|
|
154
|
-
.should('contain.text', 'Message not delivered. Retry');
|
|
155
|
-
ChatUiSelectors.chatError
|
|
156
|
-
.should('be.visible')
|
|
157
|
-
.should('contain.text', ['Custom Error Title', 'Custom error message'].join(''));
|
|
158
|
-
|
|
159
|
-
// Retry message
|
|
160
|
-
ChatUiSelectors.chatMessageErrorRetry.should('be.visible').click();
|
|
161
|
-
cy.tick(1000);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
4
|
+
describe('Chat', () => {
|
|
5
|
+
runChatSharedTests(Chat);
|
|
164
6
|
});
|
|
@@ -16,7 +16,7 @@ export const ChatConnecting: FC<IChatConnectingProps> = ({ className }) => {
|
|
|
16
16
|
>
|
|
17
17
|
<Flex direction="row" gap="4" alignItems="center">
|
|
18
18
|
<Spinner />
|
|
19
|
-
<Text className="c-
|
|
19
|
+
<Text className="c-subdued">Starting...</Text>
|
|
20
20
|
</Flex>
|
|
21
21
|
</Flex>
|
|
22
22
|
);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Alert, Button, Flex } from '@servicetitan/anvil2';
|
|
2
2
|
import { useDependencies } from '@servicetitan/react-ioc';
|
|
3
3
|
import { CHAT_UI_STORE_TOKEN } from '@servicetitan/titan-chat-ui-common';
|
|
4
4
|
import { observer } from 'mobx-react';
|
|
5
5
|
import { FC, useCallback } from 'react';
|
|
6
6
|
import { MultilineText } from '../common/multiline-text';
|
|
7
|
-
import * as Styles from './chat-error.module.less';
|
|
8
7
|
|
|
9
|
-
export const ChatError: FC = observer(() => {
|
|
8
|
+
export const ChatError: FC<{ className?: string }> = observer(({ className }) => {
|
|
10
9
|
const [chatUiStore] = useDependencies(CHAT_UI_STORE_TOKEN);
|
|
11
10
|
const { error } = chatUiStore;
|
|
12
11
|
|
|
@@ -18,23 +17,21 @@ export const ChatError: FC = observer(() => {
|
|
|
18
17
|
return null;
|
|
19
18
|
}
|
|
20
19
|
return (
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
)}
|
|
38
|
-
</Announcement>
|
|
20
|
+
<Alert status="danger" title={error.title} className={className} data-cy="titan-chat-error">
|
|
21
|
+
<Flex direction="column" gap="4">
|
|
22
|
+
<MultilineText text={error.message} data-cy="titan-chat-error-text" />
|
|
23
|
+
{error.recoverStrategy && (
|
|
24
|
+
<Button
|
|
25
|
+
type="button"
|
|
26
|
+
appearance="danger"
|
|
27
|
+
size="small"
|
|
28
|
+
onClick={handleReconnect}
|
|
29
|
+
data-cy="titan-chat-error-recover"
|
|
30
|
+
>
|
|
31
|
+
{error.recoverStrategy.recoverButtonTitle}
|
|
32
|
+
</Button>
|
|
33
|
+
)}
|
|
34
|
+
</Flex>
|
|
35
|
+
</Alert>
|
|
39
36
|
);
|
|
40
37
|
});
|
|
@@ -1,20 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
1
|
+
import { Button, Card, Flex, Icon, Text } from '@servicetitan/anvil2';
|
|
2
|
+
import IconAttachment from '@servicetitan/anvil2/assets/icons/material/round/attach_file.svg';
|
|
3
|
+
import IconDelete from '@servicetitan/anvil2/assets/icons/material/round/delete.svg';
|
|
4
|
+
import IconFile from '@servicetitan/anvil2/assets/icons/material/round/insert_drive_file.svg';
|
|
5
|
+
import IconEdit from '@servicetitan/anvil2/assets/icons/material/round/refresh.svg';
|
|
3
6
|
import { FileDescriptor } from '@servicetitan/form';
|
|
4
7
|
import { useDependencies } from '@servicetitan/react-ioc';
|
|
5
8
|
import { CHAT_UI_STORE_TOKEN } from '@servicetitan/titan-chat-ui-common';
|
|
6
9
|
import { observer } from 'mobx-react';
|
|
7
|
-
import { FC, useCallback, useEffect, useState } from 'react';
|
|
10
|
+
import { ChangeEvent, FC, Fragment, useCallback, useEffect, useRef, useState } from 'react';
|
|
8
11
|
|
|
9
12
|
export const ChatInputFile: FC<{ className?: string }> = observer(({ className }) => {
|
|
13
|
+
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
10
14
|
const [chatUiStore] = useDependencies(CHAT_UI_STORE_TOKEN);
|
|
11
15
|
const [fileDescriptor, setFileDescriptor] = useState<FileDescriptor | undefined>(undefined);
|
|
12
16
|
|
|
13
17
|
const handleSelected = useCallback(
|
|
14
|
-
(
|
|
18
|
+
(event: ChangeEvent<HTMLInputElement>) => {
|
|
19
|
+
const files = event.target.files;
|
|
20
|
+
if (!files || files.length === 0) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
15
23
|
const fileDescriptor: FileDescriptor = {
|
|
16
|
-
file:
|
|
17
|
-
displayName:
|
|
24
|
+
file: files[0],
|
|
25
|
+
displayName: files[0].name,
|
|
18
26
|
};
|
|
19
27
|
setFileDescriptor(fileDescriptor);
|
|
20
28
|
chatUiStore.setFile(fileDescriptor);
|
|
@@ -22,17 +30,9 @@ export const ChatInputFile: FC<{ className?: string }> = observer(({ className }
|
|
|
22
30
|
[chatUiStore]
|
|
23
31
|
);
|
|
24
32
|
|
|
25
|
-
const
|
|
26
|
-
(
|
|
27
|
-
|
|
28
|
-
file: newFile,
|
|
29
|
-
displayName: newFile.name,
|
|
30
|
-
};
|
|
31
|
-
setFileDescriptor(fileDescriptor);
|
|
32
|
-
chatUiStore.setFile(fileDescriptor);
|
|
33
|
-
},
|
|
34
|
-
[chatUiStore]
|
|
35
|
-
);
|
|
33
|
+
const handleUpload = () => {
|
|
34
|
+
fileInputRef.current?.click();
|
|
35
|
+
};
|
|
36
36
|
|
|
37
37
|
const handleDelete = useCallback(() => {
|
|
38
38
|
setFileDescriptor(undefined);
|
|
@@ -51,20 +51,55 @@ export const ChatInputFile: FC<{ className?: string }> = observer(({ className }
|
|
|
51
51
|
return (
|
|
52
52
|
<Flex className={className} gap="6" direction="column">
|
|
53
53
|
<Text variant="eyebrow">Upload file</Text>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
54
|
+
<input
|
|
55
|
+
type="file"
|
|
56
|
+
ref={fileInputRef}
|
|
57
|
+
onChange={handleSelected}
|
|
58
|
+
style={{ display: 'none' }} // Hide the native input
|
|
59
|
+
accept="*/*"
|
|
60
|
+
multiple={false}
|
|
61
|
+
/>
|
|
62
|
+
<Flex direction="column" gap="2" data-cy="titan-chat-upload-file">
|
|
63
|
+
{fileDescriptor ? (
|
|
64
|
+
<Card padding="small">
|
|
65
|
+
<Flex style={{ width: '100%' }} direction="row" alignItems="center" gap="2">
|
|
66
|
+
<Icon svg={IconFile} className="m-inline-start-4" />
|
|
67
|
+
<Text variant="body" flexGrow={1} data-cy="titan-chat-upload-file-name">
|
|
68
|
+
{fileDescriptor.displayName}
|
|
69
|
+
</Text>
|
|
70
|
+
<Button
|
|
71
|
+
icon={IconEdit}
|
|
72
|
+
aria-label="Replace file"
|
|
73
|
+
appearance="ghost"
|
|
74
|
+
onClick={handleUpload}
|
|
75
|
+
data-cy="titan-chat-upload-file-edit"
|
|
76
|
+
/>
|
|
77
|
+
<Button
|
|
78
|
+
icon={IconDelete}
|
|
79
|
+
aria-label="Delete file"
|
|
80
|
+
appearance="ghost"
|
|
81
|
+
onClick={handleDelete}
|
|
82
|
+
data-cy="titan-chat-upload-file-delete"
|
|
83
|
+
/>
|
|
84
|
+
</Flex>
|
|
85
|
+
</Card>
|
|
86
|
+
) : (
|
|
87
|
+
<Fragment>
|
|
88
|
+
<Button
|
|
89
|
+
type="button"
|
|
90
|
+
appearance="secondary"
|
|
91
|
+
icon={IconAttachment}
|
|
92
|
+
onClick={handleUpload}
|
|
93
|
+
data-cy="titan-chat-upload-file-btn"
|
|
94
|
+
>
|
|
95
|
+
Upload File
|
|
96
|
+
</Button>
|
|
97
|
+
<Text variant="body" size="small" subdued>
|
|
98
|
+
e.g. Screenshot of issue
|
|
99
|
+
</Text>
|
|
100
|
+
</Fragment>
|
|
101
|
+
)}
|
|
102
|
+
</Flex>
|
|
68
103
|
</Flex>
|
|
69
104
|
);
|
|
70
105
|
});
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { Button, Textarea } from '@servicetitan/anvil2';
|
|
1
|
+
import { Button, Flex, Textarea } from '@servicetitan/anvil2';
|
|
2
2
|
import IconSend from '@servicetitan/anvil2/assets/icons/material/round/send.svg';
|
|
3
3
|
import { provide, useDependencies } from '@servicetitan/react-ioc';
|
|
4
4
|
import { CHAT_UI_STORE_TOKEN } from '@servicetitan/titan-chat-ui-common';
|
|
5
5
|
import classNames from 'classnames';
|
|
6
6
|
import { observer } from 'mobx-react';
|
|
7
|
-
import { FC, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
7
|
+
import { FC, FormEvent, KeyboardEvent, useCallback, useEffect, useRef, useState } from 'react';
|
|
8
8
|
import { ChatInputStore } from '../../stores/chat-input.store';
|
|
9
9
|
import * as Styles from './chat-input.module.less';
|
|
10
10
|
|
|
@@ -22,26 +22,30 @@ export const ChatInput: FC<{ className?: string }> = provide({
|
|
|
22
22
|
ChatInputStore
|
|
23
23
|
);
|
|
24
24
|
|
|
25
|
-
const handleSendMessage = useCallback(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
25
|
+
const handleSendMessage = useCallback(
|
|
26
|
+
async (event?: FormEvent) => {
|
|
27
|
+
event?.preventDefault();
|
|
28
|
+
const validateResult = await supportChatInputStore.formState.validate();
|
|
29
|
+
if (validateResult.hasError) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const text = supportChatInputStore.formState.$.message.value;
|
|
33
|
+
if (!text.trim() && !chatUiStore.file) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
supportChatInputStore.formState.$.message.onChange('');
|
|
37
|
+
setIsSending(true);
|
|
38
|
+
try {
|
|
39
|
+
await chatUiStore.sendMessageText(text);
|
|
40
|
+
} finally {
|
|
41
|
+
setIsSending(false);
|
|
42
|
+
}
|
|
43
|
+
setTimeout(() => {
|
|
44
|
+
textareaRef.current?.focus();
|
|
45
|
+
}, 0);
|
|
46
|
+
},
|
|
47
|
+
[chatUiStore, supportChatInputStore]
|
|
48
|
+
);
|
|
45
49
|
|
|
46
50
|
const clearTimer = useCallback(() => {
|
|
47
51
|
clearTimeout(typingTimeoutRef.current ?? 0);
|
|
@@ -98,14 +102,15 @@ export const ChatInput: FC<{ className?: string }> = provide({
|
|
|
98
102
|
|
|
99
103
|
return (
|
|
100
104
|
<form className={classNames(className)} onSubmit={handleSendMessage}>
|
|
101
|
-
<
|
|
105
|
+
<Flex direction="row" gap={4} className={Styles.formContainer}>
|
|
102
106
|
<Textarea
|
|
103
107
|
ref={textareaRef}
|
|
104
108
|
name="question"
|
|
105
109
|
placeholder="Type your message"
|
|
106
110
|
rows={1}
|
|
107
|
-
|
|
111
|
+
maxRows={2}
|
|
108
112
|
autoHeight
|
|
113
|
+
flexGrow={1}
|
|
109
114
|
onKeyDown={handleTextKeyPress}
|
|
110
115
|
value={supportChatInputStore.formState.$.message.value}
|
|
111
116
|
error={supportChatInputStore.formState.$.message.error}
|
|
@@ -123,7 +128,7 @@ export const ChatInput: FC<{ className?: string }> = provide({
|
|
|
123
128
|
data-cy="titan-chat-input"
|
|
124
129
|
/>
|
|
125
130
|
<Button
|
|
126
|
-
|
|
131
|
+
alignSelf="baseline"
|
|
127
132
|
icon={IconSend}
|
|
128
133
|
data-pendo="titan-chat-send"
|
|
129
134
|
data-cy="titan-chat-send"
|
|
@@ -137,7 +142,7 @@ export const ChatInput: FC<{ className?: string }> = provide({
|
|
|
137
142
|
supportChatInputStore.isEmpty
|
|
138
143
|
}
|
|
139
144
|
/>
|
|
140
|
-
</
|
|
145
|
+
</Flex>
|
|
141
146
|
</form>
|
|
142
147
|
);
|
|
143
148
|
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Text } from '@servicetitan/anvil2';
|
|
1
|
+
import { Button, Flex, Text } from '@servicetitan/anvil2';
|
|
2
2
|
import IconRefresh from '@servicetitan/anvil2/assets/icons/material/round/refresh.svg';
|
|
3
3
|
import { useDependencies } from '@servicetitan/react-ioc';
|
|
4
4
|
import {
|
|
@@ -26,21 +26,24 @@ export const ChatMessageTemplateUser: FC<PropsWithChildren<IChatMessageProps>> =
|
|
|
26
26
|
isError={isError}
|
|
27
27
|
messageFooter={
|
|
28
28
|
isError ? (
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
<Flex direction="row" alignItems="center" gap="1">
|
|
30
|
+
<Text
|
|
31
|
+
variant="eyebrow"
|
|
32
|
+
className="c-danger"
|
|
33
|
+
data-cy="titan-chat-message-error"
|
|
34
|
+
>
|
|
35
|
+
Message not delivered. Retry
|
|
36
|
+
</Text>
|
|
35
37
|
<Button
|
|
36
38
|
icon={IconRefresh}
|
|
37
39
|
appearance="ghost"
|
|
38
40
|
size="small"
|
|
41
|
+
className="c-danger"
|
|
39
42
|
aria-label="Retry send message"
|
|
40
43
|
onClick={handleRetry}
|
|
41
44
|
data-cy="titan-chat-message-error-retry"
|
|
42
45
|
/>
|
|
43
|
-
</
|
|
46
|
+
</Flex>
|
|
44
47
|
) : !omitTimestamp ? (
|
|
45
48
|
<MessageFooter timestamp={message.timestamp} />
|
|
46
49
|
) : null
|