Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
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 @@ -1056,6 +1056,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D
mcpServer: `${ELASTIC_DOCS}solutions/search/agent-builder/mcp-server`,
a2aServer: `${ELASTIC_DOCS}solutions/search/agent-builder/a2a-server`,
limitationsKnownIssues: `${ELASTIC_DOCS}solutions/search/agent-builder/limitations-known-issues`,
learnMore: `${ELASTIC_DOCS}explore-analyze/ai-features/ai-agent-or-ai-assistant`,
},
inferenceManagement: {
inferenceAPIDocumentation: isServerless
Expand Down
1 change: 1 addition & 0 deletions src/platform/packages/shared/kbn-doc-links/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,7 @@ export interface DocLinks {
readonly mcpServer: string;
readonly a2aServer: string;
readonly limitationsKnownIssues: string;
readonly learnMore: string;
};
readonly indexManagement: {
readonly componentTemplate: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export const AIAssistantHeaderButton: React.FC<AIAssistantHeaderButtonProps> = (

const { getUrlForApp } = coreStart.application;
const { toasts } = coreStart.notifications;
const { links: docLinks } = coreStart.docLinks;

const hasAgentBuilder = coreStart.application.capabilities.agentBuilder?.show === true;
const isAiAgentsEnabled = getIsAiAgentsEnabled(coreStart.featureFlags);
Expand Down Expand Up @@ -160,9 +161,8 @@ export const AIAssistantHeaderButton: React.FC<AIAssistantHeaderButtonProps> = (
</EuiLink>
),
learnMoreLink: (
// TODO: Update link when documentation is ready
<EuiLink
href="#"
href={docLinks.agentBuilder.learnMore}
target="_blank"
data-test-subj="aiAgentBuilderLearnMoreLink"
>
Expand Down Expand Up @@ -299,6 +299,7 @@ export const AIAssistantHeaderButton: React.FC<AIAssistantHeaderButtonProps> = (
<AIAgentConfirmationModal
onConfirm={handleConfirmAgent}
onCancel={() => setConfirmModalOpen(false)}
docLinks={docLinks}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,19 @@ describe('plugin', () => {
});

// Second call: Chat Experience setting
expect(coreSetup.uiSettings.register).toHaveBeenNthCalledWith(2, {
[PREFERRED_CHAT_EXPERIENCE_SETTING_KEY]: {
...chatExperienceSetting,
value: AIChatExperience.Classic,
},
});
expect(coreSetup.uiSettings.register).toHaveBeenNthCalledWith(
2,
expect.objectContaining({
[PREFERRED_CHAT_EXPERIENCE_SETTING_KEY]: expect.objectContaining({
name: chatExperienceSetting.name,
description: chatExperienceSetting.description,
type: chatExperienceSetting.type,
options: chatExperienceSetting.options,
optionLabels: chatExperienceSetting.optionLabels,
value: AIChatExperience.Classic,
}),
})
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,17 @@
*/

import { i18n } from '@kbn/i18n';

import { schema } from '@kbn/config-schema';
import type { UiSettingsParams } from '@kbn/core-ui-settings-common';
import { AIChatExperience } from '@kbn/ai-assistant-common';
import { AI_AGENT, CHAT_EXPERIENCE_TITLE, CLASSIC_AI_ASSISTANT } from './translations';

// Define the chatExperienceSetting with proper typing
export const chatExperienceSetting: Omit<UiSettingsParams<AIChatExperience>, 'value'> = {
name: CHAT_EXPERIENCE_TITLE,
description: i18n.translate(
'aiAssistantManagementSelection.preferredChatExperienceSettingDescription',
{
defaultMessage: 'Choose which chat experience to use for everyone in this space. {link}',
values: {
// TODO: add the actual link when available
link: '<a href="" target="_blank" rel="noopener noreferrer" class="euiLink euiLink--primary">Learn more</a>',
},
defaultMessage: 'Choose which chat experience to use for all users in this space.',
}
),
schema: schema.oneOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ export const CHAT_EXPERIENCE_TITLE = i18n.translate(

export const CLASSIC_AI_ASSISTANT = i18n.translate(
'aiAssistantManagementSelection.preferredAIAssistantTypeSettingValueClassic',
{ defaultMessage: 'Classic AI Assistant (default)' }
{ defaultMessage: 'Classic AI Assistant' }
);
export const AI_AGENT = i18n.translate(
'aiAssistantManagementSelection.preferredAIAssistantTypeSettingValueAgent',
{ defaultMessage: 'AI Agent' }
{ defaultMessage: 'AI Agent (Beta)' }
);
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,26 @@ import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { AIAgentConfirmationModal } from './ai_agent_confirmation_modal';
import { I18nProvider } from '@kbn/i18n-react';
import type { DocLinks } from '@kbn/doc-links';

describe('AIAgentConfirmationModal', () => {
const mockOnConfirm = jest.fn();
const mockOnCancel = jest.fn();

const mockDocLinks: DocLinks = {
agentBuilder: {
learnMore: 'https://www.elastic.co/docs/explore-analyze/ai-features/ai-agent-or-ai-assistant',
},
} as DocLinks;

const renderComponent = () => {
return render(
<I18nProvider>
<AIAgentConfirmationModal onConfirm={mockOnConfirm} onCancel={mockOnCancel} />
<AIAgentConfirmationModal
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
docLinks={mockDocLinks}
/>
</I18nProvider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,18 +17,20 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n-react';
import type { DocLinks } from '@kbn/doc-links';

export interface AIAgentConfirmationModalProps {
onConfirm: () => void;
onCancel: () => void;
docLinks: DocLinks;
}

export const AIAgentConfirmationModal: React.FC<AIAgentConfirmationModalProps> = ({
onConfirm,
onCancel,
docLinks,
}) => {
const confirmModalTitleId = useGeneratedHtmlId({ prefix: 'aiAgentConfirmModalTitle' });

return (
<EuiConfirmModal
title={
Expand Down Expand Up @@ -75,9 +77,8 @@ export const AIAgentConfirmationModal: React.FC<AIAgentConfirmationModalProps> =
br: <br />,
bold: (str) => <strong>{str}</strong>,
learnMoreLink: (
// TODO: Update link when documentation is ready
<EuiLink
href="#"
href={docLinks.agentBuilder.learnMore}
target="_blank"
data-test-subj="AIAgentConfirmationModalLearnMoreLink"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ project:
dependsOn:
- '@kbn/i18n'
- '@kbn/i18n-react'
- '@kbn/doc-links'
tags:
- shared-browser
- package
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@
"kbn_references": [
"@kbn/i18n",
"@kbn/i18n-react",
"@kbn/doc-links",
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const AssistantSettingsContextMenu: React.FC<Params> = React.memo(
showAssistantOverlay,
settings,
toasts,
docLinks,
} = useAssistantContext();

const [isPopoverOpen, setPopover] = useState(false);
Expand Down Expand Up @@ -281,6 +282,7 @@ export const AssistantSettingsContextMenu: React.FC<Params> = React.memo(
<AIAgentConfirmationModal
onConfirm={handleConfirmAIAgent}
onCancel={handleCancelAIAgent}
docLinks={docLinks.links}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
* 2.0.
*/

import React, { useState, useCallback } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import { EuiLink } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { FieldRow, FieldRowProvider } from '@kbn/management-settings-components-field-row';
import { AI_CHAT_EXPERIENCE_TYPE } from '@kbn/management-settings-ids';
import { AIChatExperience } from '@kbn/ai-assistant-common';
Expand All @@ -22,8 +24,9 @@ export const ChatExperience: React.FC = () => {

const [isConfirmModalOpen, setConfirmModalOpen] = useState(false);
const isAiAgentsEnabled = getIsAiAgentsEnabled(featureFlags);
const field = fields[AI_CHAT_EXPERIENCE_TYPE];
const canEditAdvancedSettings = Boolean(application.capabilities.advancedSettings?.save);

// Show confirmation modal for AI Agents selection
const wrappedHandleFieldChange: typeof handleFieldChange = useCallback(
(id, change) => {
if (id === AI_CHAT_EXPERIENCE_TYPE && change?.unsavedValue === AIChatExperience.Agent) {
Expand All @@ -44,15 +47,38 @@ export const ChatExperience: React.FC = () => {
handleFieldChange(AI_CHAT_EXPERIENCE_TYPE, undefined);
}, [handleFieldChange]);

// Don't render if AI Agents feature is disabled
if (!isAiAgentsEnabled) {
const description = useMemo(
() => (
<FormattedMessage
id="aiAssistantManagementSelection.preferredChatExperienceSettingDescription"
defaultMessage="Choose which chat experience to use for everyone in this space. {learnMoreLink}"
values={{
learnMoreLink: (
<EuiLink
href={docLinks.links.agentBuilder.learnMore}
target="_blank"
data-test-subj="aiAgentBuilderLearnMoreLink"
>
<FormattedMessage
id="aiAssistantManagementSelection.preferredChatExperienceSettingDescription.learnMoreLink"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
),
[docLinks.links.agentBuilder.learnMore]
);

if (!isAiAgentsEnabled || !field) {
return null;
}

const field = fields[AI_CHAT_EXPERIENCE_TYPE];
if (!field) return null;

const canEditAdvancedSettings = application.capabilities.advancedSettings?.save;
const fieldWithDescription = {
...field,
description,
};

return (
<>
Expand All @@ -62,15 +88,19 @@ export const ChatExperience: React.FC = () => {
validateChange={(key: string, value: any) => settings.client.validateValue(key, value)}
>
<FieldRow
field={field}
isSavingEnabled={!!canEditAdvancedSettings}
field={fieldWithDescription}
isSavingEnabled={canEditAdvancedSettings}
onFieldChange={wrappedHandleFieldChange}
unsavedChange={unsavedChanges[AI_CHAT_EXPERIENCE_TYPE]}
/>
</FieldRowProvider>

{isConfirmModalOpen && (
<AIAgentConfirmationModal onConfirm={handleConfirmAgent} onCancel={handleCancelAgent} />
<AIAgentConfirmationModal
onConfirm={handleConfirmAgent}
onCancel={handleCancelAgent}
docLinks={docLinks.links}
/>
)}
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,9 @@ export const GenAiSettingsApp: React.FC<GenAiSettingsAppProps> = ({ setBreadcrum
<p>
<FormattedMessage
id="genAiSettings.aiConnectorDescription"
defaultMessage={`A large language model (LLM) is required to power the AI Assistant and AI-powered features.
By default, Elastic uses its Elastic Managed LLM connector ({additionalCostsIncur}) when no custom connectors are available.
When available, Elastic uses the last used custom connector. Set up your own connectors or disable the AI Assistant from the AI feature visibility setting below.
Select a default AI connector to enforce the choice for your space. {manageConnectors}`}
defaultMessage={`AI-powered features require a large language model (LLM) connector. You can use the Elastic Managed LLM ({atAdditionalCost}) or configure a third-party connector.
When you set a default AI connector, it is pre-selected for all of these features in this space.
If you haven't set a default, the most recently used connector is selected automatically. {manageConnectors}`}
values={{
manageConnectors: (
<EuiLink
Expand All @@ -131,12 +130,14 @@ export const GenAiSettingsApp: React.FC<GenAiSettingsAppProps> = ({ setBreadcrum
/>
</EuiLink>
),
additionalCostsIncur: (
// TODO: Update link when documentation is ready
<EuiLink href="#" target="_blank">
atAdditionalCost: (
<EuiLink
href={docLinks?.links?.observability?.elasticManagedLlmUsageCost}
target="_blank"
>
<FormattedMessage
id="genAiSettings.additionalCostsLink"
defaultMessage="additional costs incur"
id="genAiSettings.additionalCostLink"
defaultMessage="at additional cost"
/>
</EuiLink>
),
Expand Down Expand Up @@ -329,7 +330,7 @@ export const GenAiSettingsApp: React.FC<GenAiSettingsAppProps> = ({ setBreadcrum
) : (
<FormattedMessage
id="genAiSettings.showAIAssistantDescriptionLabel"
defaultMessage="Enable or disable AI-powered features in {space} settings."
defaultMessage="You can enable or disable AI-powered features from the {space} settings page."
values={{
space: (
<strong>
Expand All @@ -353,11 +354,14 @@ export const GenAiSettingsApp: React.FC<GenAiSettingsAppProps> = ({ setBreadcrum
</EuiFormRow>
</EuiDescribedFormGroup>
)}
{showChatExperienceSetting && <EuiSpacer size="l" />}
{showChatExperienceSetting && (
<EuiFlexItem>
<ChatExperience />
</EuiFlexItem>
)}
{!isAgentExperience && <EuiSpacer size="l" />}
{!isAgentExperience && <EuiSpacer size="l" />}
{showAiAssistantsVisibilitySetting && (
<EuiFlexItem>
<AIAssistantVisibility />
Expand Down