Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/Elastic.Documentation.Site/Assets/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ initializeOtel({
// Dynamically import web components after telemetry is initialized
// This ensures telemetry is available when the components execute
// Parcel will automatically code-split this into a separate chunk
import('./web-components/SearchOrAskAi/SearchOrAskAi')
import('./web-components/NavigationSearch/NavigationSearchComponent')
import('./web-components/AskAi/AskAi')
import('./web-components/VersionDropdown')
import('./web-components/AppliesToPopover')

Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,36 @@
import '../../eui-icons-cache'
import { NavigationSearch } from './NavigationSearch'
import {
// ModalMode,
useModalActions,
useModalIsOpen,
useModalMode,
} from './modal.store'
import { useAskAiModalActions, useAskAiModalIsOpen } from './askAi.modal.store'
import {
EuiPortal,
EuiOverlayMask,
EuiFocusTrap,
EuiPanel,
EuiLoadingSpinner,
EuiProvider,
useEuiTheme,
} from '@elastic/eui'
import { css } from '@emotion/react'
import { useQuery } from '@tanstack/react-query'
import { useEffect, Suspense, lazy } from 'react'
import r2wc from '@r2wc/react-to-web-component'
import {
QueryClient,
QueryClientProvider,
useQuery,
} from '@tanstack/react-query'
import { useEffect, Suspense, lazy, StrictMode } from 'react'

const queryClient = new QueryClient()

// Lazy load the modal component
const SearchOrAskAiModal = lazy(() =>
import('./SearchOrAskAiModal').then((module) => ({
default: module.SearchOrAskAiModal,
const LazyAskAiModal = lazy(() =>
import('./AskAiModal').then((module) => ({
default: module.AskAiModal,
}))
)

export const SearchOrAskAiButton = () => {
const AskAiButton = () => {
const { euiTheme } = useEuiTheme()
const isModalOpen = useModalIsOpen()
const modalMode = useModalMode()
const {
// openModal,
closeModal,
// setModalMode
} = useModalActions()
const isModalOpen = useAskAiModalIsOpen()
const { openModal, closeModal } = useAskAiModalActions()

const { data: isApiAvailable } = useQuery({
queryKey: ['api-health'],
Expand Down Expand Up @@ -62,43 +59,26 @@ export const SearchOrAskAiButton = () => {
padding: 2rem;
`

// const openAndSetModalMode = (mode: ModalMode) => {
// setModalMode(mode)
// if (!isModalOpen) {
// openModal()
// }
// }

// const openAskAiModal = () => openAndSetModalMode('askAi')
// const openSearchModal = () => openAndSetModalMode('search')

// Prevent layout jump when hiding the scrollbar by compensating its width

useEffect(() => {
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
closeModal()
//closeModal()
}

// Cmd+; to open Ask AI modal
if (
(event.metaKey || event.ctrlKey) &&
event.code === 'Semicolon'
) {
//event.preventDefault()
//openModal()
}
// Cmd+K is now handled by NavigationSearch to focus the input
// if ((event.metaKey || event.ctrlKey) && event.key === 'k') {
// event.preventDefault()
// openSearchModal()
// }

// if (
// (event.metaKey || event.ctrlKey) &&
// event.code === 'Semicolon'
// ) {
// event.preventDefault()
// openAskAiModal()
// // Input focuses itself via its own Cmd+; listener
// }
}
window.addEventListener('keydown', handleKeydown)
return () => {
window.removeEventListener('keydown', handleKeydown)
}
}, [isModalOpen, modalMode])
}, [openModal, closeModal])

useEffect(() => {
if (!isModalOpen) return
Expand Down Expand Up @@ -132,19 +112,7 @@ export const SearchOrAskAiButton = () => {
}

return (
<div>
{/*<EuiToolTip content="Keyboard shortcut: ⌘;">*/}
{/* <ElasticAiAssistantButton*/}
{/* size="s"*/}
{/* iconType={AiIcon}*/}
{/* onClick={openAskAiModal}*/}
{/* >*/}
{/* Ask AI Assistant*/}
{/* </ElasticAiAssistantButton>*/}
{/*</EuiToolTip>*/}

<NavigationSearch />

<>
{isModalOpen && (
<EuiPortal>
<EuiOverlayMask>
Expand All @@ -161,13 +129,31 @@ export const SearchOrAskAiButton = () => {
</div>
}
>
<SearchOrAskAiModal />
<LazyAskAiModal />
</Suspense>
</EuiPanel>
</EuiFocusTrap>
</EuiOverlayMask>
</EuiPortal>
)}
</div>
</>
)
}

const AskAi = () => {
return (
<StrictMode>
<EuiProvider
colorMode="light"
globalStyles={false}
utilityClasses={false}
>
<QueryClientProvider client={queryClient}>
<AskAiButton />
</QueryClientProvider>
</EuiProvider>
</StrictMode>
)
}

customElements.define('ask-ai', r2wc(AskAi))
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Chat } from './Chat'
import { useAskAiCooldown, useAskAiCooldownActions } from './useAskAiCooldown'
import { useCooldown } from './useCooldown'
import * as React from 'react'

export const AskAiModal = React.memo(() => {
// Manage cooldown countdowns at the modal level so they continue running
const askAiCooldown = useAskAiCooldown()
const { notifyCooldownFinished: notifyAskAiCooldownFinished } =
useAskAiCooldownActions()

useCooldown({
domain: 'askAi',
cooldown: askAiCooldown,
onCooldownFinished: () => notifyAskAiCooldownFinished(),
})

return <Chat />
})
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { useModalActions } from '../modal.store'
import { useChatActions } from './chat.store'
import { useIsAskAiCooldownActive } from './useAskAiCooldown'
import { EuiButton, EuiText, useEuiTheme, EuiSpacer } from '@elastic/eui'
Expand Down Expand Up @@ -39,7 +38,6 @@ const ALL_SUGGESTIONS: AskAiSuggestion[] = [

export const AskAiSuggestions = () => {
const { submitQuestion } = useChatActions()
const { setModalMode } = useModalActions()
const disabled = useIsAskAiCooldownActive()
const { euiTheme } = useEuiTheme()

Expand Down Expand Up @@ -72,7 +70,6 @@ export const AskAiSuggestions = () => {
onClick={() => {
if (!disabled) {
submitQuestion(suggestion.question)
setModalMode('askAi')
}
}}
disabled={disabled}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { cooldownStore } from '../cooldown.store'
import { modalStore } from '../modal.store'
import { cooldownStore } from '../shared/cooldown.store'
import { Chat } from './Chat'
import { chatStore } from './chat.store'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
Expand Down Expand Up @@ -38,10 +37,6 @@ const resetStores = () => {
aiProvider: 'LlmGateway',
scrollPosition: 0,
})
modalStore.setState({
isOpen: false,
mode: 'search',
})
cooldownStore.setState({
cooldowns: {
search: { cooldown: null, awaitingNewInput: false },
Expand Down Expand Up @@ -398,21 +393,4 @@ describe('Chat Component', () => {
}
})
})

describe('Close modal', () => {
it('should close modal when close button is clicked', async () => {
// Arrange
modalStore.setState({ isOpen: true })
const user = userEvent.setup()

// Act
render(<Chat />)
await user.click(
screen.getByRole('button', { name: /close ask ai modal/i })
)

// Assert
expect(modalStore.getState().isOpen).toBe(false)
})
})
})
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { InfoBanner } from '../InfoBanner'
import { KeyboardShortcutsFooter } from '../KeyboardShortcutsFooter'
import { LegalDisclaimer } from '../LegalDisclaimer'
import AiIcon from '../ai-icon.svg'
import { useModalActions } from '../modal.store'
import { AskAiSuggestions } from './AskAiSuggestions'
import { ChatInput } from './ChatInput'
import { ChatMessageList } from './ChatMessageList'
import { InfoBanner } from './InfoBanner'
import { KeyboardShortcutsFooter } from './KeyboardShortcutsFooter'
import { LegalDisclaimer } from './LegalDisclaimer'
import AiIcon from './ai-icon.svg'
import { useAskAiModalActions } from './askAi.modal.store'
import {
ChatMessage,
useChatActions,
Expand Down Expand Up @@ -119,7 +119,7 @@ export const Chat = () => {
}

const ChatHeader = () => {
const { closeModal } = useModalActions()
const { closeModal } = useAskAiModalActions()
const { clearChat } = useChatActions()
const messages = useChatMessages()
const { euiTheme } = useEuiTheme()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ElasticAiAssistantButtonIcon } from '../ElasticAiAssitant'
import { ElasticAiAssistantButtonIcon } from './ElasticAiAssitant'
import { euiShadow, useEuiScrollBar, useEuiTheme } from '@elastic/eui'
import { css } from '@emotion/react'
import { useCallback, useEffect, useRef } from 'react'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cooldownStore } from '../cooldown.store'
import { ApiError } from '../errorHandling'
import { cooldownStore } from '../shared/cooldown.store'
import { ApiError } from '../shared/errorHandling'
import { ChatMessage } from './ChatMessage'
import { ChatMessage as ChatMessageType } from './chat.store'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { initCopyButton } from '../../../copybutton'
import { hljs } from '../../../hljs'
import { aiGradients } from '../ElasticAiAssitant'
import { SearchOrAskAiErrorCallout } from '../SearchOrAskAiErrorCallout'
import { ApiError } from '../errorHandling'
import { initCopyButton } from '../../copybutton'
import { hljs } from '../../hljs'
import { ErrorCallout } from '../shared/ErrorCallout'
import { ApiError } from '../shared/errorHandling'
import { AskAiEvent, ChunkEvent, EventTypes } from './AskAiEvent'
import { aiGradients } from './ElasticAiAssitant'
import { GeneratingStatus } from './GeneratingStatus'
import { References } from './RelatedResources'
import { ChatMessage as ChatMessageType, useConversationId } from './chat.store'
Expand Down Expand Up @@ -487,7 +487,7 @@ export const ChatMessage = ({
</div>
</EuiFlexItem>
<EuiFlexItem>
<SearchOrAskAiErrorCallout
<ErrorCallout
error={message.error || error || null}
domain="askAi"
inline={true}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { create } from 'zustand/react'

interface ModalState {
isOpen: boolean
actions: {
openModal: () => void
closeModal: () => void
toggleModal: () => void
}
}

const askAiModalStore = create<ModalState>((set) => ({
isOpen: false,
actions: {
openModal: () => set({ isOpen: true }),
closeModal: () => set({ isOpen: false }),
toggleModal: () => set((state) => ({ isOpen: !state.isOpen })),
},
}))

export const useAskAiModalIsOpen = () =>
askAiModalStore((state) => state.isOpen)
export const useAskAiModalActions = () =>
askAiModalStore((state) => state.actions)

export { askAiModalStore }
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { logError, logWarn } from '../../../telemetry/logging'
import { cooldownStore } from '../cooldown.store'
import { logError, logWarn } from '../../telemetry/logging'
import { cooldownStore } from '../shared/cooldown.store'
import {
ApiError,
createApiErrorFromResponse,
isApiError,
isRateLimitError,
} from '../errorHandling'
} from '../shared/errorHandling'
import { AskAiEvent, AskAiEventSchema } from './AskAiEvent'
import { MessageThrottler } from './MessageThrottler'
import {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCooldownState, useCooldownActions } from '../cooldown.store'
import { useCooldownState, useCooldownActions } from '../shared/cooldown.store'

export const useAskAiCooldown = () => {
const state = useCooldownState('askAi')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { ApiError } from '../shared/errorHandling'
import { useRateLimitHandler } from '../shared/useRateLimitHandler'

export function useAskAiRateLimitHandler(error: ApiError | Error | null) {
useRateLimitHandler('askAi', error)
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useCooldownActions } from './cooldown.store'
import { ModalMode } from './modalmodes'
import { useCooldownActions, CooldownDomain } from '../shared/cooldown.store'
import { useEffect, useRef } from 'react'

interface UseCooldownParams {
domain: ModalMode
domain: CooldownDomain
cooldown: number | null
onCooldownFinished: () => void
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { logWarn } from '../../../telemetry/logging'
import { traceSpan } from '../../../telemetry/tracing'
import { logWarn } from '../../telemetry/logging'
import { traceSpan } from '../../telemetry/tracing'
import { Reaction, useChatActions, useMessageReaction } from './chat.store'
import { useMutation } from '@tanstack/react-query'
import { useCallback, useRef } from 'react'
Expand Down
Loading
Loading