mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Security Solution] Fixes Preconfigured Connectors not working with Assistant (#164900)
## Summary Fixes Preconfigured Connectors not working with the Assistant, and also ensures default `model` from connector will be used first if available (instead of defaulting to `gpt-3.5-turbo`). <p align="center"> <img width="500" src="637f5919
-7560-40b0-a8db-681096e77ac0" /> </p> Note how `Model` is not displayed even though this is an OpenAI connector: <p align="center"> <img width="500" src="2c4bbe91
-2851-48d7-8bfe-20e07db52155" /> </p> Additionally, resolves issue with Detection Rule Assistant CTA not displaying correctly on some platforms/browsers. It now shows as a `Chat` button to the right of the table tabs, matching the other assistant CTA's throughout the application. <p align="center"> <img width="500" src="9fcecd54
-8e1a-423a-be05-7137632acbc4" /> </p> And lastly removes `Beta` title from callout since we're going GA in `8.10` 🎉 <p align="center"> <img width="500" src="5beb379a
-1bc7-4afc-b4bc-09f1d6085211" /> </p> Resolves: https://github.com/elastic/kibana/issues/163394#issuecomment-1693431066 Resolves: https://github.com/elastic/kibana/issues/164819 ### Checklist Delete any items that are not applicable to this PR. - [X] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials
This commit is contained in:
parent
3835392e32
commit
5f9651e0ef
12 changed files with 157 additions and 176 deletions
|
@ -56,7 +56,7 @@ export const AssistantTitle: React.FC<{
|
|||
const content = useMemo(
|
||||
() => (
|
||||
<FormattedMessage
|
||||
defaultMessage="The Elastic AI Assistant is currently in beta. For more information on the assistant feature and its usage, please reference the {documentationLink}."
|
||||
defaultMessage="Responses from AI systems may not always be entirely accurate. For more information on the assistant feature and its usage, please reference the {documentationLink}."
|
||||
id="xpack.elasticAssistant.assistant.technicalPreview.tooltipContent"
|
||||
values={{
|
||||
documentationLink,
|
||||
|
@ -99,7 +99,6 @@ export const AssistantTitle: React.FC<{
|
|||
anchorPosition="rightUp"
|
||||
>
|
||||
<EuiText data-test-subj="tooltipContent" grow={false} css={{ maxWidth: '400px' }}>
|
||||
<h4>{i18n.TOOLTIP_TITLE}</h4>
|
||||
<EuiText size={'s'}>
|
||||
<p>{content}</p>
|
||||
</EuiText>
|
||||
|
@ -112,7 +111,6 @@ export const AssistantTitle: React.FC<{
|
|||
<ConnectorSelectorInline
|
||||
isDisabled={isDisabled || selectedConversation === undefined}
|
||||
onConnectorModalVisibilityChange={() => {}}
|
||||
onConnectorSelectionChange={() => {}}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
selectedConversation={selectedConversation}
|
||||
/>
|
||||
|
|
|
@ -23,6 +23,8 @@ import { ModelSelector } from '../../../connectorland/models/model_selector/mode
|
|||
import { UseAssistantContext } from '../../../assistant_context';
|
||||
import { ConversationSelectorSettings } from '../conversation_selector_settings';
|
||||
import { getDefaultSystemPrompt } from '../../use_conversation/helpers';
|
||||
import { useLoadConnectors } from '../../../connectorland/use_load_connectors';
|
||||
import { getGenAiConfig } from '../../../connectorland/helpers';
|
||||
|
||||
export interface ConversationSettingsProps {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
|
@ -63,6 +65,8 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
return getDefaultSystemPrompt({ allSystemPrompts, conversation: selectedConversation });
|
||||
}, [allSystemPrompts, selectedConversation]);
|
||||
|
||||
const { data: connectors, isSuccess: areConnectorsFetched } = useLoadConnectors({ http });
|
||||
|
||||
// Conversation callbacks
|
||||
// When top level conversation selection changes
|
||||
const onConversationSelectionChange = useCallback(
|
||||
|
@ -131,10 +135,13 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
[selectedConversation, setUpdatedConversationSettings]
|
||||
);
|
||||
|
||||
const selectedConnectorId = useMemo(
|
||||
() => selectedConversation?.apiConfig.connectorId,
|
||||
[selectedConversation?.apiConfig.connectorId]
|
||||
);
|
||||
const selectedConnector = useMemo(() => {
|
||||
const selectedConnectorId = selectedConversation?.apiConfig.connectorId;
|
||||
if (areConnectorsFetched) {
|
||||
return connectors?.find((c) => c.id === selectedConnectorId);
|
||||
}
|
||||
return undefined;
|
||||
}, [areConnectorsFetched, connectors, selectedConversation?.apiConfig.connectorId]);
|
||||
|
||||
const selectedProvider = useMemo(
|
||||
() => selectedConversation?.apiConfig.provider,
|
||||
|
@ -142,16 +149,19 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
);
|
||||
|
||||
const handleOnConnectorSelectionChange = useCallback(
|
||||
(connectorId: string, provider: OpenAiProviderType) => {
|
||||
(connector) => {
|
||||
if (selectedConversation != null) {
|
||||
const config = getGenAiConfig(connector);
|
||||
|
||||
setUpdatedConversationSettings((prev) => ({
|
||||
...prev,
|
||||
[selectedConversation.id]: {
|
||||
...selectedConversation,
|
||||
apiConfig: {
|
||||
...selectedConversation.apiConfig,
|
||||
connectorId,
|
||||
provider,
|
||||
connectorId: connector?.id,
|
||||
provider: config?.apiProvider,
|
||||
model: config?.defaultModel,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
@ -160,10 +170,11 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
[selectedConversation, setUpdatedConversationSettings]
|
||||
);
|
||||
|
||||
const selectedModel = useMemo(
|
||||
() => selectedConversation?.apiConfig.model,
|
||||
[selectedConversation?.apiConfig.model]
|
||||
);
|
||||
const selectedModel = useMemo(() => {
|
||||
const connectorModel = getGenAiConfig(selectedConnector)?.defaultModel;
|
||||
// Prefer conversation configuration over connector default
|
||||
return selectedConversation?.apiConfig.model ?? connectorModel;
|
||||
}, [selectedConnector, selectedConversation?.apiConfig.model]);
|
||||
|
||||
const handleOnModelSelectionChange = useCallback(
|
||||
(model?: string) => {
|
||||
|
@ -244,23 +255,24 @@ export const ConversationSettings: React.FC<ConversationSettingsProps> = React.m
|
|||
isDisabled={selectedConversation == null}
|
||||
onConnectorModalVisibilityChange={() => {}}
|
||||
onConnectorSelectionChange={handleOnConnectorSelectionChange}
|
||||
selectedConnectorId={selectedConnectorId}
|
||||
selectedConnectorId={selectedConnector?.id}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
|
||||
{selectedProvider === OpenAiProviderType.OpenAi && (
|
||||
<EuiFormRow
|
||||
data-test-subj="model-field"
|
||||
display="rowCompressed"
|
||||
label={i18nModel.MODEL_TITLE}
|
||||
helpText={i18nModel.HELP_LABEL}
|
||||
>
|
||||
<ModelSelector
|
||||
onModelSelectionChange={handleOnModelSelectionChange}
|
||||
selectedModel={selectedModel}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
{selectedConnector?.isPreconfigured === false &&
|
||||
selectedProvider === OpenAiProviderType.OpenAi && (
|
||||
<EuiFormRow
|
||||
data-test-subj="model-field"
|
||||
display="rowCompressed"
|
||||
label={i18nModel.MODEL_TITLE}
|
||||
helpText={i18nModel.HELP_LABEL}
|
||||
>
|
||||
<ModelSelector
|
||||
onModelSelectionChange={handleOnModelSelectionChange}
|
||||
selectedModel={selectedModel}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,13 +41,6 @@ export const API_ERROR = i18n.translate('xpack.elasticAssistant.assistant.apiErr
|
|||
'An error occurred sending your message. If the problem persists, please test the connector configuration.',
|
||||
});
|
||||
|
||||
export const TOOLTIP_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.technicalPreview.tooltipTitle',
|
||||
{
|
||||
defaultMessage: 'Beta',
|
||||
}
|
||||
);
|
||||
|
||||
export const TOOLTIP_ARIA_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.documentationLinks.ariaLabel',
|
||||
{
|
||||
|
|
|
@ -14,31 +14,24 @@ import {
|
|||
} from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
import { HttpSetup } from '@kbn/core-http-browser';
|
||||
import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { ConnectorAddModal } from '@kbn/triggers-actions-ui-plugin/public/common/constants';
|
||||
import {
|
||||
GEN_AI_CONNECTOR_ID,
|
||||
OpenAiProviderType,
|
||||
} from '@kbn/stack-connectors-plugin/public/common';
|
||||
import { GEN_AI_CONNECTOR_ID } from '@kbn/stack-connectors-plugin/public/common';
|
||||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
import * as i18n from '../translations';
|
||||
import { useLoadActionTypes } from '../use_load_action_types';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { getGenAiConfig } from '../helpers';
|
||||
|
||||
export const ADD_NEW_CONNECTOR = 'ADD_NEW_CONNECTOR';
|
||||
interface Props {
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
http: HttpSetup;
|
||||
isDisabled?: boolean;
|
||||
onConnectorSelectionChange: (connectorId: string, provider: OpenAiProviderType) => void;
|
||||
onConnectorSelectionChange: (connector: ActionConnector | undefined) => void;
|
||||
selectedConnectorId?: string;
|
||||
onConnectorModalVisibilityChange?: (isVisible: boolean) => void;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
apiProvider: string;
|
||||
}
|
||||
|
||||
export const ConnectorSelector: React.FC<Props> = React.memo(
|
||||
({
|
||||
actionTypeRegistry,
|
||||
|
@ -95,18 +88,19 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
const connectorOptions = useMemo(() => {
|
||||
return (
|
||||
connectors?.map((connector) => {
|
||||
const apiProvider: string | undefined = (
|
||||
connector as ActionConnectorProps<Config, unknown>
|
||||
)?.config?.apiProvider;
|
||||
const apiProvider = getGenAiConfig(connector)?.apiProvider;
|
||||
const connectorDetails = connector.isPreconfigured
|
||||
? i18n.PRECONFIGURED_CONNECTOR
|
||||
: apiProvider;
|
||||
return {
|
||||
value: connector.id,
|
||||
inputDisplay: connector.name,
|
||||
dropdownDisplay: (
|
||||
<React.Fragment key={connector.id}>
|
||||
<strong>{connector.name}</strong>
|
||||
{apiProvider && (
|
||||
<EuiText size="s" color="subdued">
|
||||
<p>{apiProvider}</p>
|
||||
{connectorDetails && (
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p>{connectorDetails}</p>
|
||||
</EuiText>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@ -138,10 +132,8 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
return;
|
||||
}
|
||||
|
||||
const apiProvider = (
|
||||
connectors?.find((c) => c.id === connectorId) as ActionConnectorProps<Config, unknown>
|
||||
)?.config.apiProvider as OpenAiProviderType;
|
||||
onConnectorSelectionChange(connectorId, apiProvider);
|
||||
const connector = connectors?.find((c) => c.id === connectorId);
|
||||
onConnectorSelectionChange(connector);
|
||||
},
|
||||
[connectors, onConnectorSelectionChange, onConnectorModalVisibilityChange]
|
||||
);
|
||||
|
@ -162,12 +154,8 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
<ConnectorAddModal
|
||||
actionType={actionType}
|
||||
onClose={cleanupAndCloseModal}
|
||||
postSaveEventHandler={(savedAction: ActionConnector) => {
|
||||
onConnectorSelectionChange(
|
||||
savedAction.id,
|
||||
(savedAction as ActionConnectorProps<Config, unknown>)?.config
|
||||
.apiProvider as OpenAiProviderType
|
||||
);
|
||||
postSaveEventHandler={(connector: ActionConnector) => {
|
||||
onConnectorSelectionChange(connector);
|
||||
refetchConnectors?.();
|
||||
cleanupAndCloseModal();
|
||||
}}
|
||||
|
|
|
@ -65,7 +65,6 @@ describe('ConnectorSelectorInline', () => {
|
|||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
onConnectorModalVisibilityChange={noop}
|
||||
onConnectorSelectionChange={noop}
|
||||
selectedConnectorId={undefined}
|
||||
selectedConversation={undefined}
|
||||
/>
|
||||
|
@ -85,7 +84,6 @@ describe('ConnectorSelectorInline', () => {
|
|||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
onConnectorModalVisibilityChange={noop}
|
||||
onConnectorSelectionChange={noop}
|
||||
selectedConnectorId={'missing-connector-id'}
|
||||
selectedConversation={conversation}
|
||||
/>
|
||||
|
@ -105,7 +103,6 @@ describe('ConnectorSelectorInline', () => {
|
|||
<ConnectorSelectorInline
|
||||
isDisabled={false}
|
||||
onConnectorModalVisibilityChange={noop}
|
||||
onConnectorSelectionChange={noop}
|
||||
selectedConnectorId={mockConnectors[0].id}
|
||||
selectedConversation={conversation}
|
||||
/>
|
||||
|
|
|
@ -10,7 +10,6 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|||
|
||||
import { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { ConnectorAddModal } from '@kbn/triggers-actions-ui-plugin/public/common/constants';
|
||||
import {
|
||||
GEN_AI_CONNECTOR_ID,
|
||||
|
@ -23,20 +22,16 @@ import * as i18n from '../translations';
|
|||
import { useLoadActionTypes } from '../use_load_action_types';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useConversation } from '../../assistant/use_conversation';
|
||||
import { getGenAiConfig } from '../helpers';
|
||||
|
||||
export const ADD_NEW_CONNECTOR = 'ADD_NEW_CONNECTOR';
|
||||
interface Props {
|
||||
isDisabled?: boolean;
|
||||
onConnectorSelectionChange: (connectorId: string, provider: OpenAiProviderType) => void;
|
||||
selectedConnectorId?: string;
|
||||
selectedConversation?: Conversation;
|
||||
onConnectorModalVisibilityChange?: (isVisible: boolean) => void;
|
||||
}
|
||||
|
||||
interface Config {
|
||||
apiProvider: string;
|
||||
}
|
||||
|
||||
const inputContainerClassName = css`
|
||||
height: 32px;
|
||||
|
||||
|
@ -82,7 +77,6 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
onConnectorModalVisibilityChange,
|
||||
selectedConnectorId,
|
||||
selectedConversation,
|
||||
onConnectorSelectionChange,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const { actionTypeRegistry, assistantAvailability, http } = useAssistantContext();
|
||||
|
@ -136,9 +130,10 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
const connectorOptions = useMemo(() => {
|
||||
return (
|
||||
connectors?.map((connector) => {
|
||||
const apiProvider: string | undefined = (
|
||||
connector as ActionConnectorProps<Config, unknown>
|
||||
)?.config?.apiProvider;
|
||||
const apiProvider = getGenAiConfig(connector)?.apiProvider;
|
||||
const connectorDetails = connector.isPreconfigured
|
||||
? i18n.PRECONFIGURED_CONNECTOR
|
||||
: apiProvider;
|
||||
return {
|
||||
value: connector.id,
|
||||
inputDisplay: (
|
||||
|
@ -149,9 +144,9 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
dropdownDisplay: (
|
||||
<React.Fragment key={connector.id}>
|
||||
<strong>{connector.name}</strong>
|
||||
{apiProvider && (
|
||||
{connectorDetails && (
|
||||
<EuiText size="xs" color="subdued">
|
||||
<p>{apiProvider}</p>
|
||||
<p>{connectorDetails}</p>
|
||||
</EuiText>
|
||||
)}
|
||||
</React.Fragment>
|
||||
|
@ -182,7 +177,7 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
const handleOnBlur = useCallback(() => setIsOpen(false), []);
|
||||
|
||||
const onChange = useCallback(
|
||||
(connectorId: string, apiProvider?: OpenAiProviderType) => {
|
||||
(connectorId: string, apiProvider?: OpenAiProviderType, model?: string) => {
|
||||
setIsOpen(false);
|
||||
|
||||
if (connectorId === ADD_NEW_CONNECTOR) {
|
||||
|
@ -191,31 +186,22 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
return;
|
||||
}
|
||||
|
||||
const provider =
|
||||
apiProvider ??
|
||||
((connectors?.find((c) => c.id === connectorId) as ActionConnectorProps<Config, unknown>)
|
||||
?.config.apiProvider as OpenAiProviderType);
|
||||
|
||||
const connector = connectors?.find((c) => c.id === connectorId);
|
||||
const config = getGenAiConfig(connector);
|
||||
if (selectedConversation != null) {
|
||||
setApiConfig({
|
||||
conversationId: selectedConversation.id,
|
||||
apiConfig: {
|
||||
...selectedConversation.apiConfig,
|
||||
connectorId,
|
||||
provider,
|
||||
// With the inline component, prefer config args to handle 'new connector' case
|
||||
provider: apiProvider ?? config?.apiProvider,
|
||||
model: model ?? config?.defaultModel,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onConnectorSelectionChange(connectorId, provider);
|
||||
},
|
||||
[
|
||||
connectors,
|
||||
selectedConversation,
|
||||
onConnectorSelectionChange,
|
||||
onConnectorModalVisibilityChange,
|
||||
setApiConfig,
|
||||
]
|
||||
[connectors, selectedConversation, onConnectorModalVisibilityChange, setApiConfig]
|
||||
);
|
||||
|
||||
const placeholderComponent = useMemo(
|
||||
|
@ -276,11 +262,9 @@ export const ConnectorSelectorInline: React.FC<Props> = React.memo(
|
|||
<ConnectorAddModal
|
||||
actionType={actionType}
|
||||
onClose={cleanupAndCloseModal}
|
||||
postSaveEventHandler={(savedAction: ActionConnector) => {
|
||||
const provider = (savedAction as ActionConnectorProps<Config, unknown>)?.config
|
||||
.apiProvider as OpenAiProviderType;
|
||||
onChange(savedAction.id, provider);
|
||||
onConnectorSelectionChange(savedAction.id, provider);
|
||||
postSaveEventHandler={(connector: ActionConnector) => {
|
||||
const config = getGenAiConfig(connector);
|
||||
onChange(connector.id, config?.apiProvider, config?.defaultModel);
|
||||
refetchConnectors?.();
|
||||
cleanupAndCloseModal();
|
||||
}}
|
||||
|
|
|
@ -14,11 +14,7 @@ import { ConnectorAddModal } from '@kbn/triggers-actions-ui-plugin/public/common
|
|||
import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
|
||||
import { ActionType } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import {
|
||||
GEN_AI_CONNECTOR_ID,
|
||||
OpenAiProviderType,
|
||||
} from '@kbn/stack-connectors-plugin/public/common';
|
||||
import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { GEN_AI_CONNECTOR_ID } from '@kbn/stack-connectors-plugin/public/common';
|
||||
import { WELCOME_CONVERSATION } from '../../assistant/use_conversation/sample_conversations';
|
||||
import { Conversation, Message } from '../../..';
|
||||
import { useLoadActionTypes } from '../use_load_action_types';
|
||||
|
@ -30,6 +26,7 @@ import * as i18n from '../translations';
|
|||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useLoadConnectors } from '../use_load_connectors';
|
||||
import { AssistantAvatar } from '../../assistant/assistant_avatar/assistant_avatar';
|
||||
import { getGenAiConfig } from '../helpers';
|
||||
|
||||
const ConnectorButtonWrapper = styled.div`
|
||||
margin-bottom: 10px;
|
||||
|
@ -39,10 +36,6 @@ const SkipEuiText = styled(EuiText)`
|
|||
margin-top: 20px;
|
||||
`;
|
||||
|
||||
interface Config {
|
||||
apiProvider: string;
|
||||
}
|
||||
|
||||
export interface ConnectorSetupProps {
|
||||
conversation?: Conversation;
|
||||
onSetupComplete?: () => void;
|
||||
|
@ -223,16 +216,17 @@ export const useConnectorSetup = ({
|
|||
<ConnectorAddModal
|
||||
actionType={actionType}
|
||||
onClose={() => setIsConnectorModalVisible(false)}
|
||||
postSaveEventHandler={(savedAction: ActionConnector) => {
|
||||
postSaveEventHandler={(connector: ActionConnector) => {
|
||||
const config = getGenAiConfig(connector);
|
||||
// Add connector to all conversations
|
||||
Object.values(conversations).forEach((c) => {
|
||||
setApiConfig({
|
||||
conversationId: c.id,
|
||||
apiConfig: {
|
||||
...c.apiConfig,
|
||||
connectorId: savedAction.id,
|
||||
provider: (savedAction as ActionConnectorProps<Config, unknown>)?.config
|
||||
.apiProvider as OpenAiProviderType,
|
||||
connectorId: connector.id,
|
||||
provider: config?.apiProvider,
|
||||
model: config?.defaultModel,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { ActionConnectorProps } from '@kbn/triggers-actions-ui-plugin/public/types';
|
||||
import { OpenAiProviderType } from '@kbn/stack-connectors-plugin/common/gen_ai/constants';
|
||||
|
||||
interface GenAiConfig {
|
||||
apiProvider?: OpenAiProviderType;
|
||||
apiUrl?: string;
|
||||
defaultModel?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GenAiConfig for a given ActionConnector. Note that if the connector is preconfigured,
|
||||
* the config will be undefined as the connector is neither available nor editable.
|
||||
*
|
||||
* TODO: Extract and use separate types from GenAiConfig from '@kbn/stack-connectors-plugin/common/gen_ai/types'
|
||||
*
|
||||
* @param connector
|
||||
*/
|
||||
export const getGenAiConfig = (connector: ActionConnector | undefined): GenAiConfig | undefined => {
|
||||
if (!connector?.isPreconfigured) {
|
||||
return (connector as ActionConnectorProps<GenAiConfig, unknown>)?.config;
|
||||
}
|
||||
return undefined;
|
||||
};
|
|
@ -31,6 +31,13 @@ export const WELCOME_SECURITY = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const PRECONFIGURED_CONNECTOR = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.preconfiguredTitle',
|
||||
{
|
||||
defaultMessage: 'Preconfigured',
|
||||
}
|
||||
);
|
||||
|
||||
export const CONNECTOR_SELECTOR_TITLE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.connectors.connectorSelector.ariaLabel',
|
||||
{
|
||||
|
|
|
@ -5,12 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { NewChat } from '@kbn/elastic-assistant';
|
||||
import { useUserData } from '../../../../detections/components/user_info';
|
||||
import { TabNavigation } from '../../../../common/components/navigation/tab_navigation';
|
||||
import { usePrebuiltRulesStatus } from '../../../rule_management/logic/prebuilt_rules/use_prebuilt_rules_status';
|
||||
import { useRuleManagementFilters } from '../../../rule_management/logic/use_rule_management_filters';
|
||||
import * as i18n from './translations';
|
||||
import { getPromptContextFromDetectionRules } from '../../../../assistant/helpers';
|
||||
import { useRulesTableContext } from './rules_table/rules_table_context';
|
||||
import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability';
|
||||
import * as i18nAssistant from '../../../../detections/pages/detection_engine/rules/translations';
|
||||
|
||||
export enum AllRulesTabs {
|
||||
management = 'management',
|
||||
|
@ -71,7 +77,39 @@ export const RulesTableToolbar = React.memo(() => {
|
|||
[installedTotal, updateTotal, shouldDisplayRuleUpdatesTab]
|
||||
);
|
||||
|
||||
return <TabNavigation navTabs={ruleTabs} />;
|
||||
// Assistant integration for using selected rules as prompt context
|
||||
const { hasAssistantPrivilege } = useAssistantAvailability();
|
||||
const {
|
||||
state: { rules, selectedRuleIds },
|
||||
} = useRulesTableContext();
|
||||
const selectedRules = useMemo(
|
||||
() => rules.filter((rule) => selectedRuleIds.includes(rule.id)),
|
||||
[rules, selectedRuleIds]
|
||||
);
|
||||
const getPromptContext = useCallback(
|
||||
async () => getPromptContextFromDetectionRules(selectedRules),
|
||||
[selectedRules]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiFlexGroup justifyContent={'spaceBetween'}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TabNavigation navTabs={ruleTabs} />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
{hasAssistantPrivilege && selectedRules.length > 0 && (
|
||||
<NewChat
|
||||
category="detection-rules"
|
||||
conversationId={i18nAssistant.DETECTION_RULES_CONVERSATION_ID}
|
||||
description={i18nAssistant.RULE_MANAGEMENT_CONTEXT_DESCRIPTION}
|
||||
getPromptContext={getPromptContext}
|
||||
suggestedUserPrompt={i18nAssistant.EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS}
|
||||
tooltip={i18nAssistant.RULE_MANAGEMENT_CONTEXT_TOOLTIP}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
});
|
||||
|
||||
RulesTableToolbar.displayName = 'RulesTableToolbar';
|
||||
|
|
|
@ -32,8 +32,8 @@ import { useInvalidateFindRulesQuery } from '../../../rule_management/api/hooks/
|
|||
import { importRules } from '../../../rule_management/logic';
|
||||
import { AllRules } from '../../components/rules_table';
|
||||
import { RulesTableContextProvider } from '../../components/rules_table/rules_table/rules_table_context';
|
||||
import { SuperHeader } from './super_header';
|
||||
import { useInvalidateFetchCoverageOverviewQuery } from '../../../rule_management/api/hooks/use_fetch_coverage_overview';
|
||||
import { HeaderPage } from '../../../../common/components/header_page';
|
||||
|
||||
const RulesPageComponent: React.FC = () => {
|
||||
const [isImportModalVisible, showImportModal, hideImportModal] = useBoolState();
|
||||
|
@ -110,7 +110,7 @@ const RulesPageComponent: React.FC = () => {
|
|||
|
||||
<RulesTableContextProvider>
|
||||
<SecuritySolutionPageWrapper>
|
||||
<SuperHeader>
|
||||
<HeaderPage title={i18n.PAGE_TITLE}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="s" responsive={false} wrap={true}>
|
||||
<EuiFlexItem grow={false}>
|
||||
<AddElasticRulesButton isDisabled={!canUserCRUD || loading} />
|
||||
|
@ -149,7 +149,7 @@ const RulesPageComponent: React.FC = () => {
|
|||
</SecuritySolutionLinkButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</SuperHeader>
|
||||
</HeaderPage>
|
||||
<MaintenanceWindowCallout kibanaServices={kibanaServices} />
|
||||
<AllRules data-test-subj="all-rules" />
|
||||
</SecuritySolutionPageWrapper>
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { NewChat } from '@kbn/elastic-assistant';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
|
||||
import { useAssistantAvailability } from '../../../../assistant/use_assistant_availability';
|
||||
import { getPromptContextFromDetectionRules } from '../../../../assistant/helpers';
|
||||
import { HeaderPage } from '../../../../common/components/header_page';
|
||||
import { useRulesTableContext } from '../../components/rules_table/rules_table/rules_table_context';
|
||||
import * as i18n from '../../../../detections/pages/detection_engine/rules/translations';
|
||||
|
||||
export const SuperHeader: React.FC<{ children: React.ReactNode }> = React.memo(({ children }) => {
|
||||
const { hasAssistantPrivilege } = useAssistantAvailability();
|
||||
const memoizedChildren = useMemo(() => children, [children]);
|
||||
// Rules state
|
||||
const {
|
||||
state: { rules, selectedRuleIds },
|
||||
} = useRulesTableContext();
|
||||
|
||||
const selectedRules = useMemo(
|
||||
() => rules.filter((rule) => selectedRuleIds.includes(rule.id)),
|
||||
[rules, selectedRuleIds]
|
||||
);
|
||||
|
||||
const getPromptContext = useCallback(
|
||||
async () => getPromptContextFromDetectionRules(selectedRules),
|
||||
[selectedRules]
|
||||
);
|
||||
|
||||
return (
|
||||
<HeaderPage
|
||||
title={
|
||||
<>
|
||||
{i18n.PAGE_TITLE}{' '}
|
||||
{hasAssistantPrivilege && selectedRules.length > 0 && (
|
||||
<NewChat
|
||||
category="detection-rules"
|
||||
conversationId={i18n.DETECTION_RULES_CONVERSATION_ID}
|
||||
description={i18n.RULE_MANAGEMENT_CONTEXT_DESCRIPTION}
|
||||
getPromptContext={getPromptContext}
|
||||
iconType={null}
|
||||
suggestedUserPrompt={i18n.EXPLAIN_THEN_SUMMARIZE_RULE_DETAILS}
|
||||
tooltip={i18n.RULE_MANAGEMENT_CONTEXT_TOOLTIP}
|
||||
>
|
||||
{'🪄✨'}
|
||||
</NewChat>
|
||||
)}
|
||||
</>
|
||||
}
|
||||
>
|
||||
{memoizedChildren}
|
||||
</HeaderPage>
|
||||
);
|
||||
});
|
||||
|
||||
SuperHeader.displayName = 'NewChatComponent';
|
Loading…
Add table
Add a link
Reference in a new issue