Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
3086847
Update AI connector title, description and buttons in Settings
viduni94 May 12, 2025
470285b
Update i18n
viduni94 May 12, 2025
fbfa315
Add manage connectors link to contextual insights
viduni94 May 12, 2025
b762e09
Minor update
viduni94 May 12, 2025
b8877e5
Use util function to navigate to connectors
viduni94 May 12, 2025
32b34fa
Fix test
viduni94 May 12, 2025
6f51140
Update import
viduni94 May 12, 2025
182405c
Fix import
viduni94 May 12, 2025
c2809c0
Add Elastic Managed LLM tour callout
viduni94 May 13, 2025
32d77fc
Add Elastic Managed LLM tour callout to contextual insights
viduni94 May 14, 2025
5722426
Add callout to chat
viduni94 May 14, 2025
92afd05
Make the chat callout sticky
viduni94 May 14, 2025
edbe667
Make the tour callout dismissable
viduni94 May 14, 2025
98bd4e3
Fix i18n
viduni94 May 14, 2025
bfcd519
Update i18n messages
viduni94 May 14, 2025
3d84f47
[CI] Auto-commit changed files from 'ts-node .buildkite/pipeline-reso…
kibanamachine May 14, 2025
65777b2
Fix test
viduni94 May 15, 2025
8222308
Add link to active space settings
viduni94 May 15, 2025
e20bfe7
[CI] Auto-commit changed files from 'node scripts/yarn_deduplicate'
kibanamachine May 15, 2025
1e0a444
Update links
viduni94 May 20, 2025
3e10a5c
Update how the callout is shown
viduni94 May 20, 2025
c00513d
Fix callout overlapping the flyout
viduni94 May 20, 2025
2b0e365
Enable callout only for one insight component if multiple components …
viduni94 May 21, 2025
89b66b1
Apply dismissed callout property to contextual insights
viduni94 May 23, 2025
03d25ac
Update font size and line height
viduni94 May 27, 2025
60c1c16
Update settings link to use application instead of href
viduni94 May 27, 2025
2e4c149
Update doc links
viduni94 May 27, 2025
6b5d64c
Merge branch 'main' into eis-pricing-transparency
viduni94 May 27, 2025
6ccbdb1
Merge branch 'main' into eis-pricing-transparency
viduni94 May 27, 2025
5a4f30a
Merge branch 'main' into eis-pricing-transparency
viduni94 May 28, 2025
419d100
Fix tests
viduni94 May 28, 2025
1d49d9c
Merge branch 'main' into eis-pricing-transparency
viduni94 May 30, 2025
cb3086d
Remove type annotations
viduni94 May 30, 2025
53379da
Use const for
viduni94 May 30, 2025
574e90e
Merge branch 'main' into eis-pricing-transparency
viduni94 Jun 2, 2025
dc8c3cb
add callout
flash1293 Jun 3, 2025
d3db7a3
remove existing callout
flash1293 Jun 3, 2025
687b94a
switch back to modal
flash1293 Jun 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,8 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
slo: `${ELASTIC_DOCS}solutions/observability/incident-management/service-level-objectives-slos`,
sloBurnRateRule: `${ELASTIC_DOCS}solutions/observability/incident-management/create-an-slo-burn-rate-rule`,
aiAssistant: `${ELASTIC_DOCS}solutions/observability/observability-ai-assistant`,
elasticManagedLlm: `${ELASTIC_DOCS}reference/kibana/connectors-kibana/elastic-managed-llm`,
elasticManagedLlmUsageCost: `${ELASTIC_WEBSITE_URL}pricing`,
},
alerting: {
guide: `${ELASTIC_DOCS}explore-analyze/alerts-cases/alerts/create-manage-rules`,
Expand Down
2 changes: 2 additions & 0 deletions src/platform/packages/shared/kbn-doc-links/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ export interface DocLinks {
slo: string;
sloBurnRateRule: string;
aiAssistant: string;
elasticManagedLlm: string;
elasticManagedLlmUsageCost: string;
}>;
readonly alerting: Readonly<{
authorization: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import {
EuiPopover,
EuiToolTip,
} from '@elastic/eui';
import { ConnectorSelectorBase } from '@kbn/observability-ai-assistant-plugin/public';
import {
ConnectorSelectorBase,
navigateToConnectorsManagementApp,
navigateToSettingsManagementApp,
} from '@kbn/observability-ai-assistant-plugin/public';
import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors';
import { useKibana } from '../hooks/use_kibana';
import { useKnowledgeBase } from '../hooks';
Expand All @@ -31,22 +35,10 @@ export function ChatActionsMenu({
const knowledgeBase = useKnowledgeBase();
const [isOpen, setIsOpen] = useState(false);

const handleNavigateToConnectors = () => {
application?.navigateToApp('management', {
path: '/insightsAndAlerting/triggersActionsConnectors/connectors',
});
};

const toggleActionsMenu = () => {
setIsOpen(!isOpen);
};

const handleNavigateToSettings = () => {
application?.navigateToUrl(
http!.basePath.prepend(`/app/management/kibana/observabilityAiAssistantManagement`)
);
};

const handleNavigateToSettingsKnowledgeBase = () => {
application?.navigateToUrl(
http!.basePath.prepend(
Expand Down Expand Up @@ -108,7 +100,7 @@ export function ChatActionsMenu({
}),
onClick: () => {
toggleActionsMenu();
handleNavigateToSettings();
navigateToSettingsManagementApp(application!);
},
},
{
Expand Down Expand Up @@ -143,7 +135,10 @@ export function ChatActionsMenu({
flush="left"
size="xs"
data-test-subj="settingsTabGoToConnectorsButton"
onClick={handleNavigateToConnectors}
onClick={() => {
toggleActionsMenu();
navigateToConnectorsManagementApp(application!);
}}
>
{i18n.translate('xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel', {
defaultMessage: 'Manage connectors',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
type ChatActionClickPayload,
type Feedback,
aiAssistantSimulatedFunctionCalling,
getElasticManagedLlmConnector,
} from '@kbn/observability-ai-assistant-plugin/public';
import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { findLastIndex } from 'lodash';
Expand Down Expand Up @@ -377,6 +378,10 @@ export function ChatBody({
conversation.refresh();
};

const elasticManagedLlm = getElasticManagedLlmConnector(connectors.connectors);
const showElasticLlmCalloutInChat =
elasticManagedLlm && connectors.selectedConnector === elasticManagedLlm.id;

const isPublic = conversation.value?.public;
const isArchived = !!conversation.value?.archived;
const showPromptEditor = !isArchived && (!isPublic || isConversationOwnedByCurrentUser);
Expand Down Expand Up @@ -517,6 +522,7 @@ export function ChatBody({
])
)
}
showElasticLlmCalloutInChat={showElasticLlmCalloutInChat}
/>
) : (
<ChatTimeline
Expand All @@ -543,6 +549,7 @@ export function ChatBody({
onStopGenerating={stop}
onActionClick={handleActionClick}
isArchived={isArchived}
showElasticLlmCalloutInChat={showElasticLlmCalloutInChat}
/>
)}
</EuiPanel>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* 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 from 'react';
import { render, screen } from '@testing-library/react';
import { ChatHeader } from './chat_header';
import { getElasticManagedLlmConnector } from '@kbn/observability-ai-assistant-plugin/public';
import { ElasticLlmTourCallout } from '@kbn/observability-ai-assistant-plugin/public';

jest.mock('@kbn/observability-ai-assistant-plugin/public', () => ({
ElasticLlmTourCallout: jest.fn(({ children }) => (
<div data-test-subj="elastic-llm-tour">{children}</div>
)),
getElasticManagedLlmConnector: jest.fn(),
useElasticLlmTourCalloutDismissed: jest.fn().mockReturnValue([false, jest.fn()]),
}));

jest.mock('./chat_actions_menu', () => ({
ChatActionsMenu: () => <div data-test-subj="chat-actions-menu" />,
}));

jest.mock('./chat_sharing_menu', () => ({
ChatSharingMenu: () => <div data-test-subj="chat-sharing-menu" />,
}));

jest.mock('./chat_context_menu', () => ({
ChatContextMenu: () => <div data-test-subj="chat-context-menu" />,
}));

describe('ChatHeader', () => {
const baseProps = {
conversationId: 'abc',
conversation: {
conversation: { title: 't', id: 'sample-id', last_updated: '2025-05-13T10:00:00Z' },
archived: false,
public: false,
labels: {},
numeric_labels: {},
messages: [],
namespace: 'default',
'@timestamp': '2025-05-13T10:00:00Z',
},
flyoutPositionMode: undefined,
licenseInvalid: false,
loading: false,
title: 'My title',
isConversationOwnedByCurrentUser: false,
onDuplicateConversation: jest.fn(),
onSaveTitle: jest.fn(),
onToggleFlyoutPositionMode: jest.fn(),
navigateToConversation: jest.fn(),
updateDisplayedConversation: jest.fn(),
handleConversationAccessUpdate: jest.fn(),
deleteConversation: jest.fn(),
copyConversationToClipboard: jest.fn(),
copyUrl: jest.fn(),
handleArchiveConversation: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

it('shows the Elastic Managed LLM connector tour callout when the connector is present', () => {
const elasticManagedConnector = {
id: 'elastic-llm',
actionTypeId: '.inference',
name: 'Elastic LLM',
isPreconfigured: true,
isDeprecated: false,
isSystemAction: false,
config: {
provider: 'elastic',
taskType: 'chat_completion',
inferenceId: '.rainbow-sprinkles-elastic',
providerConfig: {
model_id: 'rainbow-sprinkles',
},
},
referencedByCount: 0,
};
(getElasticManagedLlmConnector as jest.Mock).mockReturnValue(elasticManagedConnector);

render(
<ChatHeader
{...baseProps}
connectors={{
connectors: [elasticManagedConnector],
selectedConnector: undefined,
loading: false,
error: undefined,
selectConnector: (id: string) => {},
reloadConnectors: () => {},
}}
/>
);

expect(screen.getByTestId('elastic-llm-tour')).toBeInTheDocument();
expect(screen.getByTestId('chat-actions-menu')).toBeInTheDocument();
expect(ElasticLlmTourCallout).toHaveBeenCalled();
});

it('does not render the tour callout when the Elastic Managed LLM Connector is not present', () => {
(getElasticManagedLlmConnector as jest.Mock).mockReturnValue(undefined);

render(
<ChatHeader
{...baseProps}
connectors={{
connectors: [],
selectedConnector: undefined,
loading: false,
error: undefined,
selectConnector: (id: string) => {},
reloadConnectors: () => {},
}}
/>
);

expect(screen.queryByTestId('elastic-llm-tour')).toBeNull();
expect(screen.getByTestId('chat-actions-menu')).toBeInTheDocument();
expect(ElasticLlmTourCallout).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ import { i18n } from '@kbn/i18n';
import { css } from '@emotion/css';
import { AssistantIcon } from '@kbn/ai-assistant-icon';
import { Conversation, ConversationAccess } from '@kbn/observability-ai-assistant-plugin/common';
import {
ElasticLlmTourCallout,
getElasticManagedLlmConnector,
useElasticLlmTourCalloutDismissed,
} from '@kbn/observability-ai-assistant-plugin/public';
import { ChatActionsMenu } from './chat_actions_menu';
import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors';
import { FlyoutPositionMode } from './chat_flyout';
Expand Down Expand Up @@ -106,6 +111,9 @@ export function ChatHeader({
}
};

const elasticManagedLlm = getElasticManagedLlmConnector(connectors.connectors);
const [tourCalloutDismissed, setTourCalloutDismissed] = useElasticLlmTourCalloutDismissed(false);

return (
<EuiPanel
borderRadius="none"
Expand Down Expand Up @@ -267,7 +275,13 @@ export function ChatHeader({
) : null}

<EuiFlexItem grow={false}>
<ChatActionsMenu connectors={connectors} disabled={licenseInvalid} />
{!!elasticManagedLlm && !tourCalloutDismissed ? (
<ElasticLlmTourCallout dismissTour={() => setTourCalloutDismissed(true)}>
<ChatActionsMenu connectors={connectors} disabled={licenseInvalid} />
</ElasticLlmTourCallout>
) : (
<ChatActionsMenu connectors={connectors} disabled={licenseInvalid} />
)}
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base';
import { ChatItem } from './chat_item';
import { ChatConsolidatedItems } from './chat_consolidated_items';
import { getTimelineItemsfromConversation } from '../utils/get_timeline_items_from_conversation';
import { ElasticLlmCallout } from './elastic_llm_callout';

export interface ChatTimelineItem
extends Pick<Message['message'], 'role' | 'content' | 'function_call'> {
Expand Down Expand Up @@ -55,6 +56,7 @@ export interface ChatTimelineProps {
isConversationOwnedByCurrentUser: boolean;
isArchived: boolean;
currentUser?: Pick<AuthenticatedUser, 'full_name' | 'username'>;
showElasticLlmCalloutInChat: boolean;
onEdit: (message: Message, messageAfterEdit: Message) => void;
onFeedback: (feedback: Feedback) => void;
onRegenerate: (message: Message) => void;
Expand All @@ -69,6 +71,16 @@ export interface ChatTimelineProps {
}) => void;
}

const euiCommentListClassName = css`
padding-bottom: 32px;
`;

const stickyElasticLlmCalloutContainerClassName = css`
position: sticky;
top: 0;
z-index: 1;
`;

export function ChatTimeline({
conversationId,
messages,
Expand All @@ -77,6 +89,7 @@ export function ChatTimeline({
currentUser,
isConversationOwnedByCurrentUser,
isArchived,
showElasticLlmCalloutInChat,
onEdit,
onFeedback,
onRegenerate,
Expand Down Expand Up @@ -131,11 +144,12 @@ export function ChatTimeline({
]);

return (
<EuiCommentList
className={css`
padding-bottom: 32px;
`}
>
<EuiCommentList className={euiCommentListClassName}>
{showElasticLlmCalloutInChat ? (
<div className={stickyElasticLlmCalloutContainerClassName}>
<ElasticLlmCallout />
</div>
) : null}
{items.map((item, index) => {
return Array.isArray(item) ? (
<ChatConsolidatedItems
Expand Down
Loading