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
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ public override void Write<TState>(
: now.ToString("[yyyy-MM-ddTHH:mm:ss.fffZ] ", System.Globalization.CultureInfo.InvariantCulture);

textWriter.WriteLine($"{nowString}{logLevel}::{ShortCategoryName(categoryName)}:: {message}");

if (logEntry.Exception is { } exception)
textWriter.WriteLine(exception.ToString());
}

private static string GetLogLevel(LogLevel logLevel) => logLevel switch
Expand Down
166 changes: 34 additions & 132 deletions src/Elastic.Documentation.Site/Assets/web-components/AskAi/AskAi.tsx
Original file line number Diff line number Diff line change
@@ -1,57 +1,22 @@
import '../../eui-icons-cache'
import { sharedQueryClient } from '../shared/queryClient'
import { ElasticAiAssistantButton } from './ElasticAiAssistantButton'
import {
useAskAiModalActions,
useAskAiModalIsOpen,
useFlyoutWidth,
} from './askAi.modal.store'
import { AskAiFlyoutBodyContent, useAskAiFlyout } from './useAskAiFlyout'
import {
EuiFlyout,
EuiFlyoutBody,
EuiLoadingSpinner,
EuiProvider,
euiShadow,
euiShadowHover,
useEuiTheme,
} from '@elastic/eui'
import { css } from '@emotion/react'
import r2wc from '@r2wc/react-to-web-component'
import { QueryClientProvider, useQuery } from '@tanstack/react-query'
import { useEffect, Suspense, lazy, StrictMode } from 'react'

// Lazy load the modal component
const LazyAskAiModal = lazy(() =>
import('./AskAiModal').then((module) => ({
default: module.AskAiModal,
}))
)
import { QueryClientProvider } from '@tanstack/react-query'
import { StrictMode } from 'react'

