mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
# Backport This will backport the following commits from `main` to `8.x`: - [[Security Assistant] V2 Knowledge Base Settings feedback and fixes (#194354)](https://github.com/elastic/kibana/pull/194354) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Garrett Spong","email":"spong@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-09T16:17:47Z","message":"[Security Assistant] V2 Knowledge Base Settings feedback and fixes (#194354)\n\n## Summary\r\n\r\nThis PR is a follow up to #192665 and addresses a bunch of feedback and\r\nfixes including:\r\n\r\n- [X] Adds support for updating/editing entries\r\n- [X] Fixes initial loading experience of the KB Settings Setup/Table\r\n- [X] Fixes two bugs where `semantic_text` and `text` must be declared\r\nfor `IndexEntries` to work\r\n- [X] Add new Settings Context Menu items for KB and Alerts\r\n - [X] Add support for `required` entries in initial prompt\r\n* See [this\r\ntrace](https://smith.langchain.com/public/84a17a31-8ce8-4bd9-911e-38a854484dd8/r)\r\nfor included knowledge. Note that the KnowledgeBaseRetrievalTool was not\r\nselected.\r\n* Note: All prompts were updated to include the `{knowledge_history}`\r\nplaceholder, and _not behind the feature flag_, as this will just be the\r\nempty case until the feature flag is enabled.\r\n\r\nTODO (in this or follow-up PR):\r\n - [ ] Add suggestions to `index` and `fields` inputs\r\n - [ ] Adds URL deeplinking to securityAssistantManagement\r\n- [ ] Fix bug where updating entry does not re-create embeddings (see\r\n[comment](https://github.com/elastic/kibana/pull/194354#discussion_r1786475496))\r\n - [ ] Fix loading indicators when adding/editing entries\r\n - [ ] API integration tests for update API (@e40pud)\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [X] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n* Docs being tracked in\r\nhttps://github.com/elastic/security-docs/issues/5337 for when feature\r\nflag is enabled\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Patryk Kopycinski <contact@patrykkopycinski.com>","sha":"7df36721923159f45bc4fdbd26f76b20ad84249a","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Feature:Security Assistant","Team:Security Generative AI","v8.16.0","backport:version"],"title":"[Security Assistant] V2 Knowledge Base Settings feedback and fixes","number":194354,"url":"https://github.com/elastic/kibana/pull/194354","mergeCommit":{"message":"[Security Assistant] V2 Knowledge Base Settings feedback and fixes (#194354)\n\n## Summary\r\n\r\nThis PR is a follow up to #192665 and addresses a bunch of feedback and\r\nfixes including:\r\n\r\n- [X] Adds support for updating/editing entries\r\n- [X] Fixes initial loading experience of the KB Settings Setup/Table\r\n- [X] Fixes two bugs where `semantic_text` and `text` must be declared\r\nfor `IndexEntries` to work\r\n- [X] Add new Settings Context Menu items for KB and Alerts\r\n - [X] Add support for `required` entries in initial prompt\r\n* See [this\r\ntrace](https://smith.langchain.com/public/84a17a31-8ce8-4bd9-911e-38a854484dd8/r)\r\nfor included knowledge. Note that the KnowledgeBaseRetrievalTool was not\r\nselected.\r\n* Note: All prompts were updated to include the `{knowledge_history}`\r\nplaceholder, and _not behind the feature flag_, as this will just be the\r\nempty case until the feature flag is enabled.\r\n\r\nTODO (in this or follow-up PR):\r\n - [ ] Add suggestions to `index` and `fields` inputs\r\n - [ ] Adds URL deeplinking to securityAssistantManagement\r\n- [ ] Fix bug where updating entry does not re-create embeddings (see\r\n[comment](https://github.com/elastic/kibana/pull/194354#discussion_r1786475496))\r\n - [ ] Fix loading indicators when adding/editing entries\r\n - [ ] API integration tests for update API (@e40pud)\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [X] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n* Docs being tracked in\r\nhttps://github.com/elastic/security-docs/issues/5337 for when feature\r\nflag is enabled\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Patryk Kopycinski <contact@patrykkopycinski.com>","sha":"7df36721923159f45bc4fdbd26f76b20ad84249a"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194354","number":194354,"mergeCommit":{"message":"[Security Assistant] V2 Knowledge Base Settings feedback and fixes (#194354)\n\n## Summary\r\n\r\nThis PR is a follow up to #192665 and addresses a bunch of feedback and\r\nfixes including:\r\n\r\n- [X] Adds support for updating/editing entries\r\n- [X] Fixes initial loading experience of the KB Settings Setup/Table\r\n- [X] Fixes two bugs where `semantic_text` and `text` must be declared\r\nfor `IndexEntries` to work\r\n- [X] Add new Settings Context Menu items for KB and Alerts\r\n - [X] Add support for `required` entries in initial prompt\r\n* See [this\r\ntrace](https://smith.langchain.com/public/84a17a31-8ce8-4bd9-911e-38a854484dd8/r)\r\nfor included knowledge. Note that the KnowledgeBaseRetrievalTool was not\r\nselected.\r\n* Note: All prompts were updated to include the `{knowledge_history}`\r\nplaceholder, and _not behind the feature flag_, as this will just be the\r\nempty case until the feature flag is enabled.\r\n\r\nTODO (in this or follow-up PR):\r\n - [ ] Add suggestions to `index` and `fields` inputs\r\n - [ ] Adds URL deeplinking to securityAssistantManagement\r\n- [ ] Fix bug where updating entry does not re-create embeddings (see\r\n[comment](https://github.com/elastic/kibana/pull/194354#discussion_r1786475496))\r\n - [ ] Fix loading indicators when adding/editing entries\r\n - [ ] API integration tests for update API (@e40pud)\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [X] Any text added follows [EUI's writing\r\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\r\nsentence case text and includes [i18n\r\nsupport](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)\r\n- [ ]\r\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\r\nwas added for features that require explanation or tutorials\r\n* Docs being tracked in\r\nhttps://github.com/elastic/security-docs/issues/5337 for when feature\r\nflag is enabled\r\n- [ ] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios\r\n\r\n---------\r\n\r\nCo-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>\r\nCo-authored-by: Patryk Kopycinski <contact@patrykkopycinski.com>","sha":"7df36721923159f45bc4fdbd26f76b20ad84249a"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Garrett Spong <spong@users.noreply.github.com>
This commit is contained in:
parent
1da5439d8d
commit
e8992e3749
32 changed files with 686 additions and 143 deletions
|
@ -106,7 +106,11 @@ export type BaseCreateProps = z.infer<typeof BaseCreateProps>;
|
|||
export const BaseCreateProps = BaseRequiredFields.merge(BaseDefaultableFields);
|
||||
|
||||
export type BaseUpdateProps = z.infer<typeof BaseUpdateProps>;
|
||||
export const BaseUpdateProps = BaseCreateProps.partial();
|
||||
export const BaseUpdateProps = BaseCreateProps.partial().merge(
|
||||
z.object({
|
||||
id: NonEmptyString,
|
||||
})
|
||||
);
|
||||
|
||||
export type BaseResponseProps = z.infer<typeof BaseResponseProps>;
|
||||
export const BaseResponseProps = BaseRequiredFields.merge(BaseDefaultableFields.required());
|
||||
|
|
|
@ -112,6 +112,12 @@ components:
|
|||
allOf:
|
||||
- $ref: "#/components/schemas/BaseCreateProps"
|
||||
x-modify: partial
|
||||
- type: object
|
||||
properties:
|
||||
id:
|
||||
$ref: "../../common_attributes.schema.yaml#/components/schemas/NonEmptyString"
|
||||
required:
|
||||
- id
|
||||
|
||||
BaseResponseProps:
|
||||
x-inline: true
|
||||
|
|
|
@ -5,16 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useState, useMemo, useCallback } from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { QueryObserverResult, RefetchOptions, RefetchQueryFilters } from '@tanstack/react-query';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiPopover,
|
||||
EuiContextMenu,
|
||||
EuiButtonIcon,
|
||||
EuiPanel,
|
||||
EuiConfirmModal,
|
||||
EuiToolTip,
|
||||
EuiSkeletonTitle,
|
||||
} from '@elastic/eui';
|
||||
|
@ -29,6 +26,7 @@ import { FlyoutNavigation } from '../assistant_overlay/flyout_navigation';
|
|||
import { AssistantSettingsButton } from '../settings/assistant_settings_button';
|
||||
import * as i18n from './translations';
|
||||
import { AIConnector } from '../../connectorland/connector_selector';
|
||||
import { SettingsContextMenu } from '../settings/settings_context_menu/settings_context_menu';
|
||||
|
||||
interface OwnProps {
|
||||
selectedConversation: Conversation | undefined;
|
||||
|
@ -94,21 +92,6 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
[selectedConversation?.apiConfig?.connectorId]
|
||||
);
|
||||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
setPopover(!isPopoverOpen);
|
||||
}, [isPopoverOpen]);
|
||||
|
||||
const closePopover = useCallback(() => {
|
||||
setPopover(false);
|
||||
}, []);
|
||||
|
||||
const [isResetConversationModalVisible, setIsResetConversationModalVisible] = useState(false);
|
||||
|
||||
const closeDestroyModal = useCallback(() => setIsResetConversationModalVisible(false), []);
|
||||
const showDestroyModal = useCallback(() => setIsResetConversationModalVisible(true), []);
|
||||
|
||||
const onConversationChange = useCallback(
|
||||
(updatedConversation: Conversation) => {
|
||||
onConversationSelected({
|
||||
|
@ -119,32 +102,6 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
[onConversationSelected]
|
||||
);
|
||||
|
||||
const panels = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 0,
|
||||
items: [
|
||||
{
|
||||
name: i18n.RESET_CONVERSATION,
|
||||
css: css`
|
||||
color: ${euiThemeVars.euiColorDanger};
|
||||
`,
|
||||
onClick: showDestroyModal,
|
||||
icon: 'refresh',
|
||||
'data-test-subj': 'clear-chat',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[showDestroyModal]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
onChatCleared();
|
||||
closeDestroyModal();
|
||||
closePopover();
|
||||
}, [onChatCleared, closeDestroyModal, closePopover]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<FlyoutNavigation
|
||||
|
@ -246,42 +203,12 @@ export const AssistantHeader: React.FC<Props> = ({
|
|||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label="test"
|
||||
isDisabled={isDisabled}
|
||||
iconType="boxesVertical"
|
||||
onClick={onButtonClick}
|
||||
data-test-subj="chat-context-menu"
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="downLeft"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panels} />
|
||||
</EuiPopover>
|
||||
<SettingsContextMenu isDisabled={isDisabled} onChatCleared={onChatCleared} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
{isResetConversationModalVisible && (
|
||||
<EuiConfirmModal
|
||||
title={i18n.RESET_CONVERSATION}
|
||||
onCancel={closeDestroyModal}
|
||||
onConfirm={handleReset}
|
||||
cancelButtonText={i18n.CANCEL_BUTTON_TEXT}
|
||||
confirmButtonText={i18n.RESET_BUTTON_TEXT}
|
||||
buttonColor="danger"
|
||||
defaultFocusedButton="confirm"
|
||||
data-test-subj="reset-conversation-modal"
|
||||
>
|
||||
<p>{i18n.CLEAR_CHAT_CONFIRMATION}</p>
|
||||
</EuiConfirmModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -7,6 +7,34 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const AI_ASSISTANT_SETTINGS = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.aiAssistantSettings',
|
||||
{
|
||||
defaultMessage: 'AI Assistant settings',
|
||||
}
|
||||
);
|
||||
|
||||
export const ANONYMIZATION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.anonymization',
|
||||
{
|
||||
defaultMessage: 'Anonymization',
|
||||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBase',
|
||||
{
|
||||
defaultMessage: 'Knowledge Base',
|
||||
}
|
||||
);
|
||||
|
||||
export const ALERTS_TO_ANALYZE = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.alertsToAnalyze',
|
||||
{
|
||||
defaultMessage: 'Alerts to analyze',
|
||||
}
|
||||
);
|
||||
|
||||
export const RESET_CONVERSATION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.resetConversation',
|
||||
{
|
||||
|
|
|
@ -9,8 +9,8 @@ import { render, screen, fireEvent } from '@testing-library/react';
|
|||
import React from 'react';
|
||||
|
||||
import { AlertsSettings } from './alerts_settings';
|
||||
import { KnowledgeBaseConfig } from '../../assistant/types';
|
||||
import { DEFAULT_LATEST_ALERTS } from '../../assistant_context/constants';
|
||||
import { KnowledgeBaseConfig } from '../../types';
|
||||
import { DEFAULT_LATEST_ALERTS } from '../../../assistant_context/constants';
|
||||
|
||||
describe('AlertsSettings', () => {
|
||||
beforeEach(() => {
|
|
@ -9,9 +9,9 @@ import { EuiFlexGroup, EuiFormRow, EuiFlexItem, EuiSpacer, EuiText } from '@elas
|
|||
import { css } from '@emotion/react';
|
||||
import React from 'react';
|
||||
|
||||
import { KnowledgeBaseConfig } from '../../assistant/types';
|
||||
import { AlertsRange } from '../../knowledge_base/alerts_range';
|
||||
import * as i18n from '../../knowledge_base/translations';
|
||||
import { KnowledgeBaseConfig } from '../../types';
|
||||
import { AlertsRange } from '../../../knowledge_base/alerts_range';
|
||||
import * as i18n from '../../../knowledge_base/translations';
|
||||
|
||||
export const MIN_LATEST_ALERTS = 10;
|
||||
export const MAX_LATEST_ALERTS = 100;
|
|
@ -7,19 +7,24 @@
|
|||
|
||||
import { EuiPanel, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { KnowledgeBaseConfig } from '../../assistant/types';
|
||||
import { AlertsRange } from '../../knowledge_base/alerts_range';
|
||||
import * as i18n from '../../knowledge_base/translations';
|
||||
import { KnowledgeBaseConfig } from '../../types';
|
||||
import { AlertsRange } from '../../../knowledge_base/alerts_range';
|
||||
import * as i18n from '../../../knowledge_base/translations';
|
||||
|
||||
interface Props {
|
||||
knowledgeBase: KnowledgeBaseConfig;
|
||||
setUpdatedKnowledgeBaseSettings: React.Dispatch<React.SetStateAction<KnowledgeBaseConfig>>;
|
||||
hasBorder?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the AlertsSettings component used in the existing settings modal. Once the modal is
|
||||
* fully removed we can delete that component in favor of this one.
|
||||
*/
|
||||
export const AlertsSettingsManagement: React.FC<Props> = React.memo(
|
||||
({ knowledgeBase, setUpdatedKnowledgeBaseSettings }) => {
|
||||
({ knowledgeBase, setUpdatedKnowledgeBaseSettings, hasBorder = true }) => {
|
||||
return (
|
||||
<EuiPanel hasShadow={false} hasBorder paddingSize="l" title={i18n.ALERTS_LABEL}>
|
||||
<EuiPanel hasShadow={false} hasBorder={hasBorder} paddingSize="l" title={i18n.ALERTS_LABEL}>
|
||||
<EuiTitle size="m">
|
||||
<h3>{i18n.ALERTS_LABEL}</h3>
|
||||
</EuiTitle>
|
|
@ -25,6 +25,7 @@ import {
|
|||
SYSTEM_PROMPTS_TAB,
|
||||
} from './const';
|
||||
import { mockSystemPrompts } from '../../mock/system_prompt';
|
||||
import { DataViewsContract } from '@kbn/data-views-plugin/public';
|
||||
|
||||
const mockConversations = {
|
||||
[alertConvo.title]: alertConvo,
|
||||
|
@ -53,8 +54,13 @@ const mockContext = {
|
|||
},
|
||||
};
|
||||
|
||||
const mockDataViews = {
|
||||
getIndices: jest.fn(),
|
||||
} as unknown as DataViewsContract;
|
||||
|
||||
const testProps = {
|
||||
selectedConversation: welcomeConvo,
|
||||
dataViews: mockDataViews,
|
||||
};
|
||||
jest.mock('../../assistant_context');
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import React, { useEffect, useMemo } from 'react';
|
|||
import { EuiAvatar, EuiPageTemplate, EuiTitle, useEuiShadow, useEuiTheme } from '@elastic/eui';
|
||||
|
||||
import { css } from '@emotion/react';
|
||||
import { DataViewsContract } from '@kbn/data-views-plugin/public';
|
||||
import { Conversation } from '../../..';
|
||||
import * as i18n from './translations';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
|
@ -33,6 +34,7 @@ import { KnowledgeBaseSettingsManagement } from '../../knowledge_base/knowledge_
|
|||
import { EvaluationSettings } from '.';
|
||||
|
||||
interface Props {
|
||||
dataViews: DataViewsContract;
|
||||
selectedConversation: Conversation;
|
||||
}
|
||||
|
||||
|
@ -41,7 +43,7 @@ interface Props {
|
|||
* anonymization, knowledge base, and evaluation via the `isModelEvaluationEnabled` feature flag.
|
||||
*/
|
||||
export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
||||
({ selectedConversation: defaultSelectedConversation }) => {
|
||||
({ dataViews, selectedConversation: defaultSelectedConversation }) => {
|
||||
const {
|
||||
assistantFeatures: { assistantModelEvaluation: modelEvaluatorEnabled },
|
||||
http,
|
||||
|
@ -158,7 +160,9 @@ export const AssistantSettingsManagement: React.FC<Props> = React.memo(
|
|||
)}
|
||||
{selectedSettingsTab === QUICK_PROMPTS_TAB && <QuickPromptSettingsManagement />}
|
||||
{selectedSettingsTab === ANONYMIZATION_TAB && <AnonymizationSettingsManagement />}
|
||||
{selectedSettingsTab === KNOWLEDGE_BASE_TAB && <KnowledgeBaseSettingsManagement />}
|
||||
{selectedSettingsTab === KNOWLEDGE_BASE_TAB && (
|
||||
<KnowledgeBaseSettingsManagement dataViews={dataViews} />
|
||||
)}
|
||||
{selectedSettingsTab === EVALUATION_TAB && <EvaluationSettings />}
|
||||
</EuiPageTemplate.Section>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
/*
|
||||
* 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 React, { ReactElement, useCallback, useMemo, useState } from 'react';
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiContextMenuPanel,
|
||||
EuiContextMenuItem,
|
||||
EuiConfirmModal,
|
||||
EuiNotificationBadge,
|
||||
EuiPopover,
|
||||
EuiButtonIcon,
|
||||
} from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import { euiThemeVars } from '@kbn/ui-theme';
|
||||
import { useAssistantContext } from '../../../..';
|
||||
import * as i18n from '../../assistant_header/translations';
|
||||
|
||||
interface Params {
|
||||
isDisabled?: boolean;
|
||||
onChatCleared?: () => void;
|
||||
}
|
||||
|
||||
export const SettingsContextMenu: React.FC<Params> = React.memo(
|
||||
({ isDisabled = false, onChatCleared }: Params) => {
|
||||
const {
|
||||
navigateToApp,
|
||||
knowledgeBase,
|
||||
assistantFeatures: { assistantKnowledgeBaseByDefault: enableKnowledgeBaseByDefault },
|
||||
} = useAssistantContext();
|
||||
|
||||
const [isPopoverOpen, setPopover] = useState(false);
|
||||
|
||||
const [isResetConversationModalVisible, setIsResetConversationModalVisible] = useState(false);
|
||||
const closeDestroyModal = useCallback(() => setIsResetConversationModalVisible(false), []);
|
||||
|
||||
const onButtonClick = useCallback(() => {
|
||||
setPopover(!isPopoverOpen);
|
||||
}, [isPopoverOpen]);
|
||||
|
||||
const closePopover = useCallback(() => {
|
||||
setPopover(false);
|
||||
}, []);
|
||||
|
||||
const showDestroyModal = useCallback(() => {
|
||||
closePopover?.();
|
||||
setIsResetConversationModalVisible(true);
|
||||
}, [closePopover]);
|
||||
|
||||
const handleNavigateToSettings = useCallback(
|
||||
() =>
|
||||
navigateToApp('management', {
|
||||
path: 'kibana/securityAiAssistantManagement',
|
||||
}),
|
||||
[navigateToApp]
|
||||
);
|
||||
|
||||
const handleNavigateToKnowledgeBase = useCallback(
|
||||
() =>
|
||||
navigateToApp('management', {
|
||||
path: 'kibana/securityAiAssistantManagement',
|
||||
}),
|
||||
[navigateToApp]
|
||||
);
|
||||
|
||||
// We are migrating away from the settings modal in favor of the new Stack Management UI
|
||||
// Currently behind `assistantKnowledgeBaseByDefault` FF
|
||||
const newItems: ReactElement[] = useMemo(
|
||||
() => [
|
||||
<EuiContextMenuItem
|
||||
aria-label={'ai-assistant-settings'}
|
||||
onClick={handleNavigateToSettings}
|
||||
icon={'gear'}
|
||||
data-test-subj={'ai-assistant-settings'}
|
||||
>
|
||||
{i18n.AI_ASSISTANT_SETTINGS}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
aria-label={'anonymization'}
|
||||
onClick={handleNavigateToSettings}
|
||||
icon={'eye'}
|
||||
data-test-subj={'anonymization'}
|
||||
>
|
||||
{i18n.ANONYMIZATION}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
aria-label={'knowledge-base'}
|
||||
onClick={handleNavigateToKnowledgeBase}
|
||||
icon={'documents'}
|
||||
data-test-subj={'knowledge-base'}
|
||||
>
|
||||
{i18n.KNOWLEDGE_BASE}
|
||||
</EuiContextMenuItem>,
|
||||
<EuiContextMenuItem
|
||||
aria-label={'alerts-to-analyze'}
|
||||
onClick={handleNavigateToSettings}
|
||||
icon={'magnifyWithExclamation'}
|
||||
data-test-subj={'alerts-to-analyze'}
|
||||
>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>{i18n.ALERTS_TO_ANALYZE}</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiNotificationBadge color="subdued">
|
||||
{knowledgeBase.latestAlerts}
|
||||
</EuiNotificationBadge>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiContextMenuItem>,
|
||||
],
|
||||
[handleNavigateToKnowledgeBase, handleNavigateToSettings, knowledgeBase]
|
||||
);
|
||||
|
||||
const items = useMemo(
|
||||
() => [
|
||||
...(enableKnowledgeBaseByDefault ? newItems : []),
|
||||
<EuiContextMenuItem
|
||||
aria-label={'clear-chat'}
|
||||
onClick={showDestroyModal}
|
||||
icon={'refresh'}
|
||||
data-test-subj={'clear-chat'}
|
||||
css={css`
|
||||
color: ${euiThemeVars.euiColorDanger};
|
||||
`}
|
||||
>
|
||||
{i18n.RESET_CONVERSATION}
|
||||
</EuiContextMenuItem>,
|
||||
],
|
||||
|
||||
[enableKnowledgeBaseByDefault, newItems, showDestroyModal]
|
||||
);
|
||||
|
||||
const handleReset = useCallback(() => {
|
||||
onChatCleared?.();
|
||||
closeDestroyModal();
|
||||
closePopover?.();
|
||||
}, [onChatCleared, closeDestroyModal, closePopover]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButtonIcon
|
||||
aria-label="test"
|
||||
isDisabled={isDisabled}
|
||||
iconType="boxesVertical"
|
||||
onClick={onButtonClick}
|
||||
data-test-subj="chat-context-menu"
|
||||
/>
|
||||
}
|
||||
isOpen={isPopoverOpen}
|
||||
closePopover={closePopover}
|
||||
panelPaddingSize="none"
|
||||
anchorPosition="leftUp"
|
||||
>
|
||||
<EuiContextMenuPanel
|
||||
items={items}
|
||||
css={css`
|
||||
width: 250px;
|
||||
`}
|
||||
/>
|
||||
</EuiPopover>
|
||||
{isResetConversationModalVisible && (
|
||||
<EuiConfirmModal
|
||||
title={i18n.RESET_CONVERSATION}
|
||||
onCancel={closeDestroyModal}
|
||||
onConfirm={handleReset}
|
||||
cancelButtonText={i18n.CANCEL_BUTTON_TEXT}
|
||||
confirmButtonText={i18n.RESET_BUTTON_TEXT}
|
||||
buttonColor="danger"
|
||||
defaultFocusedButton="confirm"
|
||||
data-test-subj="reset-conversation-modal"
|
||||
>
|
||||
<p>{i18n.CLEAR_CHAT_CONFIRMATION}</p>
|
||||
</EuiConfirmModal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
SettingsContextMenu.displayName = 'SettingsContextMenu';
|
|
@ -12,7 +12,7 @@ import {
|
|||
MAX_LATEST_ALERTS,
|
||||
MIN_LATEST_ALERTS,
|
||||
TICK_INTERVAL,
|
||||
} from '../alerts/settings/alerts_settings';
|
||||
} from '../assistant/settings/alerts_settings/alerts_settings';
|
||||
import { KnowledgeBaseConfig } from '../assistant/types';
|
||||
import { ALERTS_RANGE } from './translations';
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ import {
|
|||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { AlertsSettings } from '../alerts/settings/alerts_settings';
|
||||
import { AlertsSettings } from '../assistant/settings/alerts_settings/alerts_settings';
|
||||
import { useAssistantContext } from '../assistant_context';
|
||||
import type { KnowledgeBaseConfig } from '../assistant/types';
|
||||
import * as i18n from './translations';
|
||||
|
|
|
@ -127,7 +127,6 @@ export const DocumentEntryEditor: React.FC<Props> = React.memo(({ entry, setEntr
|
|||
id="requiredKnowledge"
|
||||
onChange={onRequiredKnowledgeChanged}
|
||||
checked={entry?.required ?? false}
|
||||
disabled={true}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
|
|
|
@ -6,8 +6,12 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiInMemoryTable,
|
||||
EuiLink,
|
||||
EuiLoadingSpinner,
|
||||
EuiPanel,
|
||||
EuiSearchBarProps,
|
||||
EuiSpacer,
|
||||
|
@ -23,7 +27,9 @@ import {
|
|||
KnowledgeBaseEntryCreateProps,
|
||||
KnowledgeBaseEntryResponse,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { AlertsSettingsManagement } from '../../alerts/settings/alerts_settings_management';
|
||||
import { css } from '@emotion/react';
|
||||
import { DataViewsContract } from '@kbn/data-views-plugin/public';
|
||||
import { AlertsSettingsManagement } from '../../assistant/settings/alerts_settings/alerts_settings_management';
|
||||
import { useKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_knowledge_base_entries';
|
||||
import { useAssistantContext } from '../../assistant_context';
|
||||
import { useKnowledgeBaseTable } from './use_knowledge_base_table';
|
||||
|
@ -40,7 +46,7 @@ import { useFlyoutModalVisibility } from '../../assistant/common/components/assi
|
|||
import { IndexEntryEditor } from './index_entry_editor';
|
||||
import { DocumentEntryEditor } from './document_entry_editor';
|
||||
import { KnowledgeBaseSettings } from '../knowledge_base_settings';
|
||||
import { SetupKnowledgeBaseButton } from '../setup_knowledge_base_button';
|
||||
import { ESQL_RESOURCE, SetupKnowledgeBaseButton } from '../setup_knowledge_base_button';
|
||||
import { useDeleteKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_delete_knowledge_base_entries';
|
||||
import {
|
||||
isSystemEntry,
|
||||
|
@ -51,14 +57,24 @@ import { useCreateKnowledgeBaseEntry } from '../../assistant/api/knowledge_base/
|
|||
import { useUpdateKnowledgeBaseEntries } from '../../assistant/api/knowledge_base/entries/use_update_knowledge_base_entries';
|
||||
import { SETTINGS_UPDATED_TOAST_TITLE } from '../../assistant/settings/translations';
|
||||
import { KnowledgeBaseConfig } from '../../assistant/types';
|
||||
import {
|
||||
isKnowledgeBaseSetup,
|
||||
useKnowledgeBaseStatus,
|
||||
} from '../../assistant/api/knowledge_base/use_knowledge_base_status';
|
||||
|
||||
export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
||||
interface Params {
|
||||
dataViews: DataViewsContract;
|
||||
}
|
||||
|
||||
export const KnowledgeBaseSettingsManagement: React.FC<Params> = React.memo(({ dataViews }) => {
|
||||
const {
|
||||
assistantFeatures: { assistantKnowledgeBaseByDefault: enableKnowledgeBaseByDefault },
|
||||
http,
|
||||
toasts,
|
||||
} = useAssistantContext();
|
||||
const [hasPendingChanges, setHasPendingChanges] = useState(false);
|
||||
const { data: kbStatus, isFetched } = useKnowledgeBaseStatus({ http, resource: ESQL_RESOURCE });
|
||||
const isKbSetup = isKnowledgeBaseSetup(kbStatus);
|
||||
|
||||
// Only needed for legacy settings management
|
||||
const { knowledgeBase, setUpdatedKnowledgeBaseSettings, resetSettings, saveSettings } =
|
||||
|
@ -123,12 +139,12 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
|||
|
||||
// Flyout Save/Cancel Actions
|
||||
const onSaveConfirmed = useCallback(() => {
|
||||
if (isKnowledgeBaseEntryCreateProps(selectedEntry)) {
|
||||
createEntry(selectedEntry);
|
||||
closeFlyout();
|
||||
} else if (isKnowledgeBaseEntryResponse(selectedEntry)) {
|
||||
if (isKnowledgeBaseEntryResponse(selectedEntry)) {
|
||||
updateEntries([selectedEntry]);
|
||||
closeFlyout();
|
||||
} else if (isKnowledgeBaseEntryCreateProps(selectedEntry)) {
|
||||
createEntry(selectedEntry);
|
||||
closeFlyout();
|
||||
}
|
||||
}, [closeFlyout, selectedEntry, createEntry, updateEntries]);
|
||||
|
||||
|
@ -137,7 +153,11 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
|||
closeFlyout();
|
||||
}, [closeFlyout]);
|
||||
|
||||
const { data: entries } = useKnowledgeBaseEntries({
|
||||
const {
|
||||
data: entries,
|
||||
isFetching: isFetchingEntries,
|
||||
refetch: refetchEntries,
|
||||
} = useKnowledgeBaseEntries({
|
||||
http,
|
||||
toasts,
|
||||
enabled: enableKnowledgeBaseByDefault,
|
||||
|
@ -169,6 +189,9 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
|||
[deleteEntry, entries.data, getColumns, openFlyout]
|
||||
);
|
||||
|
||||
// Refresh button
|
||||
const handleRefreshTable = useCallback(() => refetchEntries(), [refetchEntries]);
|
||||
|
||||
const onDocumentClicked = useCallback(() => {
|
||||
setSelectedEntry({ type: DocumentEntryType.value, kbResource: 'user', source: 'user' });
|
||||
openFlyout();
|
||||
|
@ -182,7 +205,30 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
|||
const search: EuiSearchBarProps = useMemo(
|
||||
() => ({
|
||||
toolsRight: (
|
||||
<AddEntryButton onDocumentClicked={onDocumentClicked} onIndexClicked={onIndexClicked} />
|
||||
<EuiFlexGroup
|
||||
gutterSize={'m'}
|
||||
css={css`
|
||||
margin-left: -5px;
|
||||
`}
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiButton
|
||||
color={'text'}
|
||||
isDisabled={isFetchingEntries}
|
||||
onClick={handleRefreshTable}
|
||||
iconType={'refresh'}
|
||||
isLoading={isFetchingEntries}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.assistant.settings.knowledgeBasedSettingManagements.refreshButton"
|
||||
defaultMessage="Refresh"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<AddEntryButton onDocumentClicked={onDocumentClicked} onIndexClicked={onIndexClicked} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
box: {
|
||||
incremental: true,
|
||||
|
@ -190,7 +236,7 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
|||
},
|
||||
filters: [],
|
||||
}),
|
||||
[onDocumentClicked, onIndexClicked]
|
||||
[isFetchingEntries, handleRefreshTable, onDocumentClicked, onIndexClicked]
|
||||
);
|
||||
|
||||
const flyoutTitle = useMemo(() => {
|
||||
|
@ -247,15 +293,40 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
|||
),
|
||||
}}
|
||||
/>
|
||||
<SetupKnowledgeBaseButton display={'mini'} />
|
||||
</EuiText>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiInMemoryTable
|
||||
columns={columns}
|
||||
items={entries.data ?? []}
|
||||
search={search}
|
||||
sorting={sorting}
|
||||
/>
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
{!isFetched ? (
|
||||
<EuiLoadingSpinner size="l" />
|
||||
) : isKbSetup ? (
|
||||
<EuiInMemoryTable
|
||||
columns={columns}
|
||||
items={entries.data ?? []}
|
||||
search={search}
|
||||
sorting={sorting}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<EuiSpacer size="l" />
|
||||
<EuiText size={'m'}>
|
||||
<FormattedMessage
|
||||
id="xpack.elasticAssistant.assistant.settings.knowledgeBasedSettingManagements.knowledgeBaseSetupDescription"
|
||||
defaultMessage="Setup to get started with the Knowledge Base."
|
||||
/>
|
||||
</EuiText>
|
||||
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup justifyContent="spaceAround">
|
||||
<EuiFlexItem grow={false}>
|
||||
<SetupKnowledgeBaseButton />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="l" />
|
||||
</>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
<EuiSpacer size="m" />
|
||||
<AlertsSettingsManagement
|
||||
|
@ -286,6 +357,7 @@ export const KnowledgeBaseSettingsManagement: React.FC = React.memo(() => {
|
|||
) : (
|
||||
<IndexEntryEditor
|
||||
entry={selectedEntry as IndexEntry}
|
||||
dataViews={dataViews}
|
||||
setEntry={
|
||||
setSelectedEntry as React.Dispatch<React.SetStateAction<Partial<IndexEntry>>>
|
||||
}
|
||||
|
|
|
@ -17,14 +17,16 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { IndexEntry } from '@kbn/elastic-assistant-common';
|
||||
import { DataViewsContract } from '@kbn/data-views-plugin/public';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props {
|
||||
dataViews: DataViewsContract;
|
||||
entry?: IndexEntry;
|
||||
setEntry: React.Dispatch<React.SetStateAction<Partial<IndexEntry>>>;
|
||||
}
|
||||
|
||||
export const IndexEntryEditor: React.FC<Props> = React.memo(({ entry, setEntry }) => {
|
||||
export const IndexEntryEditor: React.FC<Props> = React.memo(({ dataViews, entry, setEntry }) => {
|
||||
// Name
|
||||
const setName = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -74,9 +76,17 @@ export const IndexEntryEditor: React.FC<Props> = React.memo(({ entry, setEntry }
|
|||
entry?.users?.length === 0 ? sharingOptions[1].value : sharingOptions[0].value;
|
||||
|
||||
// Index
|
||||
// TODO: For index field autocomplete
|
||||
// const indexOptions = useMemo(() => {
|
||||
// const indices = await dataViews.getIndices({
|
||||
// pattern: e[0]?.value ?? '',
|
||||
// isRollupIndex: () => false,
|
||||
// });
|
||||
// }, [dataViews]);
|
||||
const setIndex = useCallback(
|
||||
(e: Array<EuiComboBoxOptionOption<string>>) =>
|
||||
setEntry((prevEntry) => ({ ...prevEntry, index: e[0].value })),
|
||||
async (e: Array<EuiComboBoxOptionOption<string>>) => {
|
||||
setEntry((prevEntry) => ({ ...prevEntry, index: e[0]?.value }));
|
||||
},
|
||||
[setEntry]
|
||||
);
|
||||
|
||||
|
@ -162,30 +172,51 @@ export const IndexEntryEditor: React.FC<Props> = React.memo(({ entry, setEntry }
|
|||
<EuiFormRow label={i18n.ENTRY_FIELD_INPUT_LABEL} fullWidth>
|
||||
<EuiFieldText
|
||||
name="field"
|
||||
placeholder={i18n.ENTRY_INPUT_PLACEHOLDER}
|
||||
placeholder={i18n.ENTRY_FIELD_PLACEHOLDER}
|
||||
fullWidth
|
||||
value={entry?.field}
|
||||
onChange={setField}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow label={i18n.ENTRY_DESCRIPTION_INPUT_LABEL} fullWidth>
|
||||
<EuiFormRow
|
||||
label={i18n.ENTRY_DESCRIPTION_INPUT_LABEL}
|
||||
helpText={i18n.ENTRY_DESCRIPTION_HELP_LABEL}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
name="description"
|
||||
placeholder={i18n.ENTRY_INPUT_PLACEHOLDER}
|
||||
fullWidth
|
||||
value={entry?.description}
|
||||
onChange={setDescription}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow label={i18n.ENTRY_QUERY_DESCRIPTION_INPUT_LABEL} fullWidth>
|
||||
<EuiFormRow
|
||||
label={i18n.ENTRY_QUERY_DESCRIPTION_INPUT_LABEL}
|
||||
helpText={i18n.ENTRY_QUERY_DESCRIPTION_HELP_LABEL}
|
||||
fullWidth
|
||||
>
|
||||
<EuiFieldText
|
||||
name="description"
|
||||
placeholder={i18n.ENTRY_INPUT_PLACEHOLDER}
|
||||
fullWidth
|
||||
value={entry?.queryDescription}
|
||||
onChange={setQueryDescription}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
<EuiFormRow
|
||||
label={i18n.ENTRY_OUTPUT_FIELDS_INPUT_LABEL}
|
||||
helpText={i18n.ENTRY_OUTPUT_FIELDS_HELP_LABEL}
|
||||
fullWidth
|
||||
>
|
||||
<EuiComboBox
|
||||
aria-label={i18n.ENTRY_OUTPUT_FIELDS_INPUT_LABEL}
|
||||
isClearable={true}
|
||||
singleSelection={{ asPlainText: true }}
|
||||
onCreateOption={onCreateOption}
|
||||
fullWidth
|
||||
selectedOptions={[]}
|
||||
onChange={setIndex}
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</EuiForm>
|
||||
);
|
||||
});
|
||||
|
|
|
@ -251,14 +251,44 @@ export const ENTRY_FIELD_INPUT_LABEL = i18n.translate(
|
|||
export const ENTRY_DESCRIPTION_INPUT_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryDescriptionInputLabel',
|
||||
{
|
||||
defaultMessage: 'Description',
|
||||
defaultMessage: 'Data Description',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTRY_DESCRIPTION_HELP_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryDescriptionHelpLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'A description of the type of data in this index and/or when the assistant should look for data here.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTRY_QUERY_DESCRIPTION_INPUT_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryQueryDescriptionInputLabel',
|
||||
{
|
||||
defaultMessage: 'Query Description',
|
||||
defaultMessage: 'Query Instruction',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTRY_QUERY_DESCRIPTION_HELP_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryQueryDescriptionHelpLabel',
|
||||
{
|
||||
defaultMessage: 'Any instructions for extracting the search query from the user request.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTRY_OUTPUT_FIELDS_INPUT_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryOutputFieldsInputLabel',
|
||||
{
|
||||
defaultMessage: 'Output Fields',
|
||||
}
|
||||
);
|
||||
|
||||
export const ENTRY_OUTPUT_FIELDS_HELP_LABEL = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryOutputFieldsHelpLabel',
|
||||
{
|
||||
defaultMessage:
|
||||
'What fields should be sent to the LLM. Leave empty to send the entire document.',
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -269,6 +299,13 @@ export const ENTRY_INPUT_PLACEHOLDER = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const ENTRY_FIELD_PLACEHOLDER = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.entryFieldPlaceholder',
|
||||
{
|
||||
defaultMessage: 'semantic_text',
|
||||
}
|
||||
);
|
||||
|
||||
export const KNOWLEDGE_BASE_DOCUMENTATION = i18n.translate(
|
||||
'xpack.elasticAssistant.assistant.settings.knowledgeBaseSettingsManagement.knowledgeBaseDocumentation',
|
||||
{
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiAvatar, EuiBadge, EuiBasicTableColumn, EuiIcon, EuiLink, EuiText } from '@elastic/eui';
|
||||
import { EuiAvatar, EuiBadge, EuiBasicTableColumn, EuiIcon, EuiText } from '@elastic/eui';
|
||||
import { css } from '@emotion/react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { FormattedDate } from '@kbn/i18n-react';
|
||||
|
@ -32,7 +32,7 @@ export const useKnowledgeBaseTable = () => {
|
|||
if (['esql', 'security_labs'].includes(entry.kbResource)) {
|
||||
return 'logoElastic';
|
||||
}
|
||||
return 'visText';
|
||||
return 'document';
|
||||
} else if (entry.type === IndexEntryType.value) {
|
||||
return 'index';
|
||||
}
|
||||
|
@ -61,9 +61,7 @@ export const useKnowledgeBaseTable = () => {
|
|||
},
|
||||
{
|
||||
name: i18n.COLUMN_NAME,
|
||||
render: (entry: KnowledgeBaseEntryResponse) => (
|
||||
<EuiLink onClick={() => onEntryNameClicked(entry)}>{entry.name}</EuiLink>
|
||||
),
|
||||
render: ({ name }: KnowledgeBaseEntryResponse) => name,
|
||||
sortable: ({ name }: KnowledgeBaseEntryResponse) => name,
|
||||
width: '30%',
|
||||
},
|
||||
|
|
|
@ -30,5 +30,6 @@
|
|||
"@kbn/core-doc-links-browser",
|
||||
"@kbn/core",
|
||||
"@kbn/zod",
|
||||
"@kbn/data-views-plugin",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -12,10 +12,11 @@ import {
|
|||
DocumentEntryCreateFields,
|
||||
KnowledgeBaseEntryCreateProps,
|
||||
KnowledgeBaseEntryResponse,
|
||||
KnowledgeBaseEntryUpdateProps,
|
||||
Metadata,
|
||||
} from '@kbn/elastic-assistant-common';
|
||||
import { getKnowledgeBaseEntry } from './get_knowledge_base_entry';
|
||||
import { CreateKnowledgeBaseEntrySchema } from './types';
|
||||
import { CreateKnowledgeBaseEntrySchema, UpdateKnowledgeBaseEntrySchema } from './types';
|
||||
|
||||
export interface CreateKnowledgeBaseEntryParams {
|
||||
esClient: ElasticsearchClient;
|
||||
|
@ -77,6 +78,111 @@ export const createKnowledgeBaseEntry = async ({
|
|||
}
|
||||
};
|
||||
|
||||
interface TransformToUpdateSchemaProps {
|
||||
user: AuthenticatedUser;
|
||||
updatedAt: string;
|
||||
entry: KnowledgeBaseEntryUpdateProps;
|
||||
global?: boolean;
|
||||
}
|
||||
|
||||
export const transformToUpdateSchema = ({
|
||||
user,
|
||||
updatedAt,
|
||||
entry,
|
||||
global = false,
|
||||
}: TransformToUpdateSchemaProps): UpdateKnowledgeBaseEntrySchema => {
|
||||
const base = {
|
||||
id: entry.id,
|
||||
updated_at: updatedAt,
|
||||
updated_by: user.profile_uid ?? 'unknown',
|
||||
name: entry.name,
|
||||
type: entry.type,
|
||||
users: global
|
||||
? []
|
||||
: [
|
||||
{
|
||||
id: user.profile_uid,
|
||||
name: user.username,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
if (entry.type === 'index') {
|
||||
const { inputSchema, outputFields, queryDescription, ...restEntry } = entry;
|
||||
return {
|
||||
...base,
|
||||
...restEntry,
|
||||
query_description: queryDescription,
|
||||
input_schema:
|
||||
entry.inputSchema?.map((schema) => ({
|
||||
field_name: schema.fieldName,
|
||||
field_type: schema.fieldType,
|
||||
description: schema.description,
|
||||
})) ?? undefined,
|
||||
output_fields: outputFields ?? undefined,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...base,
|
||||
kb_resource: entry.kbResource,
|
||||
required: entry.required ?? false,
|
||||
source: entry.source,
|
||||
text: entry.text,
|
||||
vector: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const getUpdateScript = ({
|
||||
entry,
|
||||
isPatch,
|
||||
}: {
|
||||
entry: UpdateKnowledgeBaseEntrySchema;
|
||||
isPatch?: boolean;
|
||||
}) => {
|
||||
return {
|
||||
source: `
|
||||
if (params.assignEmpty == true || params.containsKey('name')) {
|
||||
ctx._source.name = params.name;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('type')) {
|
||||
ctx._source.type = params.type;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('users')) {
|
||||
ctx._source.users = params.users;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('query_description')) {
|
||||
ctx._source.query_description = params.query_description;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('input_schema')) {
|
||||
ctx._source.input_schema = params.input_schema;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('output_fields')) {
|
||||
ctx._source.output_fields = params.output_fields;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('kb_resource')) {
|
||||
ctx._source.kb_resource = params.kb_resource;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('required')) {
|
||||
ctx._source.required = params.required;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('source')) {
|
||||
ctx._source.source = params.source;
|
||||
}
|
||||
if (params.assignEmpty == true || params.containsKey('text')) {
|
||||
ctx._source.text = params.text;
|
||||
}
|
||||
ctx._source.updated_at = params.updated_at;
|
||||
ctx._source.updated_by = params.updated_by;
|
||||
`,
|
||||
lang: 'painless',
|
||||
params: {
|
||||
...entry, // when assigning undefined in painless, it will remove property and wil set it to null
|
||||
// for patch we don't want to remove unspecified value in payload
|
||||
assignEmpty: !(isPatch ?? true),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface TransformToCreateSchemaProps {
|
||||
createdAt: string;
|
||||
spaceId: string;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
import { z } from '@kbn/zod';
|
||||
import { get } from 'lodash';
|
||||
import { DynamicStructuredTool } from '@langchain/core/tools';
|
||||
import { errors } from '@elastic/elasticsearch';
|
||||
import { QueryDslQueryContainer, SearchRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
@ -189,7 +190,7 @@ export const getStructuredToolForIndexEntry = ({
|
|||
standard: {
|
||||
query: {
|
||||
nested: {
|
||||
path: 'semantic_text.inference.chunks',
|
||||
path: `${indexEntry.field}.inference.chunks`,
|
||||
query: {
|
||||
sparse_vector: {
|
||||
inference_id: elserId,
|
||||
|
@ -220,7 +221,7 @@ export const getStructuredToolForIndexEntry = ({
|
|||
}, {});
|
||||
}
|
||||
return {
|
||||
text: (hit._source as { text: string }).text,
|
||||
text: get(hit._source, `${indexEntry.field}.inference.chunks[0].text`),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ import { Document } from 'langchain/document';
|
|||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import {
|
||||
DocumentEntryType,
|
||||
DocumentEntry,
|
||||
IndexEntry,
|
||||
KnowledgeBaseEntryCreateProps,
|
||||
KnowledgeBaseEntryResponse,
|
||||
|
@ -444,7 +445,9 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
);
|
||||
this.options.logger.debug(
|
||||
() =>
|
||||
`getKnowledgeBaseDocuments() - Similarity Search Results:\n ${JSON.stringify(results)}`
|
||||
`getKnowledgeBaseDocuments() - Similarity Search returned [${JSON.stringify(
|
||||
results.length
|
||||
)}] results`
|
||||
);
|
||||
|
||||
return results;
|
||||
|
@ -454,6 +457,47 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns all global and current user's private `required` document entries.
|
||||
*/
|
||||
public getRequiredKnowledgeBaseDocumentEntries = async (): Promise<DocumentEntry[]> => {
|
||||
const user = this.options.currentUser;
|
||||
if (user == null) {
|
||||
throw new Error(
|
||||
'Authenticated user not found! Ensure kbDataClient was initialized from a request.'
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const userFilter = getKBUserFilter(user);
|
||||
const results = await this.findDocuments<EsIndexEntry>({
|
||||
// Note: This is a magic number to set some upward bound as to not blow the context with too
|
||||
// many historical KB entries. Ideally we'd query for all and token trim.
|
||||
perPage: 100,
|
||||
page: 1,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
filter: `${userFilter} AND type:document AND kb_resource:user AND required:true`,
|
||||
});
|
||||
this.options.logger.debug(
|
||||
`kbDataClient.getRequiredKnowledgeBaseDocumentEntries() - results:\n${JSON.stringify(
|
||||
results
|
||||
)}`
|
||||
);
|
||||
|
||||
if (results) {
|
||||
return transformESSearchToKnowledgeBaseEntry(results.data) as DocumentEntry[];
|
||||
}
|
||||
} catch (e) {
|
||||
this.options.logger.error(
|
||||
`kbDataClient.getRequiredKnowledgeBaseDocumentEntries() - Failed to fetch DocumentEntries`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new Knowledge Base Entry.
|
||||
*
|
||||
|
@ -492,7 +536,10 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
};
|
||||
|
||||
/**
|
||||
* Returns AssistantTools for any 'relevant' KB IndexEntries that exist in the knowledge base
|
||||
* Returns AssistantTools for any 'relevant' KB IndexEntries that exist in the knowledge base.
|
||||
*
|
||||
* Note: Accepts esClient so retrieval can be scoped to the current user as esClient on kbDataClient
|
||||
* is scoped to system user.
|
||||
*/
|
||||
public getAssistantTools = async ({
|
||||
assistantToolParams,
|
||||
|
@ -520,7 +567,7 @@ export class AIAssistantKnowledgeBaseDataClient extends AIAssistantDataClient {
|
|||
page: 1,
|
||||
sortField: 'created_at',
|
||||
sortOrder: 'asc',
|
||||
filter: `${userFilter}${` AND type:index`}`, // TODO: Support global tools (no user filter), and filter by space as well
|
||||
filter: `${userFilter} AND type:index`,
|
||||
});
|
||||
this.options.logger.debug(
|
||||
`kbDataClient.getAssistantTools() - results:\n${JSON.stringify(results, null, 2)}`
|
||||
|
|
|
@ -82,6 +82,39 @@ export interface LegacyEsKnowledgeBaseEntrySchema {
|
|||
model_id: string;
|
||||
};
|
||||
}
|
||||
export interface UpdateKnowledgeBaseEntrySchema {
|
||||
id: string;
|
||||
created_at?: string;
|
||||
created_by?: string;
|
||||
updated_at?: string;
|
||||
updated_by?: string;
|
||||
users?: Array<{
|
||||
id?: string;
|
||||
name?: string;
|
||||
}>;
|
||||
name?: string;
|
||||
type?: string;
|
||||
// Document Entry Fields
|
||||
kb_resource?: string;
|
||||
required?: boolean;
|
||||
source?: string;
|
||||
text?: string;
|
||||
vector?: {
|
||||
tokens: Record<string, number>;
|
||||
model_id: string;
|
||||
};
|
||||
// Index Entry Fields
|
||||
index?: string;
|
||||
field?: string;
|
||||
description?: string;
|
||||
query_description?: string;
|
||||
input_schema?: Array<{
|
||||
field_name: string;
|
||||
field_type: string;
|
||||
description: string;
|
||||
}>;
|
||||
output_fields?: string[];
|
||||
}
|
||||
|
||||
export interface CreateKnowledgeBaseEntrySchema {
|
||||
'@timestamp'?: string;
|
||||
|
|
|
@ -84,6 +84,7 @@ export class AIAssistantService {
|
|||
private isKBSetupInProgress: boolean = false;
|
||||
// Temporary 'feature flag' to determine if we should initialize the new kb mappings, toggled when accessing kbDataClient
|
||||
private v2KnowledgeBaseEnabled: boolean = false;
|
||||
private hasInitializedV2KnowledgeBase: boolean = false;
|
||||
|
||||
constructor(private readonly options: AIAssistantServiceOpts) {
|
||||
this.initialized = false;
|
||||
|
@ -363,8 +364,13 @@ export class AIAssistantService {
|
|||
// If either v2 KB or a modelIdOverride is provided, we need to reinitialize all persistence resources to make sure
|
||||
// they're using the correct model/mappings. Technically all existing KB data is stale since it was created
|
||||
// with a different model/mappings, but modelIdOverride is only intended for testing purposes at this time
|
||||
if (opts.v2KnowledgeBaseEnabled || opts.modelIdOverride != null) {
|
||||
// Added hasInitializedV2KnowledgeBase to prevent the console noise from re-init on each KB request
|
||||
if (
|
||||
!this.hasInitializedV2KnowledgeBase &&
|
||||
(opts.v2KnowledgeBaseEnabled || opts.modelIdOverride != null)
|
||||
) {
|
||||
await this.initializeResources();
|
||||
this.hasInitializedV2KnowledgeBase = true;
|
||||
}
|
||||
|
||||
const res = await this.checkResourcesInstallation(opts);
|
||||
|
|
|
@ -141,7 +141,7 @@ export const getDefaultAssistantGraph = ({
|
|||
})
|
||||
)
|
||||
.addNode(NodeType.AGENT, (state: AgentState) =>
|
||||
runAgent({ ...nodeParams, state, agentRunnable })
|
||||
runAgent({ ...nodeParams, state, agentRunnable, kbDataClient: dataClients?.kbDataClient })
|
||||
)
|
||||
.addNode(NodeType.TOOLS, (state: AgentState) => executeTools({ ...nodeParams, state, tools }))
|
||||
.addNode(NodeType.RESPOND, (state: AgentState) =>
|
||||
|
|
|
@ -10,15 +10,20 @@ import { AgentRunnableSequence } from 'langchain/dist/agents/agent';
|
|||
import { formatLatestUserMessage } from '../prompts';
|
||||
import { AgentState, NodeParamsBase } from '../types';
|
||||
import { NodeType } from '../constants';
|
||||
import { AIAssistantKnowledgeBaseDataClient } from '../../../../../ai_assistant_data_clients/knowledge_base';
|
||||
|
||||
export interface RunAgentParams extends NodeParamsBase {
|
||||
state: AgentState;
|
||||
config?: RunnableConfig;
|
||||
agentRunnable: AgentRunnableSequence;
|
||||
kbDataClient?: AIAssistantKnowledgeBaseDataClient;
|
||||
}
|
||||
|
||||
export const AGENT_NODE_TAG = 'agent_run';
|
||||
|
||||
const KNOWLEDGE_HISTORY_PREFIX = 'Knowledge History:';
|
||||
const NO_KNOWLEDGE_HISTORY = '[No existing knowledge history]';
|
||||
|
||||
/**
|
||||
* Node to run the agent
|
||||
*
|
||||
|
@ -26,18 +31,27 @@ export const AGENT_NODE_TAG = 'agent_run';
|
|||
* @param state - The current state of the graph
|
||||
* @param config - Any configuration that may've been supplied
|
||||
* @param agentRunnable - The agent to run
|
||||
* @param kbDataClient - Data client for accessing the Knowledge Base on behalf of the current user
|
||||
*/
|
||||
export async function runAgent({
|
||||
logger,
|
||||
state,
|
||||
agentRunnable,
|
||||
config,
|
||||
kbDataClient,
|
||||
}: RunAgentParams): Promise<Partial<AgentState>> {
|
||||
logger.debug(() => `${NodeType.AGENT}: Node state:\n${JSON.stringify(state, null, 2)}`);
|
||||
|
||||
const knowledgeHistory = await kbDataClient?.getRequiredKnowledgeBaseDocumentEntries();
|
||||
|
||||
const agentOutcome = await agentRunnable.withConfig({ tags: [AGENT_NODE_TAG] }).invoke(
|
||||
{
|
||||
...state,
|
||||
knowledge_history: `${KNOWLEDGE_HISTORY_PREFIX}\n${
|
||||
knowledgeHistory?.length
|
||||
? JSON.stringify(knowledgeHistory.map((e) => e.text))
|
||||
: NO_KNOWLEDGE_HISTORY
|
||||
}`,
|
||||
// prepend any user prompt (gemini)
|
||||
input: formatLatestUserMessage(state.input, state.llmType),
|
||||
chat_history: state.messages, // TODO: Message de-dupe with ...state spread
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
const YOU_ARE_A_HELPFUL_EXPERT_ASSISTANT =
|
||||
'You are a security analyst and expert in resolving security incidents. Your role is to assist by answering questions about Elastic Security.';
|
||||
const IF_YOU_DONT_KNOW_THE_ANSWER = 'Do not answer questions unrelated to Elastic Security.';
|
||||
export const KNOWLEDGE_HISTORY =
|
||||
'If available, use the Knowledge History provided to try and answer the question. If not provided, you can try and query for additional knowledge via the KnowledgeBaseRetrievalTool.';
|
||||
|
||||
export const DEFAULT_SYSTEM_PROMPT = `${YOU_ARE_A_HELPFUL_EXPERT_ASSISTANT} ${IF_YOU_DONT_KNOW_THE_ANSWER}`;
|
||||
export const DEFAULT_SYSTEM_PROMPT = `${YOU_ARE_A_HELPFUL_EXPERT_ASSISTANT} ${IF_YOU_DONT_KNOW_THE_ANSWER} ${KNOWLEDGE_HISTORY}`;
|
||||
// system prompt from @afirstenberg
|
||||
const BASE_GEMINI_PROMPT =
|
||||
'You are an assistant that is an expert at using tools and Elastic Security, doing your best to use these tools to answer questions or follow instructions. It is very important to use tools to answer the question or follow the instructions rather than coming up with your own answer. Tool calls are good. Sometimes you may need to make several tool calls to accomplish the task or get an answer to the question that was asked. Use as many tool calls as necessary.';
|
||||
|
@ -19,7 +21,7 @@ export const GEMINI_SYSTEM_PROMPT = `${BASE_GEMINI_PROMPT} ${KB_CATCH}`;
|
|||
export const BEDROCK_SYSTEM_PROMPT = `Use tools as often as possible, as they have access to the latest data and syntax. Always return value from ESQLKnowledgeBaseTool as is. Never return <thinking> tags in the response, but make sure to include <result> tags content in the response. Do not reflect on the quality of the returned search results in your response.`;
|
||||
export const GEMINI_USER_PROMPT = `Now, always using the tools at your disposal, step by step, come up with a response to this request:\n\n`;
|
||||
|
||||
export const STRUCTURED_SYSTEM_PROMPT = `Respond to the human as helpfully and accurately as possible. You have access to the following tools:
|
||||
export const STRUCTURED_SYSTEM_PROMPT = `Respond to the human as helpfully and accurately as possible. ${KNOWLEDGE_HISTORY} You have access to the following tools:
|
||||
|
||||
{tools}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
export const formatPrompt = (prompt: string, additionalPrompt?: string) =>
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', additionalPrompt ? `${prompt}\n\n${additionalPrompt}` : prompt],
|
||||
['placeholder', '{knowledge_history}'],
|
||||
['placeholder', '{chat_history}'],
|
||||
['human', '{input}'],
|
||||
['placeholder', '{agent_scratchpad}'],
|
||||
|
@ -39,6 +40,7 @@ export const geminiToolCallingAgentPrompt = formatPrompt(systemPrompts.gemini);
|
|||
export const formatPromptStructured = (prompt: string, additionalPrompt?: string) =>
|
||||
ChatPromptTemplate.fromMessages([
|
||||
['system', additionalPrompt ? `${prompt}\n\n${additionalPrompt}` : prompt],
|
||||
['placeholder', '{knowledge_history}'],
|
||||
['placeholder', '{chat_history}'],
|
||||
[
|
||||
'human',
|
||||
|
|
|
@ -22,11 +22,18 @@ import { buildRouteValidationWithZod } from '@kbn/elastic-assistant-common/impl/
|
|||
|
||||
import { performChecks } from '../../helpers';
|
||||
import { KNOWLEDGE_BASE_ENTRIES_TABLE_MAX_PAGE_SIZE } from '../../../../common/constants';
|
||||
import { EsKnowledgeBaseEntrySchema } from '../../../ai_assistant_data_clients/knowledge_base/types';
|
||||
import {
|
||||
EsKnowledgeBaseEntrySchema,
|
||||
UpdateKnowledgeBaseEntrySchema,
|
||||
} from '../../../ai_assistant_data_clients/knowledge_base/types';
|
||||
import { ElasticAssistantPluginRouter } from '../../../types';
|
||||
import { buildResponse } from '../../utils';
|
||||
import { transformESSearchToKnowledgeBaseEntry } from '../../../ai_assistant_data_clients/knowledge_base/transforms';
|
||||
import { transformToCreateSchema } from '../../../ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry';
|
||||
import {
|
||||
getUpdateScript,
|
||||
transformToCreateSchema,
|
||||
transformToUpdateSchema,
|
||||
} from '../../../ai_assistant_data_clients/knowledge_base/create_knowledge_base_entry';
|
||||
|
||||
export interface BulkOperationError {
|
||||
message: string;
|
||||
|
@ -210,7 +217,17 @@ export const bulkActionKnowledgeBaseEntriesRoute = (router: ElasticAssistantPlug
|
|||
})
|
||||
),
|
||||
documentsToDelete: body.delete?.ids,
|
||||
documentsToUpdate: [], // TODO: Support bulk update
|
||||
documentsToUpdate: body.update?.map((entry) =>
|
||||
// TODO: KB-RBAC check, required when users != null as entry will either be created globally if empty
|
||||
transformToUpdateSchema({
|
||||
user: authenticatedUser,
|
||||
updatedAt: changedAt,
|
||||
entry,
|
||||
global: entry.users != null && entry.users.length === 0,
|
||||
})
|
||||
),
|
||||
getUpdateScript: (entry: UpdateKnowledgeBaseEntrySchema) =>
|
||||
getUpdateScript({ entry, isPatch: true }),
|
||||
authenticatedUser,
|
||||
});
|
||||
const created =
|
||||
|
|
|
@ -66,7 +66,7 @@ export const createKnowledgeBaseEntryRoute = (router: ElasticAssistantPluginRout
|
|||
logger.debug(() => `Creating KB Entry:\n${JSON.stringify(request.body)}`);
|
||||
const createResponse = await kbDataClient?.createKnowledgeBaseEntry({
|
||||
knowledgeBaseEntry: request.body,
|
||||
// TODO: KB-RBAC check, required when users != null as entry will either be created globally if empty, or for specific users (only admin API feature)
|
||||
// TODO: KB-RBAC check, required when users != null as entry will either be created globally if empty
|
||||
global: request.body.users != null && request.body.users.length === 0,
|
||||
});
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ export const findKnowledgeBaseEntriesRoute = (router: ElasticAssistantPluginRout
|
|||
});
|
||||
const currentUser = ctx.elasticAssistant.getCurrentUser();
|
||||
const userFilter = getKBUserFilter(currentUser);
|
||||
const systemFilter = ` AND kb_resource:"user"`;
|
||||
const systemFilter = ` AND (kb_resource:"user" OR type:"index")`;
|
||||
const additionalFilter = query.filter ? ` AND ${query.filter}` : '';
|
||||
|
||||
const result = await kbDataClient?.findDocuments<EsKnowledgeBaseEntrySchema>({
|
||||
|
@ -166,7 +166,7 @@ export const findKnowledgeBaseEntriesRoute = (router: ElasticAssistantPluginRout
|
|||
body: {
|
||||
perPage: result.perPage,
|
||||
page: result.page,
|
||||
total: result.total,
|
||||
total: result.total + systemEntries.length,
|
||||
data: [...transformESSearchToKnowledgeBaseEntry(result.data), ...systemEntries],
|
||||
},
|
||||
});
|
||||
|
|
|
@ -77,6 +77,11 @@ describe('ManagementSettings', () => {
|
|||
securitySolutionAssistant: { 'ai-assistant': false },
|
||||
},
|
||||
},
|
||||
data: {
|
||||
dataViews: {
|
||||
getIndices: jest.fn(),
|
||||
},
|
||||
},
|
||||
security: {
|
||||
userProfiles: {
|
||||
getCurrent: jest.fn().mockResolvedValue({ data: { color: 'blue', initials: 'P' } }),
|
||||
|
|
|
@ -37,6 +37,7 @@ export const ManagementSettings = React.memo(() => {
|
|||
securitySolutionAssistant: { 'ai-assistant': securityAIAssistantEnabled },
|
||||
},
|
||||
},
|
||||
data: { dataViews },
|
||||
security,
|
||||
} = useKibana().services;
|
||||
|
||||
|
@ -46,8 +47,8 @@ export const ManagementSettings = React.memo(() => {
|
|||
security?.userProfiles.getCurrent<{ avatar: UserAvatar }>({
|
||||
dataPath: 'avatar',
|
||||
}),
|
||||
select: (data) => {
|
||||
return data.data.avatar;
|
||||
select: (d) => {
|
||||
return d.data.avatar;
|
||||
},
|
||||
keepPreviousData: true,
|
||||
refetchOnWindowFocus: false,
|
||||
|
@ -79,7 +80,12 @@ export const ManagementSettings = React.memo(() => {
|
|||
}
|
||||
|
||||
if (conversations) {
|
||||
return <AssistantSettingsManagement selectedConversation={currentConversation} />;
|
||||
return (
|
||||
<AssistantSettingsManagement
|
||||
selectedConversation={currentConversation}
|
||||
dataViews={dataViews}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <></>;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue