Skip to content

Commit 7e739d0

Browse files
committed
Refactorings and re-render optimizations
1 parent c95129a commit 7e739d0

File tree

6 files changed

+66
-59
lines changed

6 files changed

+66
-59
lines changed

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/AskAiSuggestions.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,13 @@ import { EuiButton, useEuiTheme } from '@elastic/eui'
44
import { css } from '@emotion/react'
55
import * as React from 'react'
66

7+
const buttonStyles = css`
8+
border: none;
9+
& > span {
10+
justify-content: flex-start;
11+
}
12+
`
13+
714
export interface AskAiSuggestion {
815
question: string
916
}
@@ -16,15 +23,14 @@ export const AskAiSuggestions = (props: Props) => {
1623
const { submitQuestion } = useChatActions()
1724
const { setModalMode } = useModalActions()
1825
const { euiTheme } = useEuiTheme()
19-
const buttonCss = css`
20-
border: none;
21-
& > span {
22-
justify-content: flex-start;
23-
}
26+
27+
const dynamicButtonStyles = css`
28+
${buttonStyles}
2429
svg {
2530
color: ${euiTheme.colors.textSubdued};
2631
}
2732
`
33+
2834
return (
2935
<ul>
3036
{Array.from(props.suggestions).map((suggestion) => (
@@ -34,7 +40,7 @@ export const AskAiSuggestions = (props: Props) => {
3440
color="text"
3541
fullWidth
3642
size="s"
37-
css={buttonCss}
43+
css={dynamicButtonStyles}
3844
onClick={() => {
3945
submitQuestion(suggestion.question)
4046
setModalMode('askAi')

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/Chat.tsx

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,29 @@ import { css } from '@emotion/react'
1717
import * as React from 'react'
1818
import { useCallback, useEffect, useRef } from 'react'
1919

20+
const containerStyles = css`
21+
height: 100%;
22+
max-height: 70vh;
23+
overflow: hidden;
24+
`
25+
26+
const scrollContainerStyles = css`
27+
position: relative;
28+
overflow: hidden;
29+
`
30+
31+
const scrollableStyles = css`
32+
height: 100%;
33+
overflow-y: auto;
34+
scrollbar-gutter: stable;
35+
padding: 1rem;
36+
`
37+
38+
const messagesStyles = css`
39+
max-width: 800px;
40+
margin: 0 auto;
41+
`
42+
2043
// Small helper for scroll behavior
2144
const scrollToBottom = (container: HTMLDivElement | null) => {
2245
if (!container) return
@@ -44,28 +67,9 @@ export const Chat = () => {
4467
const scrollRef = useRef<HTMLDivElement>(null)
4568
const lastMessageStatusRef = useRef<string | null>(null)
4669

47-
const containerStyles = css`
48-
height: 100%;
49-
max-height: 70vh;
50-
overflow: hidden;
51-
`
52-
53-
const scrollContainerStyles = css`
54-
position: relative;
55-
overflow: hidden;
56-
`
57-
58-
const scrollableStyles = css`
59-
height: 100%;
60-
overflow-y: auto;
61-
scrollbar-gutter: stable;
70+
const dynamicScrollableStyles = css`
71+
${scrollableStyles}
6272
${useEuiOverflowScroll('y', true)}
63-
padding: 1rem;
64-
`
65-
66-
const messagesStyles = css`
67-
max-width: 800px;
68-
margin: 0 auto;
6973
`
7074

7175
const handleSubmit = useCallback(
@@ -123,7 +127,7 @@ export const Chat = () => {
123127

124128
{/* Messages */}
125129
<EuiFlexItem grow={true} css={scrollContainerStyles}>
126-
<div ref={scrollRef} css={scrollableStyles}>
130+
<div ref={scrollRef} css={dynamicScrollableStyles}>
127131
{messages.length === 0 ? (
128132
<EuiEmptyPrompt
129133
iconType="logoElastic"

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/ChatMessage.tsx

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,23 @@ const createMarkedInstance = () => {
3939
return new Marked({ renderer })
4040
}
4141

42-
const markedInstance = createMarkedInstance() // Created once globally
42+
const markedInstance = createMarkedInstance()
4343

4444
interface ChatMessageProps {
4545
message: ChatMessageType
4646
llmMessages?: LlmGatewayMessage[]
47+
streamingContent?: string
4748
error?: Error | null
4849
onRetry?: () => void
4950
}
5051

51-
// Helper function to accumulate AI message content
5252
const getAccumulatedContent = (messages: LlmGatewayMessage[]) => {
5353
return messages
54-
.filter((m) => m.type === 'ai_message_chunk') // Only accumulate chunks, not the final message
54+
.filter((m) => m.type === 'ai_message_chunk')
5555
.map((m) => m.data.content)
5656
.join('')
5757
}
5858

59-
// Derived state helper for readability
6059
const getMessageState = (message: ChatMessageType) => ({
6160
isUser: message.type === 'user',
6261
isLoading: message.status === 'streaming',
@@ -127,6 +126,7 @@ const ActionBar = ({
127126
export const ChatMessage = ({
128127
message,
129128
llmMessages = [],
129+
streamingContent,
130130
error,
131131
onRetry,
132132
}: ChatMessageProps) => {
@@ -166,27 +166,22 @@ export const ChatMessage = ({
166166
)
167167
}
168168

169-
// AI message
170-
const content =
171-
llmMessages.length > 0
172-
? getAccumulatedContent(llmMessages)
173-
: message.content
169+
const content = streamingContent
170+
|| (llmMessages.length > 0 ? getAccumulatedContent(llmMessages) : message.content)
174171

175172
const hasError = message.status === 'error' || !!error
176173

177-
// Memoize the parsed HTML to avoid re-parsing on every render
178174
const parsed = useMemo(() => {
179175
const html = markedInstance.parse(content) as string
180176
return DOMPurify.sanitize(html)
181177
}, [content])
178+
182179
const ref = React.useRef<HTMLDivElement>(null)
183180

184-
// Initialize copy buttons after DOM is updated
185181
useEffect(() => {
186182
if (isComplete && ref.current) {
187183
const timer = setTimeout(() => {
188184
try {
189-
// Use the modified initCopyButton with scoped element
190185
initCopyButton(
191186
'.highlight pre',
192187
ref.current!,
@@ -198,7 +193,7 @@ export const ChatMessage = ({
198193
}, 100)
199194
return () => clearTimeout(timer)
200195
}
201-
}, [isComplete]) // Only depend on isComplete, not parsed
196+
}, [isComplete])
202197

203198
return (
204199
<EuiFlexGroup

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/AskAi/StreamingAiMessage.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ export const StreamingAiMessage = ({
2727
onMessage: (llmMessage) => {
2828
if (llmMessage.type === 'ai_message_chunk') {
2929
contentRef.current += llmMessage.data.content
30-
updateAiMessage(message.id, contentRef.current, 'streaming')
3130
} else if (llmMessage.type === 'agent_end') {
3231
updateAiMessage(message.id, contentRef.current, 'complete')
3332
}
@@ -41,8 +40,6 @@ export const StreamingAiMessage = ({
4140
},
4241
})
4342

44-
// Send question when this is the last message and status is streaming
45-
// Use store-level tracking so it persists across remounts
4643
useEffect(() => {
4744
if (
4845
isLast &&
@@ -68,6 +65,7 @@ export const StreamingAiMessage = ({
6865
<ChatMessage
6966
message={message}
7067
llmMessages={isLast ? llmMessages : []}
68+
streamingContent={isLast && message.status === 'streaming' ? contentRef.current : undefined}
7169
/>
7270
)
7371
}

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/Search/Search.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import { EuiFieldSearch, EuiSpacer, EuiButton, useEuiTheme } from '@elastic/eui'
77
import { css } from '@emotion/react'
88
import * as React from 'react'
99

10+
const askAiButtonStyles = css`
11+
font-weight: bold;
12+
`
13+
1014
export const Search = () => {
1115
const searchTerm = useSearchTerm()
1216
const { setSearchTerm } = useSearchActions()
@@ -53,15 +57,10 @@ export const Search = () => {
5357
}
5458

5559
const AskAiButton = ({ term, onAsk }: { term: string; onAsk: () => void }) => {
56-
const { euiTheme } = useEuiTheme()
5760
return (
5861
<EuiButton iconType="newChat" fullWidth onClick={onAsk}>
5962
Ask AI about{' '}
60-
<span
61-
css={css`
62-
font-weight: ${euiTheme.font.weight.bold};
63-
`}
64-
>
63+
<span css={askAiButtonStyles}>
6564
"{term}"
6665
</span>
6766
</EuiButton>

src/Elastic.Documentation.Site/Assets/web-components/SearchOrAskAi/SearchOrAskAiModal.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { css } from '@emotion/react'
1414
import * as React from 'react'
1515
import { useMemo } from 'react'
1616

17-
export const SearchOrAskAiModal = () => {
17+
export const SearchOrAskAiModal = React.memo(() => {
1818
const modalMode = useModalMode()
1919
const { setModalMode } = useModalActions()
2020

@@ -24,18 +24,13 @@ export const SearchOrAskAiModal = () => {
2424
id: 'search',
2525
name: 'Search',
2626
prepend: <EuiIcon type="search" />,
27-
content: <Search />,
27+
content: <></>,
2828
},
2929
{
3030
id: 'askAi',
3131
name: 'Ask AI',
3232
prepend: <EuiIcon type="sparkles" />,
33-
content: (
34-
<>
35-
<EuiSpacer size="m" />
36-
<Chat />
37-
</>
38-
),
33+
content: <></>,
3934
},
4035
],
4136
[]
@@ -50,10 +45,20 @@ export const SearchOrAskAiModal = () => {
5045
selectedTab={selectedTab}
5146
onTabClick={(tab) => setModalMode(tab.id as 'search' | 'askAi')}
5247
/>
48+
49+
{modalMode === 'search' ? (
50+
<Search />
51+
) : (
52+
<>
53+
<EuiSpacer size="m" />
54+
<Chat />
55+
</>
56+
)}
57+
5358
<ModalFooter />
5459
</>
5560
)
56-
}
61+
})
5762

5863
const ModalFooter = () => {
5964
return (

0 commit comments

Comments
 (0)