const AskAiButton = () => {
const isModalOpen = useAskAiModalIsOpen()
const { openModal, closeModal, setFlyoutWidth } = useAskAiModalActions()
const flyoutWidth = useFlyoutWidth()
const euiThemeContext = useEuiTheme()
const fabContainerCss = (euiThemeContext: ReturnType<typeof useEuiTheme>) => {
const { euiTheme } = euiThemeContext

const { data: isApiAvailable } = useQuery({
queryKey: ['api-health'],
queryFn: async () => {
const response = await fetch('/docs/_api/v1/', { method: 'POST' })
return response.ok
},
staleTime: 60 * 60 * 1000, // 60 minutes
retry: false,
})

const loadingCss = css`
display: flex;
justify-content: center;
align-items: center;
padding: 2rem;
`

const fabContainerCss = css`
return css`
position: fixed;
bottom: ${euiTheme.size.xxxxl};
right: ${euiTheme.size.xxxxl};
Expand All @@ -69,67 +34,45 @@ const AskAiButton = () => {
transform: translateY(0);
}
`
}

useEffect(() => {
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
event.preventDefault()
closeModal()
}
// Cmd+; to open Ask AI flyout
if (
(event.metaKey || event.ctrlKey) &&
event.code === 'Semicolon'
) {
event.preventDefault()
openModal()
}
}
window.addEventListener('keydown', handleKeydown)
return () => window.removeEventListener('keydown', handleKeydown)
}, [openModal, closeModal])
const AskAiButton = () => {
const euiThemeContext = useEuiTheme()
const {
isApiAvailable,
isModalOpen,
openModal,
closeModal,
setFlyoutWidth,
flyoutWidth,
} = useAskAiFlyout()

if (!isApiAvailable) {
return null
}

let flyout
if (isModalOpen) {
flyout = (
<EuiFlyout
ownFocus={false}
onClose={closeModal}
aria-label="Ask AI"
resizable={true}
minWidth={376}
maxWidth={700}
paddingSize="none"
hideCloseButton={true}
size={flyoutWidth}
onResize={setFlyoutWidth}
outsideClickCloses={false}
>
<EuiFlyoutBody>
<div css={backgroundWrapperCss}>
<Suspense
fallback={
<div css={loadingCss}>
<EuiLoadingSpinner size="xl" />
</div>
}
>
<LazyAskAiModal />
</Suspense>
</div>
</EuiFlyoutBody>
</EuiFlyout>
)
}
const flyout = isModalOpen ? (
<EuiFlyout
ownFocus={false}
onClose={closeModal}
aria-label="Ask AI"
resizable={true}
minWidth={376}
maxWidth={700}
paddingSize="none"
hideCloseButton={true}
size={flyoutWidth}
onResize={setFlyoutWidth}
outsideClickCloses={false}
>
<AskAiFlyoutBodyContent />
</EuiFlyout>
) : null

return (
<>
{!isModalOpen && (
<div css={fabContainerCss}>
<div css={fabContainerCss(euiThemeContext)}>
<ElasticAiAssistantButton
onClick={openModal}
aria-label="Open Ask AI"
Expand All @@ -144,47 +87,6 @@ const AskAiButton = () => {
)
}

const backgroundWrapperCss = css`
position: relative;
min-height: 100%;

&::before {
content: '';
position: absolute;
top: 38px;
left: 0;
right: 0;
bottom: 0;
background-color: #e5e5f7;
pointer-events: none;
z-index: 0;

opacity: 0.4;
background-image: radial-gradient(#444cf7 0.5px, #ffffff 0.5px);
background-size: 10px 10px;

mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 1) 10%,
rgba(0, 0, 0, 0.5) 20%,
rgba(0, 0, 0, 0) 30%
);
-webkit-mask-image: linear-gradient(
to bottom,
rgba(0, 0, 0, 1) 0%,
rgba(0, 0, 0, 1) 10%,
rgba(0, 0, 0, 0.5) 20%,
rgba(0, 0, 0, 0) 30%
);
}

& > * {
position: relative;
z-index: 1;
}
`

const AskAi = () => {
return (
<StrictMode>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import '../../eui-icons-cache'
import { ElasticAiAssistantButton } from './ElasticAiAssistantButton'
import { AskAiFlyoutBodyContent, useAskAiFlyout } from './useAskAiFlyout'
import { EuiFlyout, EuiPortal, EuiThemeProvider } from '@elastic/eui'
import { css } from '@emotion/react'

const headerButtonWrapperCss = css`
display: flex;
align-items: center;
margin-left: 8px;
`

export const AskAiHeaderButton = () => {
const {
isApiAvailable,
isModalOpen,
openModal,
closeModal,
setFlyoutWidth,
flyoutWidth,
} = useAskAiFlyout()

if (!isApiAvailable) {
return null
}

return (
<>
<span css={headerButtonWrapperCss}>
<ElasticAiAssistantButton
aria-label="Ask AI"
onClick={isModalOpen ? closeModal : openModal}
fill={true}
size="s"
>
Ask AI
</ElasticAiAssistantButton>
</span>

{isModalOpen && (
<EuiPortal>
<EuiThemeProvider colorMode="light">
<EuiFlyout
ownFocus={false}
onClose={closeModal}
aria-label="Ask AI"
resizable={true}
minWidth={376}
maxWidth={700}
paddingSize="none"
hideCloseButton={true}
size={flyoutWidth}
onResize={setFlyoutWidth}
outsideClickCloses={false}
>
<AskAiFlyoutBodyContent />
</EuiFlyout>
</EuiThemeProvider>
</EuiPortal>
)}
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,50 +1,19 @@
import { askAiConfig } from './askAi.config'
import { useChatActions } from './chat.store'
import { useIsAskAiCooldownActive } from './useAskAiCooldown'
import { EuiButton, EuiText, useEuiTheme, EuiSpacer } from '@elastic/eui'
import { css } from '@emotion/react'
import { useMemo } from 'react'

export interface AskAiSuggestion {
question: string
}

// Comprehensive list of AI suggestion questions
const ALL_SUGGESTIONS: AskAiSuggestion[] = [
{ question: 'How do I set up a data stream in Elasticsearch?' },
{ question: 'What are the best practices for indexing performance?' },
{ question: 'How can I create a dashboard in Kibana?' },
{ question: 'What is the difference between a keyword and text field?' },
{ question: 'How do I configure machine learning jobs?' },
{ question: 'What are aggregations and how do I use them?' },
{ question: 'How do I set up Elasticsearch security and authentication?' },
{ question: 'What are the different types of Elasticsearch queries?' },
{ question: 'How do I monitor cluster health and performance?' },
{
question:
'What is the Elastic Stack and how do the components work together?',
},
{ question: 'How do I create and manage Elasticsearch indices?' },
{ question: 'What are the best practices for Elasticsearch mapping?' },
{ question: 'How do I set up log shipping with Beats?' },
{ question: 'What is APM and how do I use it for application monitoring?' },
{ question: 'How do I create custom visualizations in Kibana?' },
{ question: 'What are Elasticsearch snapshots and how do I use them?' },
{ question: 'How do I configure cross-cluster search?' },
{
question:
'What are the different Elasticsearch node types and their roles?',
},
]

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

// Randomly select 3 questions from the comprehensive list
const selectedSuggestions = useMemo(() => {
// Shuffle the array and take first 3
const shuffled = [...ALL_SUGGESTIONS].sort(() => Math.random() - 0.5)
const shuffled = [...askAiConfig.suggestions].sort(
() => Math.random() - 0.5
)
return shuffled.slice(0, 3)
}, [])

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ChatMessageList } from './ChatMessageList'
import { InfoBanner } from './InfoBanner'
import { LegalDisclaimer } from './LegalDisclaimer'
import AiIcon from './ai-icon.svg'
import { askAiConfig } from './askAi.config'
import { useAskAiModalActions } from './askAi.modal.store'
import {
ChatMessage,
Expand Down Expand Up @@ -74,14 +75,8 @@ export const Chat = () => {
<EuiFlexItem grow={true} css={emptyStateContainerStyles}>
<EuiEmptyPrompt
icon={<EuiIcon type={AiIcon} size="xxl" />}
title={<h2>Hi! I'm the Elastic Docs AI Assistant</h2>}
body={
<p>
I'm here to help you find answers about Elastic,
powered entirely by our technical documentation.
How can I help?
</p>
}
title={<h2>Hi! I'm the {askAiConfig.assistantName}</h2>}
body={<p>{askAiConfig.assistantDescription}</p>}
/>
</EuiFlexItem>
) : !hasHydrated ? (
Expand Down Expand Up @@ -178,7 +173,7 @@ const ChatHeader = () => {
font-weight: ${euiTheme.font.weight.medium};
`}
>
Elastic Docs AI Assistant
{askAiConfig.assistantName}
</span>
</div>
<div
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ElasticAiAssistantButtonIcon } from './ElasticAiAssistantButton'
import { askAiConfig } from './askAi.config'
import { euiShadow, useEuiScrollBar, useEuiTheme } from '@elastic/eui'
import { css } from '@emotion/react'
import { useCallback, useEffect, useRef } from 'react'
Expand Down Expand Up @@ -28,7 +29,7 @@ export const ChatInput = ({
onSubmit,
onAbort,
disabled = false,
placeholder = 'Ask the Elastic Docs AI Assistant',
placeholder = askAiConfig.inputPlaceholder,
inputRef,
isStreaming = false,
onMetaSemicolon,
Expand Down
Loading
Loading