Skip to content

Commit

Permalink
[8.9] [Security solution] Create AI assistant availability model (#16…
Browse files Browse the repository at this point in the history
…1027) (#161692)

# Backport

This will backport the following commits from `main` to `8.9`:
- [[Security solution] Create AI assistant availability model
(#161027)](#161027)

<!--- Backport version: 8.9.7 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Steph
Milovic","email":"[email protected]"},"sourceCommit":{"committedDate":"2023-07-10T23:37:29Z","message":"[Security
solution] Create AI assistant availability model
(#161027)","sha":"ac0630015f8fc0936b99d372924ea2e54e742c74","branchLabelMapping":{"^v8.10.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:Threat
Hunting","Team:
SecuritySolution","v8.9.0","v8.10.0"],"number":161027,"url":"https://github.com/elastic/kibana/pull/161027","mergeCommit":{"message":"[Security
solution] Create AI assistant availability model
(#161027)","sha":"ac0630015f8fc0936b99d372924ea2e54e742c74"}},"sourceBranch":"main","suggestedTargetBranches":["8.9"],"targetPullRequestStates":[{"branch":"8.9","label":"v8.9.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.10.0","labelRegex":"^v8.10.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/161027","number":161027,"mergeCommit":{"message":"[Security
solution] Create AI assistant availability model
(#161027)","sha":"ac0630015f8fc0936b99d372924ea2e54e742c74"}}]}]
BACKPORT-->
  • Loading branch information
stephmilovic authored Jul 12, 2023
1 parent c0dda41 commit 5a5d179
Show file tree
Hide file tree
Showing 28 changed files with 457 additions and 323 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ const StyledEuiModal = styled(EuiModal)`
min-width: 95vw;
min-height: 25vh;
`;
interface Props {
isAssistantEnabled: boolean;
}

/**
* Modal container for Elastic AI Assistant conversations, receiving the page contents as context, plus whatever
* component currently has focus and any specific context it may provide through the SAssInterface.
*/
export const AssistantOverlay: React.FC = React.memo(() => {
export const AssistantOverlay = React.memo<Props>(({ isAssistantEnabled }) => {
const [isModalVisible, setIsModalVisible] = useState(false);
const [conversationId, setConversationId] = useState<string | undefined>(
WELCOME_CONVERSATION_TITLE
Expand Down Expand Up @@ -79,7 +83,11 @@ export const AssistantOverlay: React.FC = React.memo(() => {
<>
{isModalVisible && (
<StyledEuiModal onClose={handleCloseModal} data-test-subj="ai-assistant-modal">
<Assistant conversationId={conversationId} promptContextId={promptContextId} />
<Assistant
isAssistantEnabled={isAssistantEnabled}
conversationId={conversationId}
promptContextId={promptContextId}
/>
</StyledEuiModal>
)}
</>
Expand Down
209 changes: 139 additions & 70 deletions x-pack/packages/kbn-elastic-assistant/impl/assistant/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { css } from '@emotion/react';

import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/gen_ai/constants';
import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types';
import { UpgradeButtons } from '../upgrade/upgrade_buttons';
import { getMessageFromRawResponse } from './helpers';

import { ConversationSettingsPopover } from './conversation_settings_popover/conversation_settings_popover';
Expand All @@ -50,24 +51,26 @@ import { QuickPrompts } from './quick_prompts/quick_prompts';
import { useLoadConnectors } from '../connectorland/use_load_connectors';
import { useConnectorSetup } from '../connectorland/connector_setup';
import { WELCOME_CONVERSATION_TITLE } from './use_conversation/translations';
import { BASE_CONVERSATIONS } from './use_conversation/sample_conversations';
import { BASE_CONVERSATIONS, enterpriseMessaging } from './use_conversation/sample_conversations';

export interface Props {
promptContextId?: string;
conversationId?: string;
showTitle?: boolean;
isAssistantEnabled: boolean;
promptContextId?: string;
shouldRefocusPrompt?: boolean;
showTitle?: boolean;
}

/**
* Renders a chat window with a prompt input and a chat history, along with
* quick prompts for common actions, settings, and prompt context providers.
*/
const AssistantComponent: React.FC<Props> = ({
promptContextId = '',
showTitle = true,
conversationId = WELCOME_CONVERSATION_TITLE,
isAssistantEnabled,
promptContextId = '',
shouldRefocusPrompt = false,
showTitle = true,
}) => {
const {
actionTypeRegistry,
Expand Down Expand Up @@ -101,10 +104,37 @@ const AssistantComponent: React.FC<Props> = ({

// Welcome conversation is a special 'setup' case when no connector exists, mostly extracted to `ConnectorSetup` component,
// but currently a bit of state is littered throughout the assistant component. TODO: clean up/isolate this state
const welcomeConversation = useMemo(
() => conversations[selectedConversationId] ?? BASE_CONVERSATIONS[WELCOME_CONVERSATION_TITLE],
[conversations, selectedConversationId]
);
const welcomeConversation = useMemo(() => {
const conversation =
conversations[selectedConversationId] ?? BASE_CONVERSATIONS[WELCOME_CONVERSATION_TITLE];
const doesConversationHaveMessages = conversation.messages.length > 0;
if (!isAssistantEnabled) {
if (
!doesConversationHaveMessages ||
conversation.messages[conversation.messages.length - 1].content !==
enterpriseMessaging[0].content
) {
return {
...conversation,
messages: [...conversation.messages, ...enterpriseMessaging],
};
}
return conversation;
}

return doesConversationHaveMessages
? {
...conversation,
messages: [
...conversation.messages,
...BASE_CONVERSATIONS[WELCOME_CONVERSATION_TITLE].messages,
],
}
: {
...conversation,
messages: BASE_CONVERSATIONS[WELCOME_CONVERSATION_TITLE].messages,
};
}, [conversations, isAssistantEnabled, selectedConversationId]);

const { data: connectors, refetch: refetchConnectors } = useLoadConnectors({ http });
const defaultConnectorId = useMemo(() => connectors?.[0]?.id, [connectors]);
Expand All @@ -116,13 +146,19 @@ const AssistantComponent: React.FC<Props> = ({
);

const isWelcomeSetup = (connectors?.length ?? 0) === 0;
const isDisabled = isWelcomeSetup || !isAssistantEnabled;

const { connectorDialog, connectorPrompt } = useConnectorSetup({
const { comments: connectorComments, prompt: connectorPrompt } = useConnectorSetup({
actionTypeRegistry,
http,
refetchConnectors,
onSetupComplete: () => {
bottomRef.current?.scrollIntoView({ behavior: 'auto' });
},
conversation: welcomeConversation,
isConnectorConfigured: !!connectors?.length,
});

const currentTitle: { title: string | JSX.Element; titleIcon: string } =
isWelcomeSetup && welcomeConversation.theme?.title && welcomeConversation.theme?.titleIcon
? { title: welcomeConversation.theme?.title, titleIcon: welcomeConversation.theme?.titleIcon }
Expand All @@ -146,8 +182,8 @@ const AssistantComponent: React.FC<Props> = ({
}, [augmentMessageCodeBlocks, currentConversation]);

const isSendingDisabled = useMemo(() => {
return isWelcomeSetup || showMissingConnectorCallout;
}, [showMissingConnectorCallout, isWelcomeSetup]);
return isDisabled || showMissingConnectorCallout;
}, [showMissingConnectorCallout, isDisabled]);

// Fixes initial render not showing buttons as code block controls are added to the DOM really late
useEffect(() => {
Expand Down Expand Up @@ -270,16 +306,6 @@ const AssistantComponent: React.FC<Props> = ({
[setShowAnonymizedValues]
);

const comments = useMemo(
() =>
getComments({
currentConversation,
lastCommentRef,
showAnonymizedValues,
}),
[currentConversation, getComments, showAnonymizedValues]
);

useEffect(() => {
// Adding `conversationId !== selectedConversationId` to prevent auto-run still executing after changing selected conversation
if (currentConversation.messages.length || conversationId !== selectedConversationId) {
Expand Down Expand Up @@ -348,6 +374,66 @@ const AssistantComponent: React.FC<Props> = ({
}),
[messageCodeBlocks]
);

const chatbotComments = useMemo(
() => (
<>
<EuiCommentList
comments={getComments({
currentConversation,
lastCommentRef,
showAnonymizedValues,
})}
css={css`
margin-right: 20px;
`}
/>

<EuiSpacer size={'m'} />

{(currentConversation.messages.length === 0 ||
Object.keys(selectedPromptContexts).length > 0) && (
<PromptEditor
conversation={currentConversation}
isNewConversation={currentConversation.messages.length === 0}
promptContexts={promptContexts}
promptTextPreview={promptTextPreview}
selectedPromptContexts={selectedPromptContexts}
setSelectedPromptContexts={setSelectedPromptContexts}
/>
)}

<div ref={bottomRef} />
</>
),
[
currentConversation,
getComments,
promptContexts,
promptTextPreview,
selectedPromptContexts,
showAnonymizedValues,
]
);

const comments = useMemo(() => {
if (isDisabled) {
return (
<>
<EuiCommentList
comments={connectorComments}
css={css`
margin-right: 20px;
`}
/>
<span ref={bottomRef} />
</>
);
}

return chatbotComments;
}, [connectorComments, isDisabled, chatbotComments]);

return (
<>
<EuiModalHeader
Expand Down Expand Up @@ -388,7 +474,7 @@ const AssistantComponent: React.FC<Props> = ({
defaultProvider={defaultProvider}
onSelectionChange={(id) => setSelectedConversationId(id)}
shouldDisableKeyboardShortcut={shouldDisableConversationSelectorHotkeys}
isDisabled={isWelcomeSetup}
isDisabled={isDisabled}
/>

<>
Expand All @@ -415,7 +501,7 @@ const AssistantComponent: React.FC<Props> = ({
</EuiFlexItem>

<EuiFlexItem grow={false}>
<SettingsPopover />
<SettingsPopover isDisabled={currentConversation.replacements == null} />
</EuiFlexItem>
</EuiFlexGroup>
</>
Expand All @@ -441,7 +527,7 @@ const AssistantComponent: React.FC<Props> = ({
{/* Create portals for each EuiCodeBlock to add the `Investigate in Timeline` action */}
{createCodeBlockPortals()}

{!isWelcomeSetup && (
{!isDisabled && (
<>
<ContextPills
defaultAllow={defaultAllow}
Expand All @@ -454,52 +540,35 @@ const AssistantComponent: React.FC<Props> = ({
</>
)}
</EuiModalHeader>
<EuiModalBody>
{isWelcomeSetup ? (
connectorDialog
) : (
<>
<EuiCommentList
comments={comments}
css={css`
margin-right: 20px;
`}
/>

<EuiSpacer size={'m'} />

{(currentConversation.messages.length === 0 ||
Object.keys(selectedPromptContexts).length > 0) && (
<PromptEditor
conversation={currentConversation}
isNewConversation={currentConversation.messages.length === 0}
promptContexts={promptContexts}
promptTextPreview={promptTextPreview}
selectedPromptContexts={selectedPromptContexts}
setSelectedPromptContexts={setSelectedPromptContexts}
/>
)}

<div ref={bottomRef} />
</>
)}

<EuiSpacer />
</EuiModalBody>
<EuiModalBody>{comments}</EuiModalBody>
<EuiModalFooter
css={css`
align-items: flex-start;
flex-direction: column;
`}
>
<EuiFlexGroup
gutterSize="none"
css={css`
width: 100%;
`}
>
{isWelcomeSetup && <EuiFlexItem>{connectorPrompt}</EuiFlexItem>}
</EuiFlexGroup>
{!isAssistantEnabled ? (
<EuiFlexGroup
justifyContent="spaceAround"
css={css`
width: 100%;
`}
>
<EuiFlexItem grow={false}>
{<UpgradeButtons basePath={http.basePath.get()} />}
</EuiFlexItem>
</EuiFlexGroup>
) : (
isWelcomeSetup && (
<EuiFlexGroup
css={css`
width: 100%;
`}
>
<EuiFlexItem>{connectorPrompt}</EuiFlexItem>
</EuiFlexGroup>
)
)}
<EuiFlexGroup
gutterSize="none"
css={css`
Expand All @@ -511,8 +580,8 @@ const AssistantComponent: React.FC<Props> = ({
onPromptSubmit={handleSendMessage}
ref={promptTextAreaRef}
handlePromptChange={setPromptTextPreview}
value={isWelcomeSetup ? '' : suggestedUserPrompt ?? ''}
isDisabled={isWelcomeSetup}
value={isDisabled ? '' : suggestedUserPrompt ?? ''}
isDisabled={isDisabled}
/>
</EuiFlexItem>

Expand All @@ -536,7 +605,7 @@ const AssistantComponent: React.FC<Props> = ({
<EuiButtonIcon
display="base"
iconType="cross"
isDisabled={isWelcomeSetup}
isDisabled={isDisabled}
aria-label={i18n.CLEAR_CHAT}
color="danger"
onClick={() => {
Expand Down Expand Up @@ -565,15 +634,15 @@ const AssistantComponent: React.FC<Props> = ({
<ConversationSettingsPopover
actionTypeRegistry={actionTypeRegistry}
conversation={currentConversation}
isDisabled={isWelcomeSetup}
isDisabled={isDisabled}
http={http}
allSystemPrompts={allSystemPrompts}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
{!isWelcomeSetup && <QuickPrompts setInput={setSuggestedUserPrompt} />}
{!isDisabled && <QuickPrompts setInput={setSuggestedUserPrompt} />}
</EuiModalFooter>
</>
);
Expand Down
Loading

0 comments on commit 5a5d179

Please sign in to comment.