@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.
@@ -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: FunctionComponent<GenAICoachProps> & {
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":"AACA,OAAO,KAAK,EAAe,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAqF5D,OAAO,EAAyC,KAAK,eAAe,EAAE,MAAM,GAAG,CAAC;AAchF,eAAO,MAAM,SAAS,+CAmDrB,CAAC;;;;AA47BF,wBAA6D"}
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, StyledActionsContainer, StyledGenAIFooter, StyledAgentIcon, StyledFullscreenWrapper, StyledSalutation } from './GenAICoach.styles';
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: messageContainerRef, container: loading || error
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' }, onFocus: () => {
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();