Skip to content

Commit 64a86f5

Browse files
authored
Merge pull request #3375 from RedisInsight/fe/feature/RI-5617-legal-terms
#RI-5617 - add legal terms for chatbots
2 parents 97eb271 + 64f6aeb commit 64a86f5

File tree

18 files changed

+902
-48
lines changed

18 files changed

+902
-48
lines changed

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/assistance-chat/AssistanceChat.spec.tsx

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ import {
1414
import {
1515
aiAssistantChatSelector,
1616
createAssistantChat,
17-
getAssistantChatHistory, removeAssistantChatHistory,
18-
sendQuestion
17+
getAssistantChatHistory,
18+
removeAssistantChatHistory,
19+
sendQuestion, updateAssistantChatAgreements
1920
} from 'uiSrc/slices/panels/aiAssistant'
2021
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
2122
import { AiChatType } from 'uiSrc/slices/interfaces/aiAssistant'
@@ -30,7 +31,8 @@ jest.mock('uiSrc/slices/panels/aiAssistant', () => ({
3031
...jest.requireActual('uiSrc/slices/panels/aiAssistant'),
3132
aiAssistantChatSelector: jest.fn().mockReturnValue({
3233
id: '',
33-
messages: []
34+
messages: [],
35+
agreements: true
3436
})
3537
}))
3638

@@ -63,7 +65,8 @@ describe('AssistanceChat', () => {
6365
it('should get history', () => {
6466
(aiAssistantChatSelector as jest.Mock).mockReturnValue({
6567
id: '1',
66-
messages: []
68+
messages: [],
69+
agreements: true
6770
})
6871
render(<AssistanceChat />, { store })
6972

@@ -73,7 +76,8 @@ describe('AssistanceChat', () => {
7376
it('should call action to create an id after submit first message', () => {
7477
(aiAssistantChatSelector as jest.Mock).mockReturnValue({
7578
id: '',
76-
messages: []
79+
messages: [],
80+
agreements: true
7781
})
7882
render(<AssistanceChat />, { store })
7983

@@ -95,7 +99,8 @@ describe('AssistanceChat', () => {
9599

96100
(aiAssistantChatSelector as jest.Mock).mockReturnValue({
97101
id: '1',
98-
messages: []
102+
messages: [],
103+
agreements: true
99104
})
100105
render(<AssistanceChat />, { store })
101106

@@ -124,13 +129,65 @@ describe('AssistanceChat', () => {
124129
(sendEventTelemetry as jest.Mock).mockRestore()
125130
})
126131

132+
it('should show agreements', async () => {
133+
const sendEventTelemetryMock = jest.fn();
134+
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock);
135+
136+
(aiAssistantChatSelector as jest.Mock).mockReturnValue({
137+
id: '1',
138+
messages: [],
139+
agreements: false
140+
})
141+
render(<AssistanceChat />, { store })
142+
143+
const afterRenderActions = [...store.getActions()]
144+
145+
act(() => {
146+
fireEvent.change(
147+
screen.getByTestId('ai-message-textarea'),
148+
{ target: { value: 'test' } }
149+
)
150+
})
151+
152+
fireEvent.click(screen.getByTestId('ai-submit-message-btn'))
153+
154+
await waitForEuiPopoverVisible()
155+
156+
expect(sendEventTelemetry).toBeCalledWith({
157+
event: TelemetryEvent.AI_CHAT_BOT_TERMS_DISPLAYED,
158+
eventData: {
159+
chat: AiChatType.Assistance
160+
}
161+
});
162+
(sendEventTelemetry as jest.Mock).mockRestore()
163+
164+
act(() => {
165+
fireEvent.click(screen.getByTestId('ai-accept-agreements'))
166+
})
167+
168+
expect(sendEventTelemetry).toBeCalledWith({
169+
event: TelemetryEvent.AI_CHAT_BOT_TERMS_ACCEPTED,
170+
eventData: {
171+
chat: AiChatType.Assistance,
172+
}
173+
});
174+
(sendEventTelemetry as jest.Mock).mockRestore()
175+
176+
expect(store.getActions()).toEqual([
177+
...afterRenderActions,
178+
updateAssistantChatAgreements(true),
179+
sendQuestion(expect.objectContaining({ content: 'test' }))
180+
])
181+
})
182+
127183
it('should call action after click on restart session', async () => {
128184
const sendEventTelemetryMock = jest.fn();
129185
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock);
130186

131187
(aiAssistantChatSelector as jest.Mock).mockReturnValue({
132188
id: '1',
133-
messages: [{}]
189+
messages: [{}],
190+
agreements: true
134191
})
135192

136193
render(<AssistanceChat />, { store })

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/assistance-chat/AssistanceChat.tsx

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@ import {
77
askAssistantChatbot,
88
createAssistantChatAction,
99
getAssistantChatHistoryAction,
10-
removeAssistantChatAction,
10+
removeAssistantChatAction, removeAssistantChatHistorySuccess,
1111
sendQuestion,
12+
updateAssistantChatAgreements,
1213
} from 'uiSrc/slices/panels/aiAssistant'
1314
import { getCommandsFromQuery, Nullable, scrollIntoView } from 'uiSrc/utils'
1415
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
@@ -18,12 +19,15 @@ import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
1819
import { appRedisCommandsSelector } from 'uiSrc/slices/app/redis-commands'
1920

2021
import { generateHumanMessage } from 'uiSrc/utils/transformers/chatbot'
21-
import { AssistanceChatInitialMessage, ChatHistory, ChatForm, RestartChat } from '../shared'
22+
23+
import { CustomErrorCodes } from 'uiSrc/constants'
24+
import { ASSISTANCE_CHAT_AGREEMENTS } from '../texts'
25+
import { AssistanceChatInitialMessage, ChatForm, ChatHistory, RestartChat } from '../shared'
2226

2327
import styles from './styles.module.scss'
2428

2529
const AssistanceChat = () => {
26-
const { id, messages } = useSelector(aiAssistantChatSelector)
30+
const { id, messages, agreements, loading } = useSelector(aiAssistantChatSelector)
2731
const { modules, provider } = useSelector(connectedInstanceSelector)
2832
const { commandsArray: REDIS_COMMANDS_ARRAY } = useSelector(appRedisCommandsSelector)
2933

@@ -45,24 +49,44 @@ const AssistanceChat = () => {
4549
const handleSubmit = useCallback((message: string) => {
4650
scrollToBottom('smooth')
4751

52+
if (!agreements) {
53+
dispatch(updateAssistantChatAgreements(true))
54+
sendEventTelemetry({
55+
event: TelemetryEvent.AI_CHAT_BOT_TERMS_ACCEPTED,
56+
eventData: {
57+
chat: AiChatType.Assistance,
58+
}
59+
})
60+
}
61+
4862
if (!id) {
4963
dispatch(
5064
createAssistantChatAction(
5165
(chatId) => sendChatMessage(chatId, message),
5266
// if cannot create a chat - just put message with error
53-
() => dispatch(
54-
sendQuestion({
55-
...generateHumanMessage(message),
56-
error: { statusCode: 500 },
67+
() => {
68+
dispatch(
69+
sendQuestion({
70+
...generateHumanMessage(message),
71+
error: { statusCode: 500, errorCode: CustomErrorCodes.GeneralAiUnexpectedError },
72+
})
73+
)
74+
75+
sendEventTelemetry({
76+
event: TelemetryEvent.AI_CHAT_BOT_ERROR_MESSAGE_RECEIVED,
77+
eventData: {
78+
chat: AiChatType.Assistance,
79+
errorCode: 500
80+
}
5781
})
58-
)
82+
}
5983
)
6084
)
6185
return
6286
}
6387

6488
sendChatMessage(id, message)
65-
}, [id])
89+
}, [id, agreements])
6690

6791
const sendChatMessage = (chatId: string, message: string) => {
6892
dispatch(askAssistantChatbot(
@@ -95,7 +119,10 @@ const AssistanceChat = () => {
95119
}
96120

97121
const onClearSession = useCallback(() => {
98-
if (!id) return
122+
if (!id) {
123+
dispatch(removeAssistantChatHistorySuccess())
124+
return
125+
}
99126

100127
dispatch(removeAssistantChatAction(id))
101128

@@ -120,6 +147,15 @@ const AssistanceChat = () => {
120147
})
121148
}, [instanceId, provider])
122149

150+
const handleAgreementsDisplay = useCallback(() => {
151+
sendEventTelemetry({
152+
event: TelemetryEvent.AI_CHAT_BOT_TERMS_DISPLAYED,
153+
eventData: {
154+
chat: AiChatType.Assistance,
155+
}
156+
})
157+
}, [])
158+
123159
const scrollToBottom = useCallback((behavior: ScrollBehavior = 'smooth') => {
124160
requestAnimationFrame(() => {
125161
scrollIntoView(scrollDivRef?.current, {
@@ -149,6 +185,7 @@ const AssistanceChat = () => {
149185
</div>
150186
<div className={styles.chatHistory}>
151187
<ChatHistory
188+
isLoading={loading}
152189
modules={modules}
153190
initialMessage={AssistanceChatInitialMessage}
154191
inProgressMessage={inProgressMessage}
@@ -161,6 +198,8 @@ const AssistanceChat = () => {
161198
</div>
162199
<div className={styles.chatForm}>
163200
<ChatForm
201+
onAgreementsDisplayed={handleAgreementsDisplay}
202+
agreements={!agreements ? ASSISTANCE_CHAT_AGREEMENTS : undefined}
164203
placeholder="Ask me about Redis"
165204
isDisabled={!!inProgressMessage}
166205
onSubmit={handleSubmit}

redisinsight/ui/src/components/side-panels/panels/ai-assistant/components/expert-chat/ExpertChat.spec.tsx

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ import {
1616
clearExpertChatHistory,
1717
getExpertChatHistory,
1818
sendExpertQuestion,
19+
updateExpertChatAgreements,
1920
} from 'uiSrc/slices/panels/aiAssistant'
2021
import { sendEventTelemetry, TelemetryEvent } from 'uiSrc/telemetry'
2122
import { AiChatType } from 'uiSrc/slices/interfaces/aiAssistant'
2223
import { apiService } from 'uiSrc/services'
24+
import { connectedInstanceSelector } from 'uiSrc/slices/instances/instances'
25+
import { RedisDefaultModules } from 'uiSrc/slices/interfaces'
26+
import { loadList } from 'uiSrc/slices/browser/redisearch'
2327
import ExpertChat from './ExpertChat'
2428

2529
jest.mock('uiSrc/telemetry', () => ({
@@ -31,10 +35,25 @@ jest.mock('uiSrc/slices/panels/aiAssistant', () => ({
3135
...jest.requireActual('uiSrc/slices/panels/aiAssistant'),
3236
aiExpertChatSelector: jest.fn().mockReturnValue({
3337
loading: false,
34-
messages: []
38+
messages: [],
39+
agreements: []
3540
})
3641
}))
3742

43+
jest.mock('uiSrc/slices/instances/instances', () => ({
44+
...jest.requireActual('uiSrc/slices/instances/instances'),
45+
connectedInstanceSelector: jest.fn().mockReturnValue({
46+
modules: []
47+
}),
48+
}))
49+
50+
jest.mock('react-router-dom', () => ({
51+
...jest.requireActual('react-router-dom'),
52+
useParams: () => ({
53+
instanceId: 'instanceId',
54+
}),
55+
}))
56+
3857
let store: typeof mockedStore
3958
beforeEach(() => {
4059
cleanup()
@@ -58,7 +77,8 @@ describe('ExpertChat', () => {
5877
it('should show loading', () => {
5978
(aiExpertChatSelector as jest.Mock).mockReturnValue({
6079
loading: true,
61-
messages: []
80+
messages: [],
81+
agreements: []
6282
})
6383
render(<ExpertChat />)
6484

@@ -72,13 +92,29 @@ describe('ExpertChat', () => {
7292
expect(store.getActions()).toEqual([getExpertChatHistory()])
7393
})
7494

95+
it('should call fetch indexes', () => {
96+
(aiExpertChatSelector as jest.Mock).mockReturnValue({
97+
loading: true,
98+
messages: [],
99+
agreements: []
100+
});
101+
(connectedInstanceSelector as jest.Mock).mockImplementation(() => ({
102+
modules: [{ name: RedisDefaultModules.FT }, { name: RedisDefaultModules.ReJSON }]
103+
}))
104+
105+
render(<ExpertChat />, { store })
106+
107+
expect(store.getActions()).toEqual([getExpertChatHistory(), loadList()])
108+
})
109+
75110
it('should call action after submit message', () => {
76111
const sendEventTelemetryMock = jest.fn();
77112
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock);
78113

79114
(aiExpertChatSelector as jest.Mock).mockReturnValue({
80115
loading: false,
81-
messages: []
116+
messages: [],
117+
agreements: ['instanceId']
82118
})
83119
render(<ExpertChat />, { store })
84120

@@ -107,14 +143,67 @@ describe('ExpertChat', () => {
107143
(sendEventTelemetry as jest.Mock).mockRestore()
108144
})
109145

146+
it('should show agreements after click submit', async () => {
147+
const sendEventTelemetryMock = jest.fn();
148+
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock);
149+
150+
(aiExpertChatSelector as jest.Mock).mockReturnValue({
151+
loading: false,
152+
messages: [],
153+
agreements: []
154+
})
155+
render(<ExpertChat />, { store })
156+
157+
const afterRenderActions = [...store.getActions()]
158+
159+
act(() => {
160+
fireEvent.change(
161+
screen.getByTestId('ai-message-textarea'),
162+
{ target: { value: 'test' } }
163+
)
164+
})
165+
166+
fireEvent.click(screen.getByTestId('ai-submit-message-btn'))
167+
168+
await waitForEuiPopoverVisible()
169+
170+
expect(sendEventTelemetry).toBeCalledWith({
171+
event: TelemetryEvent.AI_CHAT_BOT_TERMS_DISPLAYED,
172+
eventData: {
173+
chat: AiChatType.Query
174+
}
175+
});
176+
(sendEventTelemetry as jest.Mock).mockRestore()
177+
178+
act(() => {
179+
fireEvent.click(screen.getByTestId('ai-accept-agreements'))
180+
})
181+
182+
expect(sendEventTelemetry).toBeCalledWith({
183+
event: TelemetryEvent.AI_CHAT_BOT_TERMS_ACCEPTED,
184+
eventData: {
185+
chat: AiChatType.Query,
186+
databaseId: 'instanceId'
187+
}
188+
});
189+
(sendEventTelemetry as jest.Mock).mockRestore()
190+
191+
expect(store.getActions()).toEqual([
192+
...afterRenderActions,
193+
updateExpertChatAgreements('instanceId'),
194+
sendExpertQuestion(expect.objectContaining({ content: 'test' }))
195+
])
196+
})
197+
110198
it('should call action after click on restart session', async () => {
111199
const sendEventTelemetryMock = jest.fn();
112200
(sendEventTelemetry as jest.Mock).mockImplementation(() => sendEventTelemetryMock)
113201
apiService.delete = jest.fn().mockResolvedValueOnce({ status: 200 });
114202

115203
(aiExpertChatSelector as jest.Mock).mockReturnValue({
116204
loading: false,
117-
messages: [{}]
205+
messages: [{}],
206+
agreements: ['instanceId']
118207
})
119208

120209
render(<ExpertChat />, { store })

0 commit comments

Comments
 (0)