diff --git a/common/types/chat_saved_object_attributes.ts b/common/types/chat_saved_object_attributes.ts
index 5f18c51f..6984bca1 100644
--- a/common/types/chat_saved_object_attributes.ts
+++ b/common/types/chat_saved_object_attributes.ts
@@ -52,19 +52,22 @@ export interface IOutput {
toolsUsed?: string[];
contentType: 'error' | 'markdown' | 'visualization' | string;
content: string;
+ additionalActions?: IAdditionalAction[];
suggestedActions?: ISuggestedAction[];
messageId?: string;
fullWidth?: boolean;
}
export type IMessage = IInput | IOutput;
-interface ISuggestedActionBase {
+interface IActionBase {
actionType: string;
message: string;
}
-export type ISuggestedAction = ISuggestedActionBase &
+export type ISuggestedAction = IActionBase &
(
- | { actionType: 'send_as_input' | 'copy' | 'view_in_dashboards' }
+ | {
+ actionType: 'send_as_input' | 'copy' | 'view_in_dashboards' | 'create_monitor_in_dashboard';
+ }
| {
actionType: 'view_ppl_visualization';
metadata: { query: string; question: string };
@@ -74,6 +77,12 @@ export type ISuggestedAction = ISuggestedActionBase &
metadata: { interactionId: string };
}
);
+
+export type IAdditionalAction = IActionBase & {
+ actionType: 'create_monitor_grid';
+ content: string;
+};
+
export interface SendFeedbackBody {
satisfaction: boolean;
}
diff --git a/public/chat_flyout.tsx b/public/chat_flyout.tsx
index a4a76658..17a2f6f5 100644
--- a/public/chat_flyout.tsx
+++ b/public/chat_flyout.tsx
@@ -12,6 +12,7 @@ import { ChatWindowHeader } from './tabs/chat_window_header';
import { ChatHistoryPage } from './tabs/history/chat_history_page';
import { AgentFrameworkTracesFlyoutBody } from './components/agent_framework_traces_flyout_body';
import { TAB_ID } from './utils/constants';
+import { ChatOverrideHeader } from './tabs/chat_override_header';
interface ChatFlyoutProps {
flyoutVisible: boolean;
@@ -86,52 +87,55 @@ export const ChatFlyout = (props: ChatFlyoutProps) => {
>
<>
-
+ {props.overrideComponent ? : }
- {props.overrideComponent}
-
- {(Panel, Resizer) => (
- <>
-
-
-
+ {props.overrideComponent ? (
+ props.overrideComponent
+ ) : (
+
+ {(Panel, Resizer) => (
<>
- {resizable && }
- {chatHistoryPageLoadedRef.current && (
-
- )}
- {chatTraceVisible && chatContext.interactionId && (
-
- )}
+ {}
+ <>
+ {resizable && }
+
+ {chatHistoryPageLoadedRef.current && (
+
+ )}
+ {chatTraceVisible && chatContext.interactionId && (
+
+ )}
+
+ >
>
- >
- )}
-
+ )}
+
+ )}
>
);
diff --git a/public/chat_header_button.tsx b/public/chat_header_button.tsx
index 2f83e388..03d7f5d0 100644
--- a/public/chat_header_button.tsx
+++ b/public/chat_header_button.tsx
@@ -18,12 +18,20 @@ import { ChatStateProvider } from './hooks';
import './index.scss';
import { ActionExecutor, AssistantActions, MessageRenderer, TabId, UserAccount } from './types';
import {
- TAB_ID,
DEFAULT_SIDECAR_DOCKED_MODE,
DEFAULT_SIDECAR_LEFT_OR_RIGHT_SIZE,
+ OVERRIDE_SIDECAR_LEFT_OR_RIGHT_SIZE,
+ TAB_ID,
} from './utils/constants';
import { useCore } from './contexts/core_context';
import { MountPointPortal } from '../../../src/plugins/opensearch_dashboards_react/public';
+import { getStateFromOsdUrl } from '../../../src/plugins/opensearch_dashboards_utils/public';
+import {
+ DiscoverRootState,
+ DiscoverState,
+} from '../../../src/plugins/discover/public/application/utils/state_management';
+import { IndexPatternAttributes } from '../../../src/plugins/data/common';
+import { SavedSearch } from '../../../src/plugins/discover/public';
interface HeaderChatButtonProps {
application: ApplicationStart;
@@ -41,6 +49,7 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => {
const [conversationId, setConversationId] = useState();
const [title, setTitle] = useState();
const [flyoutVisible, setFlyoutVisible] = useState(false);
+ const [overrideName, setOverrideName] = useState();
const [flyoutComponent, setFlyoutComponent] = useState(null);
const [selectedTabId, setSelectedTabId] = useState(TAB_ID.CHAT);
const [preSelectedTabId, setPreSelectedTabId] = useState(undefined);
@@ -74,6 +83,8 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => {
flyoutFullScreen,
setFlyoutVisible,
setFlyoutComponent,
+ overrideName,
+ setOverrideName,
userHasAccess: props.userHasAccess,
messageRenderers: props.messageRenderers,
actionExecutors: props.actionExecutors,
@@ -156,6 +167,17 @@ export const HeaderChatButton = (props: HeaderChatButtonProps) => {
flyoutMountPoint.current = mountPoint;
}, []);
+ useEffect(() => {
+ if (flyoutLoaded && flyoutVisible) {
+ core.overlays.sidecar().setSidecarConfig({
+ paddingSize:
+ selectedTabId === TAB_ID.OVERRIDE
+ ? OVERRIDE_SIDECAR_LEFT_OR_RIGHT_SIZE
+ : DEFAULT_SIDECAR_LEFT_OR_RIGHT_SIZE,
+ });
+ }
+ }, [selectedTabId === TAB_ID.OVERRIDE]);
+
useEffect(() => {
if (!props.userHasAccess) {
return;
diff --git a/public/contexts/chat_context.tsx b/public/contexts/chat_context.tsx
index f7bc516c..68053822 100644
--- a/public/contexts/chat_context.tsx
+++ b/public/contexts/chat_context.tsx
@@ -18,6 +18,8 @@ export interface IChatContext {
flyoutFullScreen: boolean;
setFlyoutVisible: React.Dispatch>;
setFlyoutComponent: React.Dispatch>;
+ overrideName?: string;
+ setOverrideName: React.Dispatch>;
userHasAccess: boolean;
messageRenderers: Record;
actionExecutors: Record;
@@ -28,6 +30,8 @@ export interface IChatContext {
setInteractionId: React.Dispatch>;
sidecarDockedMode: ISidecarConfig['dockedMode'];
setSidecarDockedMode: React.Dispatch>;
+ tabRenderer?: React.ReactNode;
+ setTabRenderer?: React.Dispatch>;
}
export const ChatContext = React.createContext(null);
diff --git a/public/index.scss b/public/index.scss
index 733b86f4..e9e4abd2 100644
--- a/public/index.scss
+++ b/public/index.scss
@@ -68,6 +68,8 @@
.llm-chat-flyout {
height: 100%;
+ display: flex;
+ flex-direction: column;
.euiFlyoutFooter {
background: transparent;
}
diff --git a/public/tabs/chat/messages/message_content.tsx b/public/tabs/chat/messages/message_content.tsx
index 5b0be173..90369229 100644
--- a/public/tabs/chat/messages/message_content.tsx
+++ b/public/tabs/chat/messages/message_content.tsx
@@ -3,11 +3,12 @@
* SPDX-License-Identifier: Apache-2.0
*/
-import { EuiMarkdownFormat, EuiText } from '@elastic/eui';
+import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiMarkdownFormat, EuiText } from '@elastic/eui';
import React from 'react';
import { IMessage } from '../../../../common/types/chat_saved_object_attributes';
import { CoreVisualization } from '../../../components/core_visualization';
import { useChatContext } from '../../../contexts/chat_context';
+import { TAB_ID } from '../../../utils/constants';
export interface MessageContentProps {
message: IMessage;
@@ -28,7 +29,42 @@ export const MessageContent: React.FC = React.memo((props)
);
case 'markdown':
- return {props.message.content};
+ return (
+ <>
+ {props.message.content}
+ {props.message.additionalActions &&
+ props.message.additionalActions.map((action, index) => (
+
+
+ {
+ if (chatContext) {
+ if (chatContext.selectedTabId !== TAB_ID.OVERRIDE) {
+ chatContext.setSelectedTabId(TAB_ID.OVERRIDE);
+ }
+ const actionMessage: IMessage = {
+ type: 'output',
+ contentType: action.actionType,
+ content: action.content,
+ };
+ const component = chatContext.messageRenderers[
+ action.actionType
+ ]?.(actionMessage, { props: { message: actionMessage }, chatContext });
+ chatContext.setFlyoutComponent(component);
+ chatContext.setOverrideName('Create Alert');
+ }
+ }}
+ fill
+ isLoading={false}
+ disabled={false}
+ >
+ Create monitor
+
+
+
+ ))}
+ >
+ );
case 'visualization':
return (
diff --git a/public/tabs/chat_override_header.tsx b/public/tabs/chat_override_header.tsx
new file mode 100644
index 00000000..b9a92871
--- /dev/null
+++ b/public/tabs/chat_override_header.tsx
@@ -0,0 +1,53 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import { EuiButtonEmpty, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
+import React, { useCallback } from 'react';
+import { IChatContext, useChatContext } from '../contexts/chat_context';
+import { TAB_ID } from '../utils/constants';
+import { SidecarIconMenu } from '../components/sidecar_icon_menu';
+
+export const ChatOverrideHeader = React.memo(() => {
+ const chatContext = useChatContext() as IChatContext;
+ const { setSelectedTabId, setFlyoutComponent, setOverrideName } = chatContext;
+
+ const handleBack = useCallback(() => {
+ setSelectedTabId(TAB_ID.CHAT);
+ setFlyoutComponent(null);
+ setOverrideName(undefined);
+ }, [setSelectedTabId]);
+
+ return (
+ <>
+
+
+
+
+ {chatContext?.overrideName || 'Back'}
+
+
+
+
+
+ {
+ chatContext.setFlyoutVisible(false);
+ }}
+ />
+
+
+
+ >
+ );
+});
diff --git a/public/types.ts b/public/types.ts
index f4bde66f..c88ccf51 100644
--- a/public/types.ts
+++ b/public/types.ts
@@ -9,6 +9,7 @@ import { IMessage, ISuggestedAction } from '../common/types/chat_saved_object_at
import { IChatContext } from './contexts/chat_context';
import { MessageContentProps } from './tabs/chat/messages/message_content';
import { IncontextInsightRegistry } from './services';
+import { TAB_ID } from './utils/constants';
export interface RenderProps {
props: MessageContentProps;
@@ -83,4 +84,4 @@ export type IncontextInsightType =
| 'chatWithSuggestions'
| 'error';
-export type TabId = 'chat' | 'compose' | 'insights' | 'history' | 'trace';
+export type TabId = TAB_ID;
diff --git a/public/utils/constants.ts b/public/utils/constants.ts
index 44978b4c..50025af5 100644
--- a/public/utils/constants.ts
+++ b/public/utils/constants.ts
@@ -10,9 +10,11 @@ export enum TAB_ID {
INSIGHTS = 'insights',
HISTORY = 'history',
TRACE = 'trace',
+ OVERRIDE = 'override',
}
export const DEFAULT_SIDECAR_DOCKED_MODE = SIDECAR_DOCKED_MODE.RIGHT;
export const DEFAULT_SIDECAR_LEFT_OR_RIGHT_SIZE = 460;
+export const OVERRIDE_SIDECAR_LEFT_OR_RIGHT_SIZE = 570;
// this is a default padding top size for sidecar when switching to takeover
export const DEFAULT_SIDECAR_TAKEOVER_PADDING_TOP_SIZE = 136;
diff --git a/server/parsers/ParserHelper.ts b/server/parsers/ParserHelper.ts
new file mode 100644
index 00000000..ef502dce
--- /dev/null
+++ b/server/parsers/ParserHelper.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import Qs from 'querystring';
+import {
+ IAdditionalAction,
+ IMessage,
+ Interaction,
+} from '../../common/types/chat_saved_object_attributes';
+
+export const CreateMonitorParserHelper = (interaction: Interaction): IAdditionalAction[] => {
+ const monitorParameters =
+ (interaction.additional_info?.['CreateAlertTool.output'] as string[] | null)?.flatMap(
+ (item: string): {} => {
+ // @typescript-eslint/no-explicit-any
+ let parameters: { [key: string]: string } = {};
+ try {
+ const parsedItem = JSON.parse(item);
+ parameters.name = parsedItem.name;
+ parameters.index = parsedItem.search.indices;
+ parameters.timeField = parsedItem.search.timeField;
+ parameters.bucketValue = parsedItem.search.bucketValue;
+ parameters.bucketUnitOfTime = parsedItem.search.bucketUnitOfTime;
+ parameters.filters = JSON.stringify(parsedItem.search.filters);
+ parameters.aggregations = JSON.stringify(parsedItem.search.aggregations);
+ parameters.triggers = JSON.stringify(parsedItem.triggers);
+ } catch (e) {
+ parameters = {};
+ }
+
+ return parameters;
+ }
+ ) || [];
+
+ if (!monitorParameters.length) return [];
+
+ return [...new Set(monitorParameters)]
+ .filter((parameters) => parameters)
+ .map((parameters) => ({
+ actionType: 'create_monitor_grid',
+ message: 'Create Alert',
+ content: Qs.stringify(parameters),
+ }));
+};
diff --git a/server/parsers/basic_input_output_parser.ts b/server/parsers/basic_input_output_parser.ts
index 257ba12e..da0519c2 100644
--- a/server/parsers/basic_input_output_parser.ts
+++ b/server/parsers/basic_input_output_parser.ts
@@ -7,6 +7,7 @@ import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
import { IInput, IOutput } from '../../common/types/chat_saved_object_attributes';
import { MessageParser } from '../types';
+import { CreateMonitorParserHelper } from './ParserHelper';
const sanitize = (content: string) => {
const window = new JSDOM('').window;
@@ -90,11 +91,18 @@ export const BasicInputOutputParser: MessageParser = {
contentType: 'text',
content: interaction.input,
};
+
+ // TODO: make it more general by using registration of all internal parsers.
+ const alertActions =
+ interaction.additional_info && 'CreateAlertTool.output' in interaction.additional_info
+ ? CreateMonitorParserHelper(interaction)
+ : undefined;
const outputItems: IOutput[] = [
{
type: 'output',
contentType: 'markdown',
content: sanitize(interaction.response),
+ additionalActions: alertActions,
interactionId: interaction.interaction_id,
suggestedActions: suggestedActions
.filter((item) => item)
diff --git a/server/parsers/create_monitor_parser.ts b/server/parsers/create_monitor_parser.ts
new file mode 100644
index 00000000..7a3f6496
--- /dev/null
+++ b/server/parsers/create_monitor_parser.ts
@@ -0,0 +1,55 @@
+/*
+ * Copyright OpenSearch Contributors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import Qs from 'querystring';
+import { IMessage } from '../../common/types/chat_saved_object_attributes';
+import { MessageParser } from '../types';
+
+export const CreateMonitorParsers: MessageParser = {
+ id: 'create_monitor_message',
+ async parserProvider(interaction) {
+ const monitorParameters =
+ (interaction.additional_info?.['CreateAlertTool.output'] as string[] | null)?.flatMap(
+ (item: string): {} => {
+ // @typescript-eslint/no-explicit-any
+ let parameters: { [key: string]: string } = {};
+ try {
+ const parsedItem = JSON.parse(item);
+ parameters.name = parsedItem.name;
+ parameters.index = parsedItem.search.indices;
+ parameters.timeField = parsedItem.search.timeField;
+ parameters.bucketValue = parsedItem.search.bucketValue;
+ parameters.bucketUnitOfTime = parsedItem.search.bucketUnitOfTime;
+ parameters.filters = JSON.stringify(parsedItem.search.filters);
+ parameters.aggregations = JSON.stringify(parsedItem.search.aggregations);
+ parameters.triggers = JSON.stringify(parsedItem.triggers);
+ } catch (e) {
+ parameters = {};
+ }
+
+ return parameters;
+ }
+ ) || [];
+
+ if (!monitorParameters.length) return [];
+
+ const createMonitorOutputs: IMessage[] = [...new Set(monitorParameters)]
+ .filter((parameters) => parameters)
+ .map((parameters) => ({
+ type: 'output',
+ content: Qs.stringify(parameters),
+ contentType: 'create_monitor_grid',
+ fullWidth: true,
+ suggestedActions: [
+ {
+ message: 'Create alert with AI suggested parameters in alerting page.',
+ actionType: 'create_monitor_in_dashboard',
+ },
+ ],
+ }));
+
+ return createMonitorOutputs;
+ },
+};