@pega/cosmos-react-work 8.29.0 → 8.31.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/lib/components/GenAICoach/GenAICoach.d.ts +1 -2
- package/lib/components/GenAICoach/GenAICoach.d.ts.map +1 -1
- package/lib/components/GenAICoach/GenAICoach.js +279 -13
- package/lib/components/GenAICoach/GenAICoach.js.map +1 -1
- package/lib/components/GenAICoach/GenAICoach.styles.d.ts +2 -0
- package/lib/components/GenAICoach/GenAICoach.styles.d.ts.map +1 -1
- package/lib/components/GenAICoach/GenAICoach.styles.js +29 -1
- package/lib/components/GenAICoach/GenAICoach.styles.js.map +1 -1
- package/lib/components/GenAICoach/GenAICoach.types.d.ts +9 -0
- package/lib/components/GenAICoach/GenAICoach.types.d.ts.map +1 -1
- package/lib/components/GenAICoach/GenAICoach.types.js.map +1 -1
- package/lib/components/SearchResults/SearchResults.d.ts.map +1 -1
- package/lib/components/SearchResults/SearchResults.js +4 -2
- package/lib/components/SearchResults/SearchResults.js.map +1 -1
- package/package.json +3 -3
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { FunctionComponent } from 'react';
|
|
2
1
|
import { type GenAICoachProps } from '.';
|
|
3
2
|
export declare const AgentIcon: () => import("react/jsx-runtime").JSX.Element;
|
|
4
|
-
declare const _default:
|
|
3
|
+
declare const _default: (({ testId, coachOptions: coachOptionsProps, onCoachChange: onCoachChangeProp, messages, onSend, initialSuggestedMessages, suggestions, loading, error, variant, onOpen, guidedMode, actions, conversationHistory, onLayoutChange, onAddAttachment, attachments, attachmentsError, lightboxPreviewProps, showSalutation, salutation, allowFullScreen, isInLandingPageUtilities, voiceToTextProps, disclaimerMessage, ...restProps }: GenAICoachProps) => import("react/jsx-runtime").JSX.Element) & {
|
|
5
4
|
getTestIds: (testIdProp?: import("@pega/cosmos-react-core").TestIdProp["testId"]) => import("@pega/cosmos-react-core").TestIdsRecord<readonly ["user-message", "coach-message", "actions", "attachments"]>;
|
|
6
5
|
};
|
|
7
6
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenAICoach.d.ts","sourceRoot":"","sources":["../../../src/components/GenAICoach/GenAICoach.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GenAICoach.d.ts","sourceRoot":"","sources":["../../../src/components/GenAICoach/GenAICoach.tsx"],"names":[],"mappings":"AAyFA,OAAO,EAAyC,KAAK,eAAe,EAAE,MAAM,GAAG,CAAC;AAchF,eAAO,MAAM,SAAS,+CAmDrB,CAAC;6bA+BC,eAAe;;;AA0xClB,wBAA6D"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
-
import { Button, Flex, Icon, MenuButton, Progress, Text, createUID, menuHelpers, useI18n, useSpeechRecognition, useTestIds, useTheme, withTestIds, registerIcon, usePrevious, getFocusables, isMenuGroupProps, ErrorState, useArrows, focusableSelector, useLiveLog, useOuterEvent, ThemeOverride, hasProp, getActiveElement, useElement, Actions, useBreakpoint, Grid, useRefMap, VisuallyHiddenText, FileList, Lightbox, FullscreenButton, FormField, useUID, SpeechToTextButton, Toaster, useToaster } from '@pega/cosmos-react-core';
|
|
3
|
+
import { Button, Flex, Icon, MenuButton, Progress, Text, createUID, menuHelpers, useI18n, useSpeechRecognition, useTestIds, useTheme, withTestIds, registerIcon, usePrevious, getFocusables, isMenuGroupProps, ErrorState, useArrows, focusableSelector, useLiveLog, useOuterEvent, ThemeOverride, hasProp, getActiveElement, useElement, Actions, useBreakpoint, Grid, useRefMap, VisuallyHiddenText, FileList, Lightbox, FullscreenButton, FormField, useUID, SpeechToTextButton, Toaster, useToaster, throttle } from '@pega/cosmos-react-core';
|
|
4
4
|
import * as caretUpIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/caret-up.icon';
|
|
5
5
|
import * as timesIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/times.icon';
|
|
6
6
|
import * as caretDownIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/caret-down.icon';
|
|
@@ -12,7 +12,7 @@ import * as chatTypingIcon from '@pega/cosmos-react-core/lib/components/Icon/ico
|
|
|
12
12
|
import * as sendSolidIcon from '@pega/cosmos-react-core/lib/components/Icon/icons/send-solid.icon';
|
|
13
13
|
import { getGenAICoachTestIds } from './GenAICoach.test-ids';
|
|
14
14
|
import { isCoachMessage, isInUtilities, isUserMessage } from './GenAICoach.utils';
|
|
15
|
-
import { StyledMessagesContainer, StyledDisclaimerText, StyledDisclaimerDivider, StyledGenAICoachContainer, StyledGenAIOptionsMenu, StyledInitialMessageContainer, StyledInputContainer, StyledMenuSuggestions, StyledMessagesList, StyledGridContainer, StyledFlexWrapper, StyledErrorContainer, StyledInput, StyledGuidedSuggestions, StyledComposerSection, StyledSendButton, StyledGenAIFormControl, StyledGenAITextArea, StyledInitialMessageGrid,
|
|
15
|
+
import { StyledMessagesContainer, StyledDisclaimerText, StyledDisclaimerDivider, StyledGenAICoachContainer, StyledGenAIOptionsMenu, StyledInitialMessageContainer, StyledInputContainer, StyledMenuSuggestions, StyledMessagesList, StyledGridContainer, StyledFlexWrapper, StyledErrorContainer, StyledInput, StyledGuidedSuggestions, StyledComposerSection, StyledSendButton, StyledGenAIFormControl, StyledGenAITextArea, StyledInitialMessageGrid, StyledGenAIFooter, StyledAgentIcon, StyledFullscreenWrapper, StyledSalutation, StyledActionsContainer, StyledScrollButton } from './GenAICoach.styles';
|
|
16
16
|
import ConversationHistory from './ConversationHistory';
|
|
17
17
|
import { GenAIMessage, InitialSuggestedMessage } from '.';
|
|
18
18
|
registerIcon(caretUpIcon, timesIcon, caretDownIcon, minusIcon, polarisSolidIcon, paperClipIcon, warnSolidIcon, chatTypingIcon, sendSolidIcon);
|
|
@@ -25,6 +25,12 @@ export const AgentIcon = () => {
|
|
|
25
25
|
};
|
|
26
26
|
const VOICE_ERROR_DISMISS_DURATION = 3000;
|
|
27
27
|
const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: onCoachChangeProp, messages = [], onSend, initialSuggestedMessages, suggestions, loading, error, variant, onOpen, guidedMode = false, actions, conversationHistory, onLayoutChange, onAddAttachment, attachments, attachmentsError, lightboxPreviewProps, showSalutation = true, salutation, allowFullScreen = true, isInLandingPageUtilities, voiceToTextProps, disclaimerMessage, ...restProps }) => {
|
|
28
|
+
const scrollTolerancePx = 2;
|
|
29
|
+
const scrollThresholdPx = 50;
|
|
30
|
+
const streamBufferLh = 20;
|
|
31
|
+
const bufferExtensionLh = 2;
|
|
32
|
+
const scrollThrottleMs = 50;
|
|
33
|
+
const resizeThrottleMs = 33;
|
|
28
34
|
const theme = useTheme();
|
|
29
35
|
const t = useI18n();
|
|
30
36
|
const testIds = useTestIds(testId, getGenAICoachTestIds);
|
|
@@ -34,9 +40,11 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
34
40
|
const genAICoachRef = useRef(null);
|
|
35
41
|
const [messageEls, getMessageCbRef] = useRefMap();
|
|
36
42
|
const focusInMessageListRef = useRef(false);
|
|
43
|
+
const mouseTriggeredFocusRef = useRef(false);
|
|
37
44
|
const isUserTriggered = useRef(false);
|
|
38
45
|
const lastFocusedMessageRef = useRef(null);
|
|
39
46
|
const initialFocusedElementRef = useRef(null);
|
|
47
|
+
const inputContainerRef = useRef(null);
|
|
40
48
|
const messageContainerRef = useRef(null);
|
|
41
49
|
const textAreaRef = useRef(null);
|
|
42
50
|
const [suggestionMenuButton, setSuggestionMenuButton] = useElement(null);
|
|
@@ -46,6 +54,20 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
46
54
|
const fileUploadInputRef = useRef(null);
|
|
47
55
|
const attachmentsAdded = useRef(false);
|
|
48
56
|
const pendingResult = useRef(false);
|
|
57
|
+
const scrollStateRef = useRef({
|
|
58
|
+
userAtBottom: false,
|
|
59
|
+
isStreamingActive: false,
|
|
60
|
+
initialBufferApplied: false,
|
|
61
|
+
smoothScrollOnEnd: false,
|
|
62
|
+
ignoreNextScrollEvent: false,
|
|
63
|
+
lastScrollTop: null,
|
|
64
|
+
bufferMinHeight: null
|
|
65
|
+
});
|
|
66
|
+
const observersRef = useRef({
|
|
67
|
+
inputContainer: null,
|
|
68
|
+
messageContainer: null,
|
|
69
|
+
streamingMessage: null
|
|
70
|
+
});
|
|
49
71
|
const isSmallOrAbove = useBreakpoint('sm', {
|
|
50
72
|
breakpointRef: genAICoachRef
|
|
51
73
|
});
|
|
@@ -56,15 +78,183 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
56
78
|
const [isComposerFocused, setIsComposerFocused] = useState(false);
|
|
57
79
|
const [animationInitialCursorPos, setAnimationInitialCursorPos] = useState(0);
|
|
58
80
|
const [fullScreen, setFullScreen] = useState(false);
|
|
81
|
+
const [showScrollToBottom, setShowScrollToBottom] = useState(false);
|
|
59
82
|
const prevFullScreen = usePrevious(fullScreen);
|
|
60
83
|
const previousMessages = usePrevious(messages);
|
|
61
84
|
const composerId = useUID();
|
|
62
85
|
const latestMessage = messages.at(-1);
|
|
86
|
+
const firstMessageId = messages.at(0)?.id;
|
|
87
|
+
const isLatestAgentMessageStreaming = !!latestMessage && isCoachMessage(latestMessage) && !!latestMessage.loading;
|
|
63
88
|
const condition = !(fullScreen && conversationHistory) &&
|
|
64
89
|
(variant.placement === 'dialog' ||
|
|
65
90
|
variant.placement === 'utilities' ||
|
|
66
91
|
(variant.placement === 'fullpage' && !isSmallOrAbove) ||
|
|
67
92
|
!conversationHistory);
|
|
93
|
+
const applyBuffer = useCallback(() => {
|
|
94
|
+
const container = messageContainerRef.current;
|
|
95
|
+
const conversationEl = conversationRef.current;
|
|
96
|
+
const scrollState = scrollStateRef.current;
|
|
97
|
+
if (!container || !conversationEl)
|
|
98
|
+
return;
|
|
99
|
+
const contentHeight = Array.from(conversationEl.children).reduce((sum, child) => {
|
|
100
|
+
if (child instanceof HTMLElement)
|
|
101
|
+
return sum + child.offsetHeight;
|
|
102
|
+
return sum;
|
|
103
|
+
}, 0);
|
|
104
|
+
const computedStyle = window.getComputedStyle(conversationEl);
|
|
105
|
+
const lineHeightPx = parseFloat(computedStyle.lineHeight) || 20;
|
|
106
|
+
const bufferSizePx = streamBufferLh * lineHeightPx;
|
|
107
|
+
const currentBuffer = scrollState.bufferMinHeight ?? 0;
|
|
108
|
+
const distanceToBufferEnd = currentBuffer - contentHeight;
|
|
109
|
+
const extensionThreshold = bufferExtensionLh * lineHeightPx;
|
|
110
|
+
if (currentBuffer === 0 || distanceToBufferEnd < extensionThreshold) {
|
|
111
|
+
const newBuffer = contentHeight + bufferSizePx;
|
|
112
|
+
scrollState.bufferMinHeight = newBuffer;
|
|
113
|
+
conversationEl.style.setProperty('--stream-buffer', `${newBuffer}px`);
|
|
114
|
+
}
|
|
115
|
+
}, []);
|
|
116
|
+
const manageBuffer = useCallback((action) => {
|
|
117
|
+
const container = messageContainerRef.current;
|
|
118
|
+
const conversationEl = conversationRef.current;
|
|
119
|
+
const scrollState = scrollStateRef.current;
|
|
120
|
+
if (!container || !conversationEl)
|
|
121
|
+
return;
|
|
122
|
+
if (action === 'clear') {
|
|
123
|
+
conversationEl.style.removeProperty('--stream-buffer');
|
|
124
|
+
scrollState.bufferMinHeight = null;
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (action === 'init') {
|
|
128
|
+
applyBuffer();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
if (!scrollState.isStreamingActive || !scrollState.userAtBottom)
|
|
132
|
+
return;
|
|
133
|
+
applyBuffer();
|
|
134
|
+
}, [applyBuffer]);
|
|
135
|
+
const followStreamingMessage = useCallback(() => {
|
|
136
|
+
const container = messageContainerRef.current;
|
|
137
|
+
const scrollState = scrollStateRef.current;
|
|
138
|
+
if (!container ||
|
|
139
|
+
!scrollState.isStreamingActive ||
|
|
140
|
+
!scrollState.userAtBottom ||
|
|
141
|
+
(scrollState.lastScrollTop !== null &&
|
|
142
|
+
container.scrollTop < scrollState.lastScrollTop - scrollTolerancePx))
|
|
143
|
+
return;
|
|
144
|
+
manageBuffer('extend');
|
|
145
|
+
const targetScroll = container.scrollHeight - container.clientHeight;
|
|
146
|
+
const currentScroll = container.scrollTop;
|
|
147
|
+
if (Math.abs(currentScroll - targetScroll) > scrollTolerancePx) {
|
|
148
|
+
scrollState.ignoreNextScrollEvent = true;
|
|
149
|
+
container.scrollTo({ top: targetScroll, behavior: 'auto' });
|
|
150
|
+
}
|
|
151
|
+
}, [manageBuffer]);
|
|
152
|
+
const scrollToLatest = useCallback((behavior = 'auto', preferLast = true) => {
|
|
153
|
+
const container = messageContainerRef.current;
|
|
154
|
+
const conversationEl = conversationRef.current;
|
|
155
|
+
const lastMessage = conversationEl?.lastElementChild;
|
|
156
|
+
if (!container)
|
|
157
|
+
return;
|
|
158
|
+
const scrollState = scrollStateRef.current;
|
|
159
|
+
if (preferLast && lastMessage && lastMessage instanceof HTMLElement) {
|
|
160
|
+
const scrollHeight = container.scrollHeight;
|
|
161
|
+
const clientHeight = container.clientHeight;
|
|
162
|
+
const targetScrollTop = scrollHeight - clientHeight - scrollThresholdPx / 2;
|
|
163
|
+
if (targetScrollTop > container.scrollTop) {
|
|
164
|
+
scrollState.ignoreNextScrollEvent = true;
|
|
165
|
+
container.scrollTo({ top: targetScrollTop, behavior: 'auto' });
|
|
166
|
+
}
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
scrollState.ignoreNextScrollEvent = true;
|
|
170
|
+
container.scrollTo({ top: container.scrollHeight, behavior });
|
|
171
|
+
}, []);
|
|
172
|
+
const updateScrollState = useCallback((isScrollEvent = false) => {
|
|
173
|
+
const container = messageContainerRef.current;
|
|
174
|
+
const conversationEl = conversationRef.current;
|
|
175
|
+
if (!container || !conversationEl) {
|
|
176
|
+
setShowScrollToBottom(false);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const state = scrollStateRef.current;
|
|
180
|
+
if (state.initialBufferApplied) {
|
|
181
|
+
scrollToLatest('auto', true);
|
|
182
|
+
setShowScrollToBottom(false);
|
|
183
|
+
state.initialBufferApplied = false;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (state.smoothScrollOnEnd) {
|
|
187
|
+
scrollToLatest('smooth', false);
|
|
188
|
+
setShowScrollToBottom(false);
|
|
189
|
+
state.smoothScrollOnEnd = false;
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (isScrollEvent && state.ignoreNextScrollEvent) {
|
|
193
|
+
state.ignoreNextScrollEvent = false;
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const isUserScroll = isScrollEvent;
|
|
197
|
+
const { clientHeight, scrollHeight, scrollTop } = container;
|
|
198
|
+
const trueContentHeight = Array.from(conversationEl.children).reduce((sum, child) => (child instanceof HTMLElement ? sum + child.offsetHeight : sum), 0);
|
|
199
|
+
const hasOverflow = trueContentHeight > clientHeight + scrollTolerancePx;
|
|
200
|
+
const distanceFromBufferedBottom = scrollHeight - scrollTop - clientHeight;
|
|
201
|
+
const isAtBufferedBottom = distanceFromBufferedBottom <= scrollThresholdPx;
|
|
202
|
+
if (!state.isStreamingActive) {
|
|
203
|
+
const distanceFromBottom = trueContentHeight - scrollTop - clientHeight;
|
|
204
|
+
const isAtBottom = distanceFromBottom <= scrollThresholdPx;
|
|
205
|
+
setShowScrollToBottom(hasOverflow && !isAtBottom);
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (isUserScroll) {
|
|
209
|
+
const previousScrollTop = state.lastScrollTop;
|
|
210
|
+
state.lastScrollTop = scrollTop;
|
|
211
|
+
if (previousScrollTop !== null && scrollTop < previousScrollTop - scrollTolerancePx) {
|
|
212
|
+
state.userAtBottom = false;
|
|
213
|
+
}
|
|
214
|
+
else if (isAtBufferedBottom) {
|
|
215
|
+
state.userAtBottom = true;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (state.userAtBottom && isAtBufferedBottom) {
|
|
219
|
+
followStreamingMessage();
|
|
220
|
+
setShowScrollToBottom(false);
|
|
221
|
+
}
|
|
222
|
+
else {
|
|
223
|
+
setShowScrollToBottom(hasOverflow && !isAtBufferedBottom);
|
|
224
|
+
}
|
|
225
|
+
}, [followStreamingMessage, scrollToLatest]);
|
|
226
|
+
const updateScrollStateOnResize = useMemo(() => throttle(() => updateScrollState(false), resizeThrottleMs), [updateScrollState]);
|
|
227
|
+
const updateScrollStateOnScrollEvent = useMemo(() => throttle(() => updateScrollState(true), scrollThrottleMs), [updateScrollState]);
|
|
228
|
+
const handleScrollToBottomClick = useCallback(() => {
|
|
229
|
+
const scrollState = scrollStateRef.current;
|
|
230
|
+
const isStreaming = scrollState.isStreamingActive;
|
|
231
|
+
scrollToLatest(isStreaming ? 'auto' : 'smooth', false);
|
|
232
|
+
if (isStreaming) {
|
|
233
|
+
scrollState.userAtBottom = true;
|
|
234
|
+
}
|
|
235
|
+
setShowScrollToBottom(false);
|
|
236
|
+
}, [scrollToLatest]);
|
|
237
|
+
const setInputContainerRef = useCallback((el) => {
|
|
238
|
+
if (inputContainerRef.current === el)
|
|
239
|
+
return;
|
|
240
|
+
observersRef.current.inputContainer?.disconnect();
|
|
241
|
+
observersRef.current.inputContainer = null;
|
|
242
|
+
inputContainerRef.current = el;
|
|
243
|
+
if (!el)
|
|
244
|
+
return;
|
|
245
|
+
const ro = new ResizeObserver(entries => {
|
|
246
|
+
const entry = entries[0];
|
|
247
|
+
if (entry && genAICoachRef.current) {
|
|
248
|
+
const height = Math.round(entry.contentRect.height);
|
|
249
|
+
genAICoachRef.current.style.setProperty('--composer-height', `${height}px`);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
ro.observe(el);
|
|
253
|
+
observersRef.current.inputContainer = ro;
|
|
254
|
+
}, []);
|
|
255
|
+
const setMessageContainerRef = useCallback((el) => {
|
|
256
|
+
messageContainerRef.current = el;
|
|
257
|
+
}, []);
|
|
68
258
|
const onCoachChange = (id) => {
|
|
69
259
|
setCoachOptions(cur => {
|
|
70
260
|
return cur.map(coach => {
|
|
@@ -207,6 +397,9 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
207
397
|
window.addEventListener('resize', onResize);
|
|
208
398
|
return () => {
|
|
209
399
|
window.removeEventListener('resize', onResize);
|
|
400
|
+
Object.values(observersRef.current).forEach(observer => {
|
|
401
|
+
observer?.disconnect();
|
|
402
|
+
});
|
|
210
403
|
};
|
|
211
404
|
}, []);
|
|
212
405
|
useEffect(() => {
|
|
@@ -214,12 +407,6 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
214
407
|
const currentFilteredMsgs = messages?.filter(item => !hasProp(item, 'loading')) ?? [];
|
|
215
408
|
if (prevFilteredMsgs.length < currentFilteredMsgs.length ||
|
|
216
409
|
(latestMessage && hasProp(latestMessage, 'loading'))) {
|
|
217
|
-
setTimeout(() => {
|
|
218
|
-
conversationRef.current?.lastElementChild?.scrollIntoView({
|
|
219
|
-
behavior: 'smooth',
|
|
220
|
-
block: 'nearest'
|
|
221
|
-
});
|
|
222
|
-
}, 0);
|
|
223
410
|
if (latestMessage && isUserMessage(latestMessage)) {
|
|
224
411
|
textAreaRef.current?.focus();
|
|
225
412
|
setLastFocusableElement();
|
|
@@ -234,6 +421,71 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
234
421
|
}
|
|
235
422
|
}
|
|
236
423
|
}, [messages]);
|
|
424
|
+
useEffect(() => {
|
|
425
|
+
const scrollState = scrollStateRef.current;
|
|
426
|
+
const wasStreaming = scrollState.isStreamingActive;
|
|
427
|
+
if (isLatestAgentMessageStreaming && !wasStreaming) {
|
|
428
|
+
scrollStateRef.current = {
|
|
429
|
+
...scrollStateRef.current,
|
|
430
|
+
isStreamingActive: true,
|
|
431
|
+
initialBufferApplied: true,
|
|
432
|
+
userAtBottom: false,
|
|
433
|
+
smoothScrollOnEnd: false,
|
|
434
|
+
lastScrollTop: null
|
|
435
|
+
};
|
|
436
|
+
requestAnimationFrame(() => {
|
|
437
|
+
manageBuffer('init');
|
|
438
|
+
});
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
if (!isLatestAgentMessageStreaming && wasStreaming) {
|
|
442
|
+
scrollStateRef.current = {
|
|
443
|
+
...scrollStateRef.current,
|
|
444
|
+
isStreamingActive: false,
|
|
445
|
+
initialBufferApplied: false,
|
|
446
|
+
userAtBottom: false,
|
|
447
|
+
smoothScrollOnEnd: true
|
|
448
|
+
};
|
|
449
|
+
manageBuffer('clear');
|
|
450
|
+
}
|
|
451
|
+
}, [isLatestAgentMessageStreaming, manageBuffer]);
|
|
452
|
+
useEffect(() => {
|
|
453
|
+
updateScrollState(false);
|
|
454
|
+
setShowScrollToBottom(false);
|
|
455
|
+
}, [firstMessageId, updateScrollState]);
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
const container = messageContainerRef.current;
|
|
458
|
+
const content = conversationRef.current;
|
|
459
|
+
if (!container || !content)
|
|
460
|
+
return;
|
|
461
|
+
const containerRO = new ResizeObserver(updateScrollStateOnResize);
|
|
462
|
+
containerRO.observe(container);
|
|
463
|
+
containerRO.observe(content);
|
|
464
|
+
observersRef.current.messageContainer = containerRO;
|
|
465
|
+
let streamingMessageRO = null;
|
|
466
|
+
const lastMessageEl = content.lastElementChild;
|
|
467
|
+
if (isLatestAgentMessageStreaming && lastMessageEl) {
|
|
468
|
+
const handleStreamingUpdate = throttle(() => {
|
|
469
|
+
followStreamingMessage();
|
|
470
|
+
updateScrollState(false);
|
|
471
|
+
}, resizeThrottleMs);
|
|
472
|
+
streamingMessageRO = new ResizeObserver(handleStreamingUpdate);
|
|
473
|
+
streamingMessageRO.observe(lastMessageEl);
|
|
474
|
+
observersRef.current.streamingMessage = streamingMessageRO;
|
|
475
|
+
}
|
|
476
|
+
return () => {
|
|
477
|
+
containerRO.disconnect();
|
|
478
|
+
observersRef.current.messageContainer = null;
|
|
479
|
+
streamingMessageRO?.disconnect();
|
|
480
|
+
observersRef.current.streamingMessage = null;
|
|
481
|
+
};
|
|
482
|
+
}, [
|
|
483
|
+
messages.length,
|
|
484
|
+
isLatestAgentMessageStreaming,
|
|
485
|
+
followStreamingMessage,
|
|
486
|
+
updateScrollState,
|
|
487
|
+
updateScrollStateOnResize
|
|
488
|
+
]);
|
|
237
489
|
useEffect(() => {
|
|
238
490
|
if (suggestionMenuButton && isGeneratingResponse.current) {
|
|
239
491
|
announcePolite({ message: t('response_generated') });
|
|
@@ -339,9 +591,9 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
339
591
|
const fullPageAndInitialScreen = (fullScreen || (variant.placement === 'fullpage' && !isInLandingPageUtilities)) &&
|
|
340
592
|
messages.length === 0;
|
|
341
593
|
const renderTextArea = fullPageAndInitialScreen ? null : genAIFooter;
|
|
342
|
-
const GenAICoachElement = (_jsx(_Fragment, { children: ((isInUtilities(variant) && variant.state !== 'minimized') || !isInUtilities(variant)) && (_jsxs(Flex, { container: { direction: 'column' }, as: StyledFlexWrapper, children: [_jsx(Flex, { as: StyledMessagesContainer, ref:
|
|
594
|
+
const GenAICoachElement = (_jsx(_Fragment, { children: ((isInUtilities(variant) && variant.state !== 'minimized') || !isInUtilities(variant)) && (_jsxs(Flex, { container: { direction: 'column' }, as: StyledFlexWrapper, children: [_jsx(Flex, { as: StyledMessagesContainer, ref: setMessageContainerRef, container: loading || error
|
|
343
595
|
? { direction: 'column', pad: [0, 2], justify: 'center' }
|
|
344
|
-
: { direction: 'column', pad: [0, 2] }, item: { grow: 1 }, children: loading || error ? (_jsx(Flex, { container: { justify: 'center', alignItems: 'center' }, children: loading ? (_jsx(Progress, { variant: 'ring', placement: 'block', message: typeof loading === 'string' ? loading : t('loading') })) : (_jsx(ErrorState, { message: error })) })) : (_jsx(_Fragment, { children: messages.length === 0 ? (_jsxs(Flex, { as: StyledInitialMessageContainer, container: {
|
|
596
|
+
: { direction: 'column', pad: [0, 2] }, item: { grow: 1 }, onScroll: updateScrollStateOnScrollEvent, children: loading || error ? (_jsx(Flex, { container: { justify: 'center', alignItems: 'center' }, children: loading ? (_jsx(Progress, { variant: 'ring', placement: 'block', message: typeof loading === 'string' ? loading : t('loading') })) : (_jsx(ErrorState, { message: error })) })) : (_jsx(_Fragment, { children: messages.length === 0 ? (_jsxs(Flex, { as: StyledInitialMessageContainer, container: {
|
|
345
597
|
justify: 'center',
|
|
346
598
|
direction: 'column',
|
|
347
599
|
alignItems: 'center',
|
|
@@ -360,7 +612,21 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
360
612
|
}
|
|
361
613
|
isUserTriggered.current = true;
|
|
362
614
|
textAreaRef.current?.focus();
|
|
363
|
-
}, testId: initialSuggestedMessage.id }))) }), fullPageAndInitialScreen && !guidedMode && genAIFooter] })) : (_jsxs(Flex, { as: StyledMessagesList, ref: conversationRef, "aria-label": `${t('conversation')} ${t('view')}`, container: { direction: 'column' },
|
|
615
|
+
}, testId: initialSuggestedMessage.id }))) }), fullPageAndInitialScreen && !guidedMode && genAIFooter] })) : (_jsxs(Flex, { as: StyledMessagesList, ref: conversationRef, "aria-label": `${t('conversation')} ${t('view')}`, container: { direction: 'column' }, onMouseDown: (e) => {
|
|
616
|
+
mouseTriggeredFocusRef.current = true;
|
|
617
|
+
const target = e.target instanceof Element ? e.target : null;
|
|
618
|
+
const clickedMessage = getMessageContainingElement(target);
|
|
619
|
+
if (clickedMessage) {
|
|
620
|
+
lastFocusedMessageRef.current = clickedMessage;
|
|
621
|
+
}
|
|
622
|
+
}, onMouseUp: () => {
|
|
623
|
+
mouseTriggeredFocusRef.current = false;
|
|
624
|
+
}, onFocus: () => {
|
|
625
|
+
if (mouseTriggeredFocusRef.current) {
|
|
626
|
+
mouseTriggeredFocusRef.current = false;
|
|
627
|
+
focusInMessageListRef.current = true;
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
364
630
|
if (!focusInMessageListRef.current) {
|
|
365
631
|
if (lastFocusedMessageRef.current) {
|
|
366
632
|
lastFocusedMessageRef.current.focus();
|
|
@@ -456,7 +722,7 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
456
722
|
}
|
|
457
723
|
}, ...(prevFullScreen !== fullScreen ? { animationInitialCursorPos } : {}) }));
|
|
458
724
|
})] })) })) }), ((isInUtilities(variant) && variant.state !== 'minimized') || !isInUtilities(variant)) &&
|
|
459
|
-
!loading && (_jsx(Flex, { container: { direction: 'column' }, as: StyledInputContainer, children: _jsx(StyledComposerSection, { children: guidedMode ? (_jsx(_Fragment, { children: suggestions && messages.length > 0 && (_jsxs(_Fragment, { children: [latestMessage &&
|
|
725
|
+
!loading && (_jsx(Flex, { container: { direction: 'column' }, as: StyledInputContainer, ref: setInputContainerRef, children: _jsx(StyledComposerSection, { children: guidedMode ? (_jsx(_Fragment, { children: suggestions && messages.length > 0 && (_jsxs(_Fragment, { children: [latestMessage &&
|
|
460
726
|
isCoachMessage(latestMessage) &&
|
|
461
727
|
latestMessage.loading ? (_jsxs(Flex, { container: {
|
|
462
728
|
gap: 1,
|
|
@@ -482,7 +748,7 @@ const GenAICoach = ({ testId, coachOptions: coachOptionsProps, onCoachChange: on
|
|
|
482
748
|
});
|
|
483
749
|
}
|
|
484
750
|
}
|
|
485
|
-
} }) })), _jsx(Flex, { container: { justify: 'center' }, children: _jsx(StyledDisclaimerText, { inFullPage: !condition, children: t('ai_disclaimer') }) })] })) })) : (renderTextArea) }) }))] })) }));
|
|
751
|
+
} }) })), _jsx(Flex, { container: { justify: 'center' }, children: _jsx(StyledDisclaimerText, { inFullPage: !condition, children: t('ai_disclaimer') }) })] })) })) : (renderTextArea) }) })), showScrollToBottom && (_jsx(StyledScrollButton, { icon: true, onClick: handleScrollToBottomClick, "aria-label": t('scroll_to_latest_message'), children: _jsx(Icon, { name: 'caret-down' }) }))] })) }));
|
|
486
752
|
useEffect(() => {
|
|
487
753
|
if (focusTextArea.current && !conversationHistory && textAreaRef.current) {
|
|
488
754
|
textAreaRef.current.focus();
|