Extract AI assistant to package (#194552)

## Summary

This extracts the Observability AI Assistant into a shared package so
Search and Observability can both consume it.

A few notes:

This still relies on significantly tight coupling with the Obs AI
assistant plugin, which we will want to slowly decouple over time. It
means that currently to consume this in multiple places, you need to
provide a number of plugins for useKibana. Hopefully we can get rid of
that and replace them with props eventually and make the interface a
little less plugin-dependent.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Sander Philipse 2024-10-10 15:11:49 +02:00 committed by GitHub
parent e6c2750151
commit 8a3a05927b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 1414 additions and 978 deletions

1
.github/CODEOWNERS vendored
View file

@ -10,6 +10,7 @@ x-pack/plugins/actions @elastic/response-ops
x-pack/test/alerting_api_integration/common/plugins/actions_simulators @elastic/response-ops
packages/kbn-actions-types @elastic/response-ops
src/plugins/advanced_settings @elastic/appex-sharedux @elastic/kibana-management
x-pack/packages/kbn-ai-assistant @elastic/search-kibana
src/plugins/ai_assistant_management/selection @elastic/obs-knowledge-team
x-pack/packages/ml/aiops_change_point_detection @elastic/ml-ui
x-pack/packages/ml/aiops_common @elastic/ml-ui

View file

@ -158,6 +158,7 @@
"@kbn/actions-simulators-plugin": "link:x-pack/test/alerting_api_integration/common/plugins/actions_simulators",
"@kbn/actions-types": "link:packages/kbn-actions-types",
"@kbn/advanced-settings-plugin": "link:src/plugins/advanced_settings",
"@kbn/ai-assistant": "link:x-pack/packages/kbn-ai-assistant",
"@kbn/ai-assistant-management-plugin": "link:src/plugins/ai_assistant_management/selection",
"@kbn/aiops-change-point-detection": "link:x-pack/packages/ml/aiops_change_point_detection",
"@kbn/aiops-common": "link:x-pack/packages/ml/aiops_common",

View file

@ -315,6 +315,8 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
// 'xpack.reporting.poll.jobsRefresh.intervalErrorMultiplier (number)',
'xpack.rollup.ui.enabled (boolean?)',
'xpack.saved_object_tagging.cache_refresh_interval (duration?)',
'xpack.searchAssistant.ui.enabled (boolean?)',
'xpack.searchInferenceEndpoints.ui.enabled (boolean?)',
'xpack.searchPlayground.ui.enabled (boolean?)',
'xpack.security.loginAssistanceMessage (string?)',

View file

@ -14,6 +14,8 @@
"@kbn/actions-types/*": ["packages/kbn-actions-types/*"],
"@kbn/advanced-settings-plugin": ["src/plugins/advanced_settings"],
"@kbn/advanced-settings-plugin/*": ["src/plugins/advanced_settings/*"],
"@kbn/ai-assistant": ["x-pack/packages/kbn-ai-assistant"],
"@kbn/ai-assistant/*": ["x-pack/packages/kbn-ai-assistant/*"],
"@kbn/ai-assistant-management-plugin": ["src/plugins/ai_assistant_management/selection"],
"@kbn/ai-assistant-management-plugin/*": ["src/plugins/ai_assistant_management/selection/*"],
"@kbn/aiops-change-point-detection": ["x-pack/packages/ml/aiops_change_point_detection"],

View file

@ -8,6 +8,7 @@
"packages/ml/aiops_log_rate_analysis",
"plugins/aiops"
],
"xpack.aiAssistant": "packages/kbn-ai-assistant",
"xpack.alerting": "plugins/alerting",
"xpack.eventLog": "plugins/event_log",
"xpack.stackAlerts": "plugins/stack_alerts",
@ -44,9 +45,15 @@
"xpack.dataVisualizer": "plugins/data_visualizer",
"xpack.exploratoryView": "plugins/observability_solution/exploratory_view",
"xpack.fileUpload": "plugins/file_upload",
"xpack.globalSearch": ["plugins/global_search"],
"xpack.globalSearchBar": ["plugins/global_search_bar"],
"xpack.graph": ["plugins/graph"],
"xpack.globalSearch": [
"plugins/global_search"
],
"xpack.globalSearchBar": [
"plugins/global_search_bar"
],
"xpack.graph": [
"plugins/graph"
],
"xpack.grokDebugger": "plugins/grokdebugger",
"xpack.idxMgmt": "plugins/index_management",
"xpack.idxMgmtPackage": "packages/index-management",
@ -68,9 +75,13 @@
"xpack.licenseMgmt": "plugins/license_management",
"xpack.licensing": "plugins/licensing",
"xpack.lists": "plugins/lists",
"xpack.logstash": ["plugins/logstash"],
"xpack.logstash": [
"plugins/logstash"
],
"xpack.main": "legacy/plugins/xpack_main",
"xpack.maps": ["plugins/maps"],
"xpack.maps": [
"plugins/maps"
],
"xpack.metricsData": "plugins/observability_solution/metrics_data_access",
"xpack.ml": [
"packages/ml/anomaly_utils",
@ -85,7 +96,9 @@
"packages/ml/ui_actions",
"plugins/ml"
],
"xpack.monitoring": ["plugins/monitoring"],
"xpack.monitoring": [
"plugins/monitoring"
],
"xpack.observability": "plugins/observability_solution/observability",
"xpack.observabilityAiAssistant": [
"plugins/observability_solution/observability_ai_assistant",
@ -100,10 +113,17 @@
],
"xpack.osquery": ["plugins/osquery"],
"xpack.painlessLab": "plugins/painless_lab",
"xpack.profiling": ["plugins/observability_solution/profiling"],
"xpack.profiling": [
"plugins/observability_solution/profiling"
],
"xpack.remoteClusters": "plugins/remote_clusters",
"xpack.reporting": ["plugins/reporting"],
"xpack.rollupJobs": ["packages/rollup", "plugins/rollup"],
"xpack.reporting": [
"plugins/reporting"
],
"xpack.rollupJobs": [
"packages/rollup",
"plugins/rollup"
],
"xpack.runtimeFields": "plugins/runtime_fields",
"xpack.screenshotting": "plugins/screenshotting",
"xpack.searchSharedUI": "packages/search/shared_ui",
@ -114,7 +134,10 @@
"xpack.searchInferenceEndpoints": "plugins/search_inference_endpoints",
"xpack.searchAssistant": "plugins/search_assistant",
"xpack.searchProfiler": "plugins/searchprofiler",
"xpack.security": ["plugins/security", "packages/security"],
"xpack.security": [
"plugins/security",
"packages/security"
],
"xpack.server": "legacy/server",
"xpack.serverless": "plugins/serverless",
"xpack.serverlessSearch": "plugins/serverless_search",
@ -126,20 +149,30 @@
"xpack.slo": "plugins/observability_solution/slo",
"xpack.snapshotRestore": "plugins/snapshot_restore",
"xpack.spaces": "plugins/spaces",
"xpack.savedObjectsTagging": ["plugins/saved_objects_tagging"],
"xpack.savedObjectsTagging": [
"plugins/saved_objects_tagging"
],
"xpack.taskManager": "legacy/plugins/task_manager",
"xpack.threatIntelligence": "plugins/threat_intelligence",
"xpack.timelines": "plugins/timelines",
"xpack.transform": "plugins/transform",
"xpack.triggersActionsUI": "plugins/triggers_actions_ui",
"xpack.upgradeAssistant": "plugins/upgrade_assistant",
"xpack.uptime": ["plugins/observability_solution/uptime"],
"xpack.synthetics": ["plugins/observability_solution/synthetics"],
"xpack.ux": ["plugins/observability_solution/ux"],
"xpack.uptime": [
"plugins/observability_solution/uptime"
],
"xpack.synthetics": [
"plugins/observability_solution/synthetics"
],
"xpack.ux": [
"plugins/observability_solution/ux"
],
"xpack.urlDrilldown": "plugins/drilldowns/url_drilldown",
"xpack.watcher": "plugins/watcher"
},
"exclude": ["examples"],
"exclude": [
"examples"
],
"translations": [
"@kbn/translations-plugin/translations/zh-CN.json",
"@kbn/translations-plugin/translations/ja-JP.json",

View file

@ -0,0 +1,3 @@
# @kbn/ai-assistant
Provides components, types and context to render the AI Assistant in plugins.

View file

@ -0,0 +1,7 @@
/*
* 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.
*/
export * from './src';

View file

@ -0,0 +1,18 @@
/*
* 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.
*/
module.exports = {
coverageDirectory: '<rootDir>/target/kibana-coverage/jest/x-pack/packages/kbn_ai_assistant_src',
coverageReporters: ['text', 'html'],
collectCoverageFrom: [
'<rootDir>/x-pack/packages/kbn-ai-assistant/src/**/*.{ts,tsx}',
'!<rootDir>/x-pack/packages/kbn-ai-assistant/src/*.test.{ts,tsx}',
],
preset: '@kbn/test',
rootDir: '../../..',
roots: ['<rootDir>/x-pack/packages/kbn-ai-assistant'],
};

View file

@ -0,0 +1,5 @@
{
"id": "@kbn/ai-assistant",
"owner": "@elastic/search-kibana",
"type": "shared-browser"
}

View file

@ -0,0 +1,7 @@
{
"name": "@kbn/ai-assistant",
"private": true,
"version": "1.0.0",
"license": "Elastic License 2.0",
"sideEffects": false
}

View file

@ -0,0 +1,9 @@
/*
* 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.
*/
// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom';

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

View file

@ -46,12 +46,13 @@ export function AskAssistantButton({
variant,
onClick,
}: AskAssistantButtonProps) {
const buttonLabel = i18n.translate(
'xpack.observabilityAiAssistant.askAssistantButton.buttonLabel',
{
defaultMessage: 'Ask Assistant',
}
);
const buttonLabel = i18n.translate('xpack.aiAssistant.askAssistantButton.buttonLabel', {
defaultMessage: 'Ask Assistant',
});
const aiAssistantLabel = i18n.translate('xpack.aiAssistant.aiAssistantLabel', {
defaultMessage: 'AI Assistant',
});
switch (variant) {
case 'basic':
@ -84,23 +85,13 @@ export function AskAssistantButton({
return (
<EuiToolTip
position="top"
title={i18n.translate('xpack.observabilityAiAssistant.askAssistantButton.popoverTitle', {
defaultMessage: 'AI Assistant for Observability',
title={aiAssistantLabel}
content={i18n.translate('xpack.aiAssistant.askAssistantButton.popoverContent', {
defaultMessage: 'Get insights into your data with the Elastic Assistant',
})}
content={i18n.translate(
'xpack.observabilityAiAssistant.askAssistantButton.popoverContent',
{
defaultMessage: 'Get insights into your data with the Elastic Assistant',
}
)}
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.askAssistantButton.popoverTitle',
{
defaultMessage: 'AI Assistant for Observability',
}
)}
aria-label={aiAssistantLabel}
data-test-subj="observabilityAiAssistantAskAssistantButtonButtonIcon"
display={fill ? 'fill' : 'base'}
iconType="sparkles"

View file

@ -21,10 +21,10 @@ export function HideExpandConversationListButton(props: HideExpandConversationLi
{...props}
>
{props.isExpanded
? i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.hide', {
? i18n.translate('xpack.aiAssistant.hideExpandConversationButton.hide', {
defaultMessage: 'Hide chats',
})
: i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.show', {
: i18n.translate('xpack.aiAssistant.hideExpandConversationButton.show', {
defaultMessage: 'Show chats',
})}
</EuiButtonEmpty>

View file

@ -18,7 +18,7 @@ export function NewChatButton(
iconType="newChat"
{...nextProps}
>
{i18n.translate('xpack.observabilityAiAssistant.newChatButton', {
{i18n.translate('xpack.aiAssistant.newChatButton', {
defaultMessage: 'New chat',
})}
</EuiButton>

View file

@ -16,10 +16,8 @@ import {
EuiToolTip,
} from '@elastic/eui';
import { ConnectorSelectorBase } from '@kbn/observability-ai-assistant-plugin/public';
import { useKibana } from '../../hooks/use_kibana';
import { getSettingsHref } from '../../utils/get_settings_href';
import { getSettingsKnowledgeBaseHref } from '../../utils/get_settings_kb_href';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors';
import { useKibana } from '../hooks/use_kibana';
export function ChatActionsMenu({
connectors,
@ -32,14 +30,11 @@ export function ChatActionsMenu({
disabled: boolean;
onCopyConversationClick: () => void;
}) {
const {
application: { navigateToUrl, navigateToApp },
http,
} = useKibana().services;
const { application, http } = useKibana().services;
const [isOpen, setIsOpen] = useState(false);
const handleNavigateToConnectors = () => {
navigateToApp('management', {
application?.navigateToApp('management', {
path: '/insightsAndAlerting/triggersActionsConnectors/connectors',
});
};
@ -49,11 +44,17 @@ export function ChatActionsMenu({
};
const handleNavigateToSettings = () => {
navigateToUrl(getSettingsHref(http));
application?.navigateToUrl(
http!.basePath.prepend(`/app/management/kibana/observabilityAiAssistantManagement`)
);
};
const handleNavigateToSettingsKnowledgeBase = () => {
navigateToUrl(getSettingsKnowledgeBaseHref(http));
application?.navigateToUrl(
http!.basePath.prepend(
`/app/management/kibana/observabilityAiAssistantManagement?tab=knowledge_base`
)
);
};
return (
@ -61,10 +62,9 @@ export function ChatActionsMenu({
isOpen={isOpen}
button={
<EuiToolTip
content={i18n.translate(
'xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel',
{ defaultMessage: 'More actions' }
)}
content={i18n.translate('xpack.aiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel', {
defaultMessage: 'More actions',
})}
display="block"
>
<EuiButtonIcon
@ -73,7 +73,7 @@ export function ChatActionsMenu({
iconType="boxesVertical"
onClick={toggleActionsMenu}
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel',
'xpack.aiAssistant.chatActionsMenu.euiButtonIcon.menuLabel',
{ defaultMessage: 'Menu' }
)}
/>
@ -87,24 +87,21 @@ export function ChatActionsMenu({
panels={[
{
id: 0,
title: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.title', {
title: i18n.translate('xpack.aiAssistant.chatHeader.actions.title', {
defaultMessage: 'Actions',
}),
items: [
{
name: i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase',
{
defaultMessage: 'Manage knowledge base',
}
),
name: i18n.translate('xpack.aiAssistant.chatHeader.actions.knowledgeBase', {
defaultMessage: 'Manage knowledge base',
}),
onClick: () => {
toggleActionsMenu();
handleNavigateToSettingsKnowledgeBase();
},
},
{
name: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.settings', {
name: i18n.translate('xpack.aiAssistant.chatHeader.actions.settings', {
defaultMessage: 'AI Assistant Settings',
}),
onClick: () => {
@ -115,7 +112,7 @@ export function ChatActionsMenu({
{
name: (
<div className="eui-textTruncate">
{i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', {
{i18n.translate('xpack.aiAssistant.chatHeader.actions.connector', {
defaultMessage: 'Connector',
})}{' '}
<strong>
@ -129,12 +126,9 @@ export function ChatActionsMenu({
panel: 1,
},
{
name: i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.actions.copyConversation',
{
defaultMessage: 'Copy conversation',
}
),
name: i18n.translate('xpack.aiAssistant.chatHeader.actions.copyConversation', {
defaultMessage: 'Copy conversation',
}),
disabled: !conversationId,
onClick: () => {
toggleActionsMenu();
@ -146,7 +140,7 @@ export function ChatActionsMenu({
{
id: 1,
width: 256,
title: i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', {
title: i18n.translate('xpack.aiAssistant.chatHeader.actions.connector', {
defaultMessage: 'Connector',
}),
content: (
@ -159,10 +153,9 @@ export function ChatActionsMenu({
data-test-subj="settingsTabGoToConnectorsButton"
onClick={handleNavigateToConnectors}
>
{i18n.translate(
'xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel',
{ defaultMessage: 'Manage connectors' }
)}
{i18n.translate('xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel', {
defaultMessage: 'Manage connectors',
})}
</EuiButtonEmpty>
</EuiPanel>
),

View file

@ -8,9 +8,9 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import React from 'react';
import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { buildSystemMessage } from '../utils/builders';
import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories';
import { ChatBody as Component } from './chat_body';
import { buildSystemMessage } from '../../utils/builders';
const meta: ComponentMeta<typeof Component> = {
component: Component,

View file

@ -31,20 +31,20 @@ import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { euiThemeVars } from '@kbn/ui-theme';
import { findLastIndex } from 'lodash';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useConversation } from '../../hooks/use_conversation';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import { useLicense } from '../../hooks/use_license';
import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service';
import { useSimulatedFunctionCalling } from '../../hooks/use_simulated_function_calling';
import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../../i18n';
import { PromptEditor } from '../prompt_editor/prompt_editor';
import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base';
import { ASSISTANT_SETUP_TITLE, EMPTY_CONVERSATION_TITLE, UPGRADE_LICENSE_TITLE } from '../i18n';
import { useAIAssistantChatService } from '../hooks/use_ai_assistant_chat_service';
import { useSimulatedFunctionCalling } from '../hooks/use_simulated_function_calling';
import { useGenAIConnectors } from '../hooks/use_genai_connectors';
import { useConversation } from '../hooks/use_conversation';
import { FlyoutPositionMode } from './chat_flyout';
import { ChatHeader } from './chat_header';
import { ChatTimeline } from './chat_timeline';
import { IncorrectLicensePanel } from './incorrect_license_panel';
import { SimulatedFunctionCallingCallout } from './simulated_function_calling_callout';
import { WelcomeMessage } from './welcome_message';
import { useLicense } from '../hooks/use_license';
import { PromptEditor } from '../prompt_editor/prompt_editor';
const fullHeightClassName = css`
height: 100%;
@ -110,7 +110,7 @@ export function ChatBody({
showLinkToConversationsApp,
onConversationUpdate,
onToggleFlyoutPositionMode,
onClose,
navigateToConversation,
}: {
connectors: ReturnType<typeof useGenAIConnectors>;
currentUser?: Pick<AuthenticatedUser, 'full_name' | 'username'>;
@ -122,14 +122,14 @@ export function ChatBody({
showLinkToConversationsApp: boolean;
onConversationUpdate: (conversation: { conversation: Conversation['conversation'] }) => void;
onToggleFlyoutPositionMode?: (flyoutPositionMode: FlyoutPositionMode) => void;
onClose?: () => void;
navigateToConversation: (conversationId?: string) => void;
}) {
const license = useLicense();
const hasCorrectLicense = license?.hasAtLeast('enterprise');
const euiTheme = useEuiTheme();
const scrollBarStyles = euiScrollBarStyles(euiTheme);
const chatService = useObservabilityAIAssistantChatService();
const chatService = useAIAssistantChatService();
const { simulatedFunctionCallingEnabled } = useSimulatedFunctionCalling();
@ -440,12 +440,12 @@ export function ChatBody({
<EuiFlexItem grow={false} className={chatBodyContainerClassNameWithError}>
<EuiCallOut
color="danger"
title={i18n.translate('xpack.observabilityAiAssistant.couldNotFindConversationTitle', {
title={i18n.translate('xpack.aiAssistant.couldNotFindConversationTitle', {
defaultMessage: 'Conversation not found',
})}
iconType="warning"
>
{i18n.translate('xpack.observabilityAiAssistant.couldNotFindConversationContent', {
{i18n.translate('xpack.aiAssistant.couldNotFindConversationContent', {
defaultMessage:
'Could not find a conversation with id {conversationId}. Make sure the conversation exists and you have access to it.',
values: { conversationId: initialConversationId },
@ -470,12 +470,12 @@ export function ChatBody({
{conversation.error ? (
<EuiCallOut
color="danger"
title={i18n.translate('xpack.observabilityAiAssistant.couldNotFindConversationTitle', {
title={i18n.translate('xpack.aiAssistant.couldNotFindConversationTitle', {
defaultMessage: 'Conversation not found',
})}
iconType="warning"
>
{i18n.translate('xpack.observabilityAiAssistant.couldNotFindConversationContent', {
{i18n.translate('xpack.aiAssistant.couldNotFindConversationContent', {
defaultMessage:
'Could not find a conversation with id {conversationId}. Make sure the conversation exists and you have access to it.',
values: { conversationId: initialConversationId },
@ -500,7 +500,7 @@ export function ChatBody({
saveTitle(newTitle);
}}
onToggleFlyoutPositionMode={onToggleFlyoutPositionMode}
onClose={onClose}
navigateToConversation={navigateToConversation}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>

View file

@ -90,11 +90,11 @@ export function ChatConsolidatedItems({
>
<em>
{!expanded
? i18n.translate('xpack.observabilityAiAssistant.chatCollapsedItems.showEvents', {
? i18n.translate('xpack.aiAssistant.chatCollapsedItems.showEvents', {
defaultMessage: 'Show {count} events',
values: { count: consolidatedItem.length },
})
: i18n.translate('xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents', {
: i18n.translate('xpack.aiAssistant.chatCollapsedItems.hideEvents', {
defaultMessage: 'Hide {count} events',
values: { count: consolidatedItem.length },
})}
@ -104,12 +104,9 @@ export function ChatConsolidatedItems({
username=""
actions={
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatCollapsedItems.toggleButtonLabel',
{
defaultMessage: 'Show / hide items',
}
)}
aria-label={i18n.translate('xpack.aiAssistant.chatCollapsedItems.toggleButtonLabel', {
defaultMessage: 'Show / hide items',
})}
color="text"
data-test-subj="observabilityAiAssistantChatCollapsedItemsButton"
iconType={expanded ? 'arrowUp' : 'arrowDown'}

View file

@ -7,8 +7,8 @@
import { ComponentStory } from '@storybook/react';
import React from 'react';
import { buildSystemMessage } from '../../utils/builders';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { buildSystemMessage } from '../utils/builders';
import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories';
import { ChatFlyout as Component, FlyoutPositionMode } from './chat_flyout';
export default {
@ -33,6 +33,7 @@ const defaultProps: ChatFlyoutProps = {
initialMessages: [buildSystemMessage()],
initialFlyoutPositionMode: FlyoutPositionMode.OVERLAY,
onClose: () => {},
navigateToConversation: () => {},
};
export const ChatFlyout = Template.bind({});

View file

@ -19,16 +19,16 @@ import { i18n } from '@kbn/i18n';
import { Message } from '@kbn/observability-ai-assistant-plugin/common';
import React, { useState } from 'react';
import ReactDOM from 'react-dom';
import { useConversationKey } from '../../hooks/use_conversation_key';
import { useConversationList } from '../../hooks/use_conversation_list';
import { useCurrentUser } from '../../hooks/use_current_user';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
import { useKibana } from '../../hooks/use_kibana';
import { useKnowledgeBase } from '../../hooks/use_knowledge_base';
import { NewChatButton } from '../buttons/new_chat_button';
import { useConversationKey } from '../hooks/use_conversation_key';
import { useConversationList } from '../hooks/use_conversation_list';
import { useCurrentUser } from '../hooks/use_current_user';
import { useGenAIConnectors } from '../hooks/use_genai_connectors';
import { ChatBody } from './chat_body';
import { ChatInlineEditingContent } from './chat_inline_edit';
import { ConversationList } from './conversation_list';
import { useKibana } from '../hooks/use_kibana';
import { useKnowledgeBase } from '../hooks/use_knowledge_base';
import { NewChatButton } from '../buttons/new_chat_button';
const CONVERSATIONS_SIDEBAR_WIDTH = 260;
const CONVERSATIONS_SIDEBAR_WIDTH_COLLAPSED = 34;
@ -46,12 +46,14 @@ export function ChatFlyout({
initialFlyoutPositionMode,
isOpen,
onClose,
navigateToConversation,
}: {
initialTitle: string;
initialMessages: Message[];
initialFlyoutPositionMode?: FlyoutPositionMode;
isOpen: boolean;
onClose: () => void;
navigateToConversation(conversationId?: string): void;
}) {
const { euiTheme } = useEuiTheme();
const breakpoint = useCurrentEuiBreakpoint();
@ -75,11 +77,7 @@ export function ChatFlyout({
const {
services: {
plugins: {
start: {
observabilityAIAssistant: { ObservabilityAIAssistantMultipaneFlyoutContext },
},
},
observabilityAIAssistant: { ObservabilityAIAssistantMultipaneFlyoutContext },
},
} = useKibana();
const conversationList = useConversationList();
@ -148,8 +146,8 @@ export function ChatFlyout({
>
<EuiFlyoutResizable
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantForObservabilityLabel',
{ defaultMessage: 'AI Assistant for Observability Chat Flyout' }
'xpack.aiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantLabel',
{ defaultMessage: 'AI Assistant Chat Flyout' }
)}
className={flyoutClassName}
closeButtonProps={{
@ -185,11 +183,11 @@ export function ChatFlyout({
content={
conversationsExpanded
? i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel',
'xpack.aiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel',
{ defaultMessage: 'Collapse conversation list' }
)
: i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel',
'xpack.aiAssistant.chatFlyout.euiToolTip.expandConversationListLabel',
{ defaultMessage: 'Expand conversation list' }
)
}
@ -197,7 +195,7 @@ export function ChatFlyout({
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel',
'xpack.aiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel',
{ defaultMessage: 'Expand conversation list' }
)}
className={expandButtonClassName}
@ -232,14 +230,14 @@ export function ChatFlyout({
button={
<EuiToolTip
content={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel',
'xpack.aiAssistant.chatFlyout.euiToolTip.newChatLabel',
{ defaultMessage: 'New chat' }
)}
display="block"
>
<NewChatButton
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel',
'xpack.aiAssistant.chatFlyout.euiButtonIcon.newChatLabel',
{ defaultMessage: 'New chat' }
)}
collapsed
@ -274,7 +272,10 @@ export function ChatFlyout({
conversationList.conversations.refresh();
}}
onToggleFlyoutPositionMode={handleToggleFlyoutPositionMode}
onClose={onClose}
navigateToConversation={(newConversationId?: string) => {
if (onClose) onClose();
navigateToConversation(newConversationId);
}}
/>
</EuiFlexItem>

View file

@ -21,8 +21,7 @@ import { i18n } from '@kbn/i18n';
import { css } from '@emotion/css';
import { AssistantAvatar } from '@kbn/observability-ai-assistant-plugin/public';
import { ChatActionsMenu } from './chat_actions_menu';
import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors';
import { FlyoutPositionMode } from './chat_flyout';
// needed to prevent InlineTextEdit component from expanding container
@ -50,7 +49,7 @@ export function ChatHeader({
onCopyConversation,
onSaveTitle,
onToggleFlyoutPositionMode,
onClose,
navigateToConversation,
}: {
connectors: UseGenAIConnectorsResult;
conversationId?: string;
@ -61,36 +60,17 @@ export function ChatHeader({
onCopyConversation: () => void;
onSaveTitle: (title: string) => void;
onToggleFlyoutPositionMode?: (newFlyoutPositionMode: FlyoutPositionMode) => void;
onClose?: () => void;
navigateToConversation: (nextConversationId?: string) => void;
}) {
const theme = useEuiTheme();
const breakpoint = useCurrentEuiBreakpoint();
const router = useObservabilityAIAssistantRouter();
const [newTitle, setNewTitle] = useState(title);
useEffect(() => {
setNewTitle(title);
}, [title]);
const handleNavigateToConversations = () => {
if (onClose) {
onClose();
}
if (conversationId) {
router.push('/conversations/{conversationId}', {
path: {
conversationId,
},
query: {},
});
} else {
router.push('/conversations/new', { path: {}, query: {} });
}
};
const handleToggleFlyoutPositionMode = () => {
if (flyoutPositionMode) {
onToggleFlyoutPositionMode?.(
@ -126,10 +106,9 @@ export function ChatHeader({
className={css`
color: ${!!title ? theme.euiTheme.colors.text : theme.euiTheme.colors.subduedText};
`}
inputAriaLabel={i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.editConversationInput',
{ defaultMessage: 'Edit conversation' }
)}
inputAriaLabel={i18n.translate('xpack.aiAssistant.chatHeader.editConversationInput', {
defaultMessage: 'Edit conversation',
})}
isReadOnly={
!conversationId ||
!connectors.selectedConnector ||
@ -162,11 +141,11 @@ export function ChatHeader({
content={
flyoutPositionMode === 'overlay'
? i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock',
'xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock',
{ defaultMessage: 'Dock chat' }
)
: i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock',
'xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock',
{ defaultMessage: 'Undock chat' }
)
}
@ -174,7 +153,7 @@ export function ChatHeader({
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel',
'xpack.aiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel',
{ defaultMessage: 'Toggle flyout mode' }
)}
data-test-subj="observabilityAiAssistantChatHeaderButton"
@ -192,19 +171,19 @@ export function ChatHeader({
button={
<EuiToolTip
content={i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel',
'xpack.aiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel',
{ defaultMessage: 'Navigate to conversations' }
)}
display="block"
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel',
'xpack.aiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel',
{ defaultMessage: 'Navigate to conversations' }
)}
data-test-subj="observabilityAiAssistantChatHeaderButton"
iconType="discuss"
onClick={handleNavigateToConversations}
onClick={() => navigateToConversation(conversationId)}
/>
</EuiToolTip>
}

View file

@ -22,11 +22,11 @@ import {
Feedback,
TelemetryEventTypeWithPayload,
} from '@kbn/observability-ai-assistant-plugin/public';
import { getRoleTranslation } from '../utils/get_role_translation';
import { ChatItemActions } from './chat_item_actions';
import { ChatItemAvatar } from './chat_item_avatar';
import { ChatItemContentInlinePromptEditor } from './chat_item_content_inline_prompt_editor';
import { ChatTimelineItem } from './chat_timeline';
import { getRoleTranslation } from '../../utils/get_role_translation';
export interface ChatItemProps extends Omit<ChatTimelineItem, 'message'> {
onActionClick: ChatActionClickHandler;

View file

@ -46,12 +46,9 @@ export function ChatItemActions({
<>
{canEdit ? (
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.actions.editPrompt',
{
defaultMessage: 'Edit prompt',
}
)}
aria-label={i18n.translate('xpack.aiAssistant.chatTimeline.actions.editPrompt', {
defaultMessage: 'Edit prompt',
})}
color="text"
data-test-subj="observabilityAiAssistantChatItemActionsEditPromptButton"
display={editing ? 'fill' : 'empty'}
@ -62,12 +59,9 @@ export function ChatItemActions({
{collapsed ? (
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.actions.inspectPrompt',
{
defaultMessage: 'Inspect prompt',
}
)}
aria-label={i18n.translate('xpack.aiAssistant.chatTimeline.actions.inspectPrompt', {
defaultMessage: 'Inspect prompt',
})}
color="text"
data-test-subj="observabilityAiAssistantChatItemActionsInspectPromptButton"
display={expanded ? 'fill' : 'empty'}
@ -80,12 +74,9 @@ export function ChatItemActions({
<EuiPopover
button={
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage',
{
defaultMessage: 'Copy message',
}
)}
aria-label={i18n.translate('xpack.aiAssistant.chatTimeline.actions.copyMessage', {
defaultMessage: 'Copy message',
})}
color="text"
data-test-subj="observabilityAiAssistantChatItemActionsCopyMessageButton"
iconType="copyClipboard"
@ -101,12 +92,9 @@ export function ChatItemActions({
closePopover={() => setIsPopoverOpen(undefined)}
>
<EuiText size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful',
{
defaultMessage: 'Copied message',
}
)}
{i18n.translate('xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful', {
defaultMessage: 'Copied message',
})}
</EuiText>
</EuiPopover>
) : null}

View file

@ -7,6 +7,7 @@
import { euiThemeVars } from '@kbn/ui-theme';
import React, { ReactNode } from 'react';
import { css } from '@emotion/react';
interface ChatItemTitleProps {
actionsTrigger?: ReactNode;
@ -14,14 +15,15 @@ interface ChatItemTitleProps {
}
export function ChatItemTitle({ actionsTrigger, title }: ChatItemTitleProps) {
const containerCSS = css`
position: absolute;
top: 2;
right: ${euiThemeVars.euiSizeS};
`;
return (
<>
{title}
{actionsTrigger ? (
<div css={{ position: 'absolute', top: 2, right: euiThemeVars.euiSizeS }}>
{actionsTrigger}
</div>
) : null}
{actionsTrigger ? <div css={containerCSS}>{actionsTrigger}</div> : null}
</>
);
}

View file

@ -18,7 +18,7 @@ import {
buildFunctionResponseMessage,
buildSystemMessage,
buildUserMessage,
} from '../../utils/builders';
} from '../utils/builders';
import { ChatTimeline as Component, type ChatTimelineProps } from './chat_timeline';
export default {
@ -86,11 +86,11 @@ const defaultProps: ComponentProps<typeof Component> = {
Mathematical Functions:
In mathematics, a function maps input values to corresponding output values based on a specific rule or expression. The general process of how a mathematical function works can be summarized as follows:
Step 1: Input - You provide an input value to the function, denoted as 'x' in the notation f(x). This value represents the independent variable.
Step 2: Processing - The function takes the input value and applies a specific rule or algorithm to it. This rule is defined by the function itself and varies depending on the function's expression.
Step 3: Output - After processing the input, the function produces an output value, denoted as 'f(x)' or 'y'. This output represents the dependent variable and is the result of applying the function's rule to the input.
Step 4: Uniqueness - A well-defined mathematical function ensures that each input value corresponds to exactly one output value. In other words, the function should yield the same output for the same input whenever it is called.`,
},
}),

View file

@ -18,10 +18,10 @@ import {
type ObservabilityAIAssistantChatService,
type TelemetryEventTypeWithPayload,
} from '@kbn/observability-ai-assistant-plugin/public';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
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 { getTimelineItemsfromConversation } from '../utils/get_timeline_items_from_conversation';
export interface ChatTimelineItem
extends Pick<Message['message'], 'role' | 'content' | 'function_call'> {

View file

@ -7,8 +7,8 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import React from 'react';
import { buildConversation } from '../../utils/builders';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { buildConversation } from '../utils/builders';
import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories';
import { ConversationList as Component } from './conversation_list';
type ConversationListProps = React.ComponentProps<typeof Component>;

View file

@ -21,10 +21,9 @@ import {
import { css } from '@emotion/css';
import { i18n } from '@kbn/i18n';
import React, { MouseEvent } from 'react';
import { useConfirmModal } from '../../hooks/use_confirm_modal';
import type { UseConversationListResult } from '../../hooks/use_conversation_list';
import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router';
import { EMPTY_CONVERSATION_TITLE } from '../../i18n';
import { useConfirmModal } from '../hooks/use_confirm_modal';
import type { UseConversationListResult } from '../hooks/use_conversation_list';
import { EMPTY_CONVERSATION_TITLE } from '../i18n';
import { NewChatButton } from '../buttons/new_chat_button';
const titleClassName = css`
@ -51,15 +50,17 @@ export function ConversationList({
selectedConversationId,
onConversationSelect,
onConversationDeleteClick,
newConversationHref,
getConversationHref,
}: {
conversations: UseConversationListResult['conversations'];
isLoading: boolean;
selectedConversationId?: string;
onConversationSelect?: (conversationId?: string) => void;
onConversationDeleteClick: (conversationId: string) => void;
newConversationHref?: string;
getConversationHref?: (conversationId: string) => string;
}) {
const router = useObservabilityAIAssistantRouter();
const euiTheme = useEuiTheme();
const scrollBarStyles = euiScrollBarStyles(euiTheme);
@ -70,21 +71,15 @@ export function ConversationList({
`;
const { element: confirmDeleteElement, confirm: confirmDeleteCallback } = useConfirmModal({
title: i18n.translate('xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle', {
title: i18n.translate('xpack.aiAssistant.flyout.confirmDeleteConversationTitle', {
defaultMessage: 'Delete this conversation?',
}),
children: i18n.translate(
'xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent',
{
defaultMessage: 'This action cannot be undone.',
}
),
confirmButtonText: i18n.translate(
'xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText',
{
defaultMessage: 'Delete conversation',
}
),
children: i18n.translate('xpack.aiAssistant.flyout.confirmDeleteConversationContent', {
defaultMessage: 'This action cannot be undone.',
}),
confirmButtonText: i18n.translate('xpack.aiAssistant.flyout.confirmDeleteButtonText', {
defaultMessage: 'Delete conversation',
}),
});
const displayedConversations = [
@ -94,7 +89,7 @@ export function ConversationList({
id: '',
label: EMPTY_CONVERSATION_TITLE,
lastUpdated: '',
href: router.link('/conversations/new'),
href: newConversationHref,
},
]
: []),
@ -102,11 +97,7 @@ export function ConversationList({
id: conversation.id,
label: conversation.title,
lastUpdated: conversation.last_updated,
href: router.link('/conversations/{conversationId}', {
path: {
conversationId: conversation.id,
},
}),
href: getConversationHref ? getConversationHref(conversation.id) : undefined,
})),
];
@ -123,7 +114,7 @@ export function ConversationList({
<EuiSpacer size="s" />
<EuiText className={titleClassName} size="s">
<strong>
{i18n.translate('xpack.observabilityAiAssistant.conversationList.title', {
{i18n.translate('xpack.aiAssistant.conversationList.title', {
defaultMessage: 'Previously',
})}
</strong>
@ -147,12 +138,9 @@ export function ConversationList({
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="s" color="danger">
{i18n.translate(
'xpack.observabilityAiAssistant.conversationList.errorMessage',
{
defaultMessage: 'Failed to load',
}
)}
{i18n.translate('xpack.aiAssistant.conversationList.errorMessage', {
defaultMessage: 'Failed to load',
})}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
@ -185,7 +173,7 @@ export function ConversationList({
? {
iconType: 'trash',
'aria-label': i18n.translate(
'xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel',
'xpack.aiAssistant.conversationList.deleteConversationIconLabel',
{
defaultMessage: 'Delete',
}
@ -211,12 +199,9 @@ export function ConversationList({
{!isLoading && !conversations.error && !displayedConversations?.length ? (
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="s">
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.conversationList.noConversations',
{
defaultMessage: 'No conversations',
}
)}
{i18n.translate('xpack.aiAssistant.conversationList.noConversations', {
defaultMessage: 'No conversations',
})}
</EuiText>
</EuiPanel>
) : null}
@ -228,7 +213,7 @@ export function ConversationList({
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow className={newChatButtonWrapperClassName}>
<NewChatButton
href={router.link('/conversations/new')}
href={newConversationHref}
onClick={(
event: MouseEvent<HTMLButtonElement> | MouseEvent<HTMLAnchorElement>
) => {

View file

@ -17,7 +17,7 @@ export function Disclaimer() {
textAlign="center"
data-test-subj="observabilityAiAssistantDisclaimer"
>
{i18n.translate('xpack.observabilityAiAssistant.disclaimer.disclaimerLabel', {
{i18n.translate('xpack.aiAssistant.disclaimer.disclaimerLabel', {
defaultMessage:
"This chat is powered by an integration with your LLM provider. LLMs are known to sometimes present incorrect information as if it's correct. Elastic supports configuration and connection to the LLM provider and your knowledge base, but is not responsible for the LLM's responses.",
})}

View file

@ -7,7 +7,7 @@
import { ComponentStory } from '@storybook/react';
import React from 'react';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories';
import { FunctionListPopover as Component } from './function_list_popover';
export default {

View file

@ -22,7 +22,7 @@ import type { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components
import { i18n } from '@kbn/i18n';
import { FunctionVisibility } from '@kbn/observability-ai-assistant-plugin/public';
import type { FunctionDefinition } from '@kbn/observability-ai-assistant-plugin/common';
import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service';
import { useAIAssistantChatService } from '../hooks/use_ai_assistant_chat_service';
interface FunctionListOption {
label: string;
@ -40,7 +40,7 @@ export function FunctionListPopover({
onSelectFunction: (func: string | undefined) => void;
disabled: boolean;
}) {
const { getFunctions } = useObservabilityAIAssistantChatService();
const { getFunctions } = useAIAssistantChatService();
const functions = getFunctions();
const [functionOptions, setFunctionOptions] = useState<
@ -80,21 +80,18 @@ export function FunctionListPopover({
content={
mode === 'prompt'
? i18n.translate(
'xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel',
'xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel',
{ defaultMessage: 'Select a function' }
)
: i18n.translate(
'xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction',
{
defaultMessage: 'Clear function',
}
)
: i18n.translate('xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction', {
defaultMessage: 'Clear function',
})
}
display="block"
>
<EuiButtonIcon
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel',
'xpack.aiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel',
{ defaultMessage: 'Select function' }
)}
data-test-subj="observabilityAiAssistantFunctionListPopoverButton"
@ -112,12 +109,9 @@ export function FunctionListPopover({
isOpen={isFunctionListOpen}
>
<EuiSelectable
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.prompt.functionList.functionList',
{
defaultMessage: 'Function list',
}
)}
aria-label={i18n.translate('xpack.aiAssistant.prompt.functionList.functionList', {
defaultMessage: 'Function list',
})}
listProps={{
isVirtualized: false,
showIcons: false,
@ -128,7 +122,7 @@ export function FunctionListPopover({
searchProps={{
'data-test-subj': 'searchFiltersList',
id: 'searchFilterList',
placeholder: i18n.translate('xpack.observabilityAiAssistant.prompt.functionList.filter', {
placeholder: i18n.translate('xpack.aiAssistant.prompt.functionList.filter', {
defaultMessage: 'Filter',
}),
}}

View file

@ -19,9 +19,9 @@ import {
import { css } from '@emotion/css';
import { i18n } from '@kbn/i18n';
import { euiThemeVars } from '@kbn/ui-theme';
import { UPGRADE_LICENSE_TITLE } from '../../i18n';
import ctaImage from '../../assets/elastic_ai_assistant.png';
import { useLicenseManagementLocator } from '../../hooks/use_license_management_locator';
import { elasticAiAssistantImage } from '@kbn/observability-ai-assistant-plugin/public';
import { UPGRADE_LICENSE_TITLE } from '../i18n';
import { useLicenseManagementLocator } from '../hooks/use_license_management_locator';
const incorrectLicenseContainer = css`
height: 100%;
@ -39,12 +39,12 @@ export function IncorrectLicensePanel() {
justifyContent="center"
className={incorrectLicenseContainer}
>
<EuiImage src={ctaImage} alt="Elastic AI Assistant" size="m" />
<EuiImage src={elasticAiAssistantImage} alt="Elastic AI Assistant" size="m" />
<EuiTitle>
<h2>{UPGRADE_LICENSE_TITLE}</h2>
</EuiTitle>
<EuiText color="subdued">
{i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.body', {
{i18n.translate('xpack.aiAssistant.incorrectLicense.body', {
defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.',
})}
</EuiText>
@ -57,12 +57,9 @@ export function IncorrectLicensePanel() {
href="https://www.elastic.co/subscriptions"
target="_blank"
>
{i18n.translate(
'xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton',
{
defaultMessage: 'Subscription plans',
}
)}
{i18n.translate('xpack.aiAssistant.incorrectLicense.subscriptionPlansButton', {
defaultMessage: 'Subscription plans',
})}
</EuiButton>
</EuiFlexItem>
<EuiFlexItem>
@ -70,7 +67,7 @@ export function IncorrectLicensePanel() {
data-test-subj="observabilityAiAssistantIncorrectLicensePanelManageLicenseButton"
onClick={handleNavigateToLicenseManagement}
>
{i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.manageLicense', {
{i18n.translate('xpack.aiAssistant.incorrectLicense.manageLicense', {
defaultMessage: 'Manage license',
})}
</EuiButtonEmpty>

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export * from './chat_body';
export * from './chat_inline_edit';
export * from './conversation_list';
export * from './chat_flyout';

View file

@ -7,7 +7,7 @@
import { ComponentMeta, ComponentStoryObj } from '@storybook/react';
import { merge } from 'lodash';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories';
import { KnowledgeBaseCallout as Component } from './knowledge_base_callout';
const meta: ComponentMeta<typeof Component> = {

View file

@ -17,7 +17,7 @@ import {
EuiText,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base';
export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnowledgeBaseResult }) {
let content: React.ReactNode;
@ -32,7 +32,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color="subdued">
{i18n.translate('xpack.observabilityAiAssistant.checkingKbAvailability', {
{i18n.translate('xpack.aiAssistant.checkingKbAvailability', {
defaultMessage: 'Checking availability of knowledge base',
})}
</EuiText>
@ -43,7 +43,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow
color = 'danger';
content = (
<EuiText size="xs" color={color}>
{i18n.translate('xpack.observabilityAiAssistant.failedToGetStatus', {
{i18n.translate('xpack.aiAssistant.failedToGetStatus', {
defaultMessage: 'Failed to get model status.',
})}
</EuiText>
@ -53,7 +53,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow
content = (
<EuiText size="xs" color="subdued">
<EuiIcon type="iInCircle" />{' '}
{i18n.translate('xpack.observabilityAiAssistant.poweredByModel', {
{i18n.translate('xpack.aiAssistant.poweredByModel', {
defaultMessage: 'Powered by {model}',
values: {
model: 'ELSER',
@ -70,7 +70,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiText size="xs" color={color}>
{i18n.translate('xpack.observabilityAiAssistant.installingKb', {
{i18n.translate('xpack.aiAssistant.installingKb', {
defaultMessage: 'Setting up the knowledge base',
})}
</EuiText>
@ -81,7 +81,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow
color = 'danger';
content = (
<EuiText size="xs" color={color}>
{i18n.translate('xpack.observabilityAiAssistant.failedToSetupKnowledgeBase', {
{i18n.translate('xpack.aiAssistant.failedToSetupKnowledgeBase', {
defaultMessage: 'Failed to set up knowledge base.',
})}
</EuiText>
@ -96,7 +96,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow
>
<EuiText size="xs">
<EuiIcon type="iInCircle" />{' '}
{i18n.translate('xpack.observabilityAiAssistant.setupKb', {
{i18n.translate('xpack.aiAssistant.setupKb', {
defaultMessage: 'Improve your experience by setting up the knowledge base.',
})}
</EuiText>

View file

@ -17,7 +17,7 @@ export function SimulatedFunctionCallingCallout() {
</EuiFlexItem>
<EuiFlexItem grow>
<EuiText size="s">
{i18n.translate('xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel', {
{i18n.translate('xpack.aiAssistant.simulatedFunctionCallingCalloutLabel', {
defaultMessage:
'Simulated function calling is enabled. You might see degradated performance.',
})}

View file

@ -16,9 +16,9 @@ import {
} from '@elastic/eui';
import { css } from '@emotion/css';
import { uniq } from 'lodash';
import { useObservabilityAIAssistantAppService } from '../../hooks/use_observability_ai_assistant_app_service';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
import { nonNullable } from '../../utils/non_nullable';
import { useAIAssistantAppService } from '../hooks/use_ai_assistant_app_service';
import { useGenAIConnectors } from '../hooks/use_genai_connectors';
import { nonNullable } from '../utils/non_nullable';
const starterPromptClassName = css`
max-width: 50%;
@ -30,7 +30,7 @@ const starterPromptInnerClassName = css`
`;
export function StarterPrompts({ onSelectPrompt }: { onSelectPrompt: (prompt: string) => void }) {
const service = useObservabilityAIAssistantAppService();
const service = useAIAssistantAppService();
const { connectors } = useGenAIConnectors();

View file

@ -5,19 +5,19 @@
* 2.0.
*/
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';
import { css } from '@emotion/css';
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, useCurrentEuiBreakpoint } from '@elastic/eui';
import type { ActionConnector } from '@kbn/triggers-actions-ui-plugin/public';
import { GenerativeAIForObservabilityConnectorFeatureId } from '@kbn/actions-plugin/common';
import { isSupportedConnectorType } from '@kbn/observability-ai-assistant-plugin/public';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base';
import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors';
import { Disclaimer } from './disclaimer';
import { WelcomeMessageConnectors } from './welcome_message_connectors';
import { WelcomeMessageKnowledgeBase } from './welcome_message_knowledge_base';
import { useKibana } from '../../hooks/use_kibana';
import { StarterPrompts } from './starter_prompts';
import { useKibana } from '../hooks/use_kibana';
const fullHeightClassName = css`
height: 100%;
@ -39,22 +39,15 @@ export function WelcomeMessage({
}) {
const breakpoint = useCurrentEuiBreakpoint();
const {
application: { navigateToApp, capabilities },
plugins: {
start: {
triggersActionsUi: { getAddConnectorFlyout: ConnectorFlyout },
},
},
} = useKibana().services;
const { application, triggersActionsUi } = useKibana().services;
const [connectorFlyoutOpen, setConnectorFlyoutOpen] = useState(false);
const handleConnectorClick = () => {
if (capabilities.management?.insightsAndAlerting?.triggersActions) {
if (application?.capabilities.management?.insightsAndAlerting?.triggersActions) {
setConnectorFlyoutOpen(true);
} else {
navigateToApp('management', {
application?.navigateToApp('management', {
path: '/insightsAndAlerting/triggersActionsConnectors/connectors',
});
}
@ -72,6 +65,11 @@ export function WelcomeMessage({
}
};
const ConnectorFlyout = useMemo(
() => triggersActionsUi.getAddConnectorFlyout,
[triggersActionsUi]
);
return (
<>
<EuiFlexGroup

View file

@ -19,7 +19,7 @@ import {
import { i18n } from '@kbn/i18n';
import { euiThemeVars } from '@kbn/ui-theme';
import { isHttpFetchError } from '@kbn/core-http-browser';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors';
const fadeInAnimation = keyframes`
from {
@ -56,11 +56,11 @@ export function WelcomeMessageConnectors({
<EuiText color="danger">
{isForbiddenError
? i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel',
'xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel',
{ defaultMessage: 'Required privileges to get connectors are missing' }
)
: i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel',
'xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel',
{ defaultMessage: 'Could not load connectors' }
)}
</EuiText>
@ -72,21 +72,15 @@ export function WelcomeMessageConnectors({
return !connectors.loading && connectors.connectors?.length === 0 && onSetupConnectorClick ? (
<div className={fadeInClassName}>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2',
{
defaultMessage:
'Start working with the Elastic AI Assistant by setting up a connector for your AI provider. The model needs to support function calls. When using OpenAI or Azure, we recommend using GPT4.',
}
)}
{i18n.translate('xpack.aiAssistant.initialSetupPanel.setupConnector.description2', {
defaultMessage:
'Start working with the Elastic AI Assistant by setting up a connector for your AI provider. The model needs to support function calls. When using OpenAI or Azure, we recommend using GPT4.',
})}
<EuiIconTip
content={i18n.translate(
'xpack.observabilityAiAssistant.technicalPreviewBadgeDescription',
{
defaultMessage:
"GPT4 is required for a more consistent experience when using function calls (for example when performing root cause analysis, visualizing data and more). GPT3.5 can work for some of the simpler workflows, such as explaining errors or for a ChatGPT like experience within Kibana which don't require the use of frequent function calls.",
}
)}
content={i18n.translate('xpack.aiAssistant.technicalPreviewBadgeDescription', {
defaultMessage:
"GPT4 is required for a more consistent experience when using function calls (for example when performing root cause analysis, visualizing data and more). GPT3.5 can work for some of the simpler workflows, such as explaining errors or for a ChatGPT like experience within Kibana which don't require the use of frequent function calls.",
})}
anchorProps={{
css: { verticalAlign: 'text-bottom' },
}}
@ -105,12 +99,9 @@ export function WelcomeMessageConnectors({
color="primary"
onClick={onSetupConnectorClick}
>
{i18n.translate(
'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel',
{
defaultMessage: 'Set up GenAI connector',
}
)}
{i18n.translate('xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel', {
defaultMessage: 'Set up GenAI connector',
})}
</EuiButton>
</div>
</div>

View file

@ -22,8 +22,8 @@ import usePrevious from 'react-use/lib/usePrevious';
import useTimeoutFn from 'react-use/lib/useTimeoutFn';
import useInterval from 'react-use/lib/useInterval';
import { WelcomeMessageKnowledgeBaseSetupErrorPanel } from './welcome_message_knowledge_base_setup_error_panel';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors';
import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base';
import type { UseGenAIConnectorsResult } from '../hooks/use_genai_connectors';
export function WelcomeMessageKnowledgeBase({
connectors,
@ -80,13 +80,10 @@ export function WelcomeMessageKnowledgeBase({
{knowledgeBase.isInstalling ? (
<>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel',
{
defaultMessage:
'We are setting up your knowledge base. This may take a few minutes. You can continue to use the Assistant while this process is underway.',
}
)}
{i18n.translate('xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel', {
defaultMessage:
'We are setting up your knowledge base. This may take a few minutes. You can continue to use the Assistant while this process is underway.',
})}
</EuiText>
<EuiSpacer size="m" />
@ -96,10 +93,9 @@ export function WelcomeMessageKnowledgeBase({
isLoading
onClick={noop}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel',
{ defaultMessage: 'Setting up Knowledge base' }
)}
{i18n.translate('xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel', {
defaultMessage: 'Setting up Knowledge base',
})}
</EuiButtonEmpty>
</>
) : null}
@ -112,7 +108,7 @@ export function WelcomeMessageKnowledgeBase({
<>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel',
'xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel',
{ defaultMessage: `Your Knowledge base hasn't been set up.` }
)}
</EuiText>
@ -130,12 +126,9 @@ export function WelcomeMessageKnowledgeBase({
iconType="importAction"
onClick={handleRetryInstall}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel',
{
defaultMessage: 'Install Knowledge base',
}
)}
{i18n.translate('xpack.aiAssistant.welcomeMessage.retryButtonLabel', {
defaultMessage: 'Install Knowledge base',
})}
</EuiButton>
</div>
</EuiFlexItem>
@ -149,7 +142,7 @@ export function WelcomeMessageKnowledgeBase({
onClick={() => setIsPopoverOpen(!isPopoverOpen)}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel',
'xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel',
{ defaultMessage: 'Inspect issues' }
)}
</EuiButtonEmpty>
@ -180,7 +173,7 @@ export function WelcomeMessageKnowledgeBase({
<EuiFlexItem grow={false}>
<EuiText color="subdued" size="s">
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel',
'xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel',
{ defaultMessage: 'Knowledge base successfully installed' }
)}
</EuiText>

View file

@ -21,8 +21,8 @@ import {
EuiPanel,
} from '@elastic/eui';
import { css } from '@emotion/css';
import { useKibana } from '../../hooks/use_kibana';
import type { UseKnowledgeBaseResult } from '../../hooks/use_knowledge_base';
import { useKibana } from '../hooks/use_kibana';
import type { UseKnowledgeBaseResult } from '../hooks/use_knowledge_base';
const panelContainerClassName = css`
width: 330px;
@ -47,10 +47,9 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<EuiDescriptionList>
<EuiDescriptionListTitle>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel',
{ defaultMessage: 'Issues' }
)}
{i18n.translate('xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel', {
defaultMessage: 'Issues',
})}
</EuiDescriptionListTitle>
<EuiSpacer size="s" />
@ -61,7 +60,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
<li>
<EuiIcon type="alert" color="subdued" />{' '}
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel"
id="xpack.aiAssistant.welcomeMessage.modelIsNotDeployedLabel"
defaultMessage="Model {modelName} is not deployed"
values={{
modelName: <EuiCode>{modelName}</EuiCode>,
@ -75,7 +74,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
<li>
<EuiIcon type="alert" color="subdued" />{' '}
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel"
id="xpack.aiAssistant.welcomeMessage.modelIsNotStartedLabel"
defaultMessage="Deployment state of {modelName} is {deploymentState}"
values={{
modelName: <EuiCode>{modelName}</EuiCode>,
@ -92,7 +91,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
<li>
<EuiIcon type="alert" color="subdued" />{' '}
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel"
id="xpack.aiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel"
defaultMessage="Allocation state of {modelName} is {allocationState}"
values={{
modelName: <EuiCode>{modelName}</EuiCode>,
@ -113,7 +112,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
<EuiPanel hasBorder={false} hasShadow={false} paddingSize="m">
<EuiText color="subdued" size="xs">
<FormattedMessage
id="xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel"
id="xpack.aiAssistant.welcomeMessage.div.checkTrainedModelsToLabel"
defaultMessage="
{retryInstallingLink} or check {trainedModelsLink} to ensure {modelName} is deployed and running."
values={{
@ -124,7 +123,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
onClick={onRetryInstall}
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel',
'xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel',
{ defaultMessage: 'Retry install' }
)}
</EuiLink>
@ -133,13 +132,12 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
<EuiLink
data-test-subj="observabilityAiAssistantWelcomeMessageTrainedModelsLink"
external
href={http.basePath.prepend('/app/ml/trained_models')}
href={http?.basePath.prepend('/app/ml/trained_models')}
target="_blank"
>
{i18n.translate(
'xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel',
{ defaultMessage: 'Trained Models' }
)}
{i18n.translate('xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel', {
defaultMessage: 'Trained Models',
})}
</EuiLink>
),
}}

View file

@ -9,44 +9,44 @@ import { css } from '@emotion/css';
import { euiThemeVars } from '@kbn/ui-theme';
import React, { useEffect, useState } from 'react';
import ReactDOM from 'react-dom';
import { useAbortableAsync } from '@kbn/observability-ai-assistant-plugin/public';
import { ChatBody } from '../../components/chat/chat_body';
import { ChatInlineEditingContent } from '../../components/chat/chat_inline_edit';
import { ConversationList } from '../../components/chat/conversation_list';
import { useCurrentUser } from '../../hooks/use_current_user';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
import { useKnowledgeBase } from '../../hooks/use_knowledge_base';
import { useObservabilityAIAssistantParams } from '../../hooks/use_observability_ai_assistant_params';
import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router';
import { useObservabilityAIAssistantAppService } from '../../hooks/use_observability_ai_assistant_app_service';
import { useKibana } from '../../hooks/use_kibana';
import { useConversationKey } from '../../hooks/use_conversation_key';
import { useConversationList } from '../../hooks/use_conversation_list';
import { useKibana } from '../hooks/use_kibana';
import { ConversationList, ChatBody, ChatInlineEditingContent } from '../chat';
import { useConversationKey } from '../hooks/use_conversation_key';
import { useCurrentUser } from '../hooks/use_current_user';
import { useGenAIConnectors } from '../hooks/use_genai_connectors';
import { useKnowledgeBase } from '../hooks/use_knowledge_base';
import { useAIAssistantAppService } from '../hooks/use_ai_assistant_app_service';
import { useAbortableAsync } from '../hooks/use_abortable_async';
import { useConversationList } from '../hooks/use_conversation_list';
const SECOND_SLOT_CONTAINER_WIDTH = 400;
export function ConversationView() {
interface ConversationViewProps {
conversationId?: string;
navigateToConversation: (nextConversationId?: string) => void;
getConversationHref?: (conversationId: string) => string;
newConversationHref?: string;
}
export const ConversationView: React.FC<ConversationViewProps> = ({
conversationId,
navigateToConversation,
getConversationHref,
newConversationHref,
}) => {
const { euiTheme } = useEuiTheme();
const currentUser = useCurrentUser();
const service = useObservabilityAIAssistantAppService();
const service = useAIAssistantAppService();
const connectors = useGenAIConnectors();
const knowledgeBase = useKnowledgeBase();
const observabilityAIAssistantRouter = useObservabilityAIAssistantRouter();
const { path } = useObservabilityAIAssistantParams('/conversations/*');
const {
services: {
plugins: {
start: {
observabilityAIAssistant: { ObservabilityAIAssistantChatServiceContext },
},
},
observabilityAIAssistant: { ObservabilityAIAssistantChatServiceContext },
},
} = useKibana();
@ -57,8 +57,6 @@ export function ConversationView() {
[service]
);
const conversationId = 'conversationId' in path ? path.conversationId : undefined;
const { key: bodyKey, updateConversationIdInPlace } = useConversationKey(conversationId);
const [secondSlotContainer, setSecondSlotContainer] = useState<HTMLDivElement | null>(null);
@ -66,19 +64,6 @@ export function ConversationView() {
const conversationList = useConversationList();
function navigateToConversation(nextConversationId?: string) {
if (nextConversationId) {
observabilityAIAssistantRouter.push('/conversations/{conversationId}', {
path: {
conversationId: nextConversationId,
},
query: {},
});
} else {
observabilityAIAssistantRouter.push('/conversations/new', { path: {}, query: {} });
}
}
function handleRefreshConversations() {
conversationList.conversations.refresh();
}
@ -153,6 +138,9 @@ export function ConversationView() {
}
});
}}
newConversationHref={newConversationHref}
onConversationSelect={navigateToConversation}
getConversationHref={getConversationHref}
/>
<EuiSpacer size="s" />
</EuiFlexItem>
@ -176,6 +164,7 @@ export function ConversationView() {
knowledgeBase={knowledgeBase}
showLinkToConversationsApp={false}
onConversationUpdate={handleConversationUpdate}
navigateToConversation={navigateToConversation}
/>
<div className={sidebarContainerClass}>
@ -189,4 +178,4 @@ export function ConversationView() {
)}
</EuiFlexGroup>
);
}
};

View file

@ -0,0 +1,10 @@
/*
* 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.
*/
export * from './use_ai_assistant_app_service';
export * from './use_ai_assistant_chat_service';
export * from './use_knowledge_base';

View file

@ -0,0 +1,87 @@
/*
* 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 { isPromise } from '@kbn/std';
import { useEffect, useMemo, useRef, useState } from 'react';
interface State<T> {
error?: Error;
value?: T;
loading: boolean;
}
export type AbortableAsyncState<T> = (T extends Promise<infer TReturn>
? State<TReturn>
: State<T>) & { refresh: () => void };
export function useAbortableAsync<T>(
fn: ({}: { signal: AbortSignal }) => T | Promise<T>,
deps: any[],
options?: { clearValueOnNext?: boolean; defaultValue?: () => T }
): AbortableAsyncState<T> {
const clearValueOnNext = options?.clearValueOnNext;
const controllerRef = useRef(new AbortController());
const [refreshId, setRefreshId] = useState(0);
const [error, setError] = useState<Error>();
const [loading, setLoading] = useState(false);
const [value, setValue] = useState<T | undefined>(options?.defaultValue);
useEffect(() => {
controllerRef.current.abort();
const controller = new AbortController();
controllerRef.current = controller;
if (clearValueOnNext) {
setValue(undefined);
setError(undefined);
}
try {
const response = fn({ signal: controller.signal });
if (isPromise(response)) {
setLoading(true);
response
.then((nextValue) => {
setError(undefined);
setValue(nextValue);
})
.catch((err) => {
setValue(undefined);
setError(err);
})
.finally(() => setLoading(false));
} else {
setError(undefined);
setValue(response);
setLoading(false);
}
} catch (err) {
setValue(undefined);
setError(err);
setLoading(false);
}
return () => {
controller.abort();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps.concat(refreshId, clearValueOnNext));
return useMemo<AbortableAsyncState<T>>(() => {
return {
error,
loading,
value,
refresh: () => {
setRefreshId((id) => id + 1);
},
} as unknown as AbortableAsyncState<T>;
}, [error, value, loading]);
}

View file

@ -0,0 +1,20 @@
/*
* 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 { useKibana } from './use_kibana';
export function useAIAssistantAppService() {
const { services } = useKibana();
if (!services.observabilityAIAssistant?.service) {
throw new Error(
'AI Assistant Service is not available. Did you provide this service in your plugin contract?'
);
}
return services.observabilityAIAssistant.service;
}

View file

@ -0,0 +1,15 @@
/*
* 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 { useKibana } from './use_kibana';
export function useAIAssistantChatService() {
const {
services: { observabilityAIAssistant },
} = useKibana();
return observabilityAIAssistant.useObservabilityAIAssistantChatService();
}

View file

@ -19,9 +19,8 @@ import {
StreamingChatResponseEventType,
StreamingChatResponseEventWithoutError,
} from '@kbn/observability-ai-assistant-plugin/common';
import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider';
import { EMPTY_CONVERSATION_TITLE } from '../i18n';
import type { ObservabilityAIAssistantAppService } from '../service/create_app_service';
import type { AIAssistantAppService } from '../service/create_app_service';
import {
useConversation,
type UseConversationProps,
@ -35,9 +34,9 @@ import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
let hookResult: RenderHookResult<UseConversationProps, UseConversationResult>;
type MockedService = DeeplyMockedKeys<Omit<ObservabilityAIAssistantAppService, 'conversations'>> & {
type MockedService = DeeplyMockedKeys<Omit<AIAssistantAppService, 'conversations'>> & {
conversations: DeeplyMockedKeys<
Omit<ObservabilityAIAssistantAppService['conversations'], 'predefinedConversation$'>
Omit<AIAssistantAppService['conversations'], 'predefinedConversation$'>
> & {
predefinedConversation$: Observable<any>;
};
@ -66,18 +65,15 @@ const useKibanaMockServices = {
uiSettings: {
get: jest.fn(),
},
plugins: {
start: {
observabilityAIAssistant: {
useChat: createUseChat({
notifications: {
toasts: {
addError: addErrorMock,
},
} as unknown as NotificationsStart,
}),
},
},
observabilityAIAssistant: {
useChat: createUseChat({
notifications: {
toasts: {
addError: addErrorMock,
},
} as unknown as NotificationsStart,
}),
service: mockService,
},
};
@ -87,11 +83,7 @@ describe('useConversation', () => {
beforeEach(() => {
jest.clearAllMocks();
wrapper = ({ children }: PropsWithChildren<unknown>) => (
<KibanaContextProvider services={useKibanaMockServices}>
<ObservabilityAIAssistantAppServiceProvider value={mockService}>
{children}
</ObservabilityAIAssistantAppServiceProvider>
</KibanaContextProvider>
<KibanaContextProvider services={useKibanaMockServices}>{children}</KibanaContextProvider>
);
});

View file

@ -12,16 +12,14 @@ import type {
ConversationCreateRequest,
Message,
} from '@kbn/observability-ai-assistant-plugin/common';
import {
ObservabilityAIAssistantChatService,
useAbortableAsync,
} from '@kbn/observability-ai-assistant-plugin/public';
import type { ObservabilityAIAssistantChatService } from '@kbn/observability-ai-assistant-plugin/public';
import type { AbortableAsyncState } from '@kbn/observability-ai-assistant-plugin/public';
import type { UseChatResult } from '@kbn/observability-ai-assistant-plugin/public';
import { EMPTY_CONVERSATION_TITLE } from '../i18n';
import { useAIAssistantAppService } from './use_ai_assistant_app_service';
import { useKibana } from './use_kibana';
import { useOnce } from './use_once';
import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service';
import { useAbortableAsync } from './use_abortable_async';
function createNewConversation({
title = EMPTY_CONVERSATION_TITLE,
@ -62,17 +60,13 @@ export function useConversation({
connectorId,
onConversationUpdate,
}: UseConversationProps): UseConversationResult {
const service = useObservabilityAIAssistantAppService();
const service = useAIAssistantAppService();
const { scope } = service;
const {
services: {
notifications,
plugins: {
start: {
observabilityAIAssistant: { useChat },
},
},
observabilityAIAssistant: { useChat },
},
} = useKibana();
@ -106,8 +100,8 @@ export function useConversation({
},
})
.catch((err) => {
notifications.toasts.addError(err, {
title: i18n.translate('xpack.observabilityAiAssistant.errorUpdatingConversation', {
notifications!.toasts.addError(err, {
title: i18n.translate('xpack.aiAssistant.errorUpdatingConversation', {
defaultMessage: 'Could not update conversation',
}),
});

View file

@ -12,9 +12,8 @@ import {
type Conversation,
useAbortableAsync,
} from '@kbn/observability-ai-assistant-plugin/public';
import { useAIAssistantAppService } from './use_ai_assistant_app_service';
import { useKibana } from './use_kibana';
import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service';
export interface UseConversationListResult {
isLoading: boolean;
conversations: AbortableAsyncState<{ conversations: Conversation[] }>;
@ -22,7 +21,7 @@ export interface UseConversationListResult {
}
export function useConversationList(): UseConversationListResult {
const service = useObservabilityAIAssistantAppService();
const service = useAIAssistantAppService();
const [isUpdatingList, setIsUpdatingList] = useState(false);
@ -62,8 +61,8 @@ export function useConversationList(): UseConversationListResult {
conversations.refresh();
} catch (err) {
notifications.toasts.addError(err, {
title: i18n.translate('xpack.observabilityAiAssistant.flyout.failedToDeleteConversation', {
notifications!.toasts.addError(err, {
title: i18n.translate('xpack.aiAssistant.flyout.failedToDeleteConversation', {
defaultMessage: 'Could not delete conversation',
}),
});

View file

@ -5,9 +5,9 @@
* 2.0.
*/
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { AuthenticatedUser } from '@kbn/security-plugin/common';
import { useEffect, useState } from 'react';
import { useKibana } from './use_kibana';
export function useCurrentUser() {
const {
@ -19,7 +19,7 @@ export function useCurrentUser() {
useEffect(() => {
const getCurrentUser = async () => {
try {
const authenticatedUser = await security.authc.getCurrentUser();
const authenticatedUser = await security!.authc.getCurrentUser();
setUser(authenticatedUser);
} catch {
setUser(undefined);

View file

@ -5,16 +5,13 @@
* 2.0.
*/
import { useKibana } from './use_kibana';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import { AIAssistantPluginStartDependencies } from '../types';
export function useGenAIConnectors() {
const {
services: {
plugins: {
start: { observabilityAIAssistant },
},
},
} = useKibana();
services: { observabilityAIAssistant },
} = useKibana<AIAssistantPluginStartDependencies>();
return observabilityAIAssistant.useGenAIConnectors();
}

View file

@ -7,7 +7,7 @@
import { useEffect, useMemo, useState } from 'react';
import { monaco } from '@kbn/monaco';
import { createInitializedObject } from '../utils/create_initialized_object';
import { useObservabilityAIAssistantChatService } from './use_observability_ai_assistant_chat_service';
import { useAIAssistantChatService } from './use_ai_assistant_chat_service';
import { safeJsonParse } from '../utils/safe_json_parse';
const { editor, languages, Uri } = monaco;
@ -19,7 +19,7 @@ export const useJsonEditorModel = ({
functionName: string | undefined;
initialJson?: string | undefined;
}) => {
const chatService = useObservabilityAIAssistantChatService();
const chatService = useAIAssistantChatService();
const functionDefinition = chatService.getFunctions().find((func) => func.name === functionName);

View file

@ -0,0 +1,13 @@
/*
* 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 { useKibana } from '@kbn/kibana-react-plugin/public';
import { AIAssistantPluginStartDependencies } from '../types';
const useTypedKibana = () => useKibana<AIAssistantPluginStartDependencies>();
export { useTypedKibana as useKibana };

View file

@ -15,7 +15,7 @@ import {
useAbortableAsync,
} from '@kbn/observability-ai-assistant-plugin/public';
import { useKibana } from './use_kibana';
import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service';
import { useAIAssistantAppService } from './use_ai_assistant_app_service';
export interface UseKnowledgeBaseResult {
status: AbortableAsyncState<{
@ -31,13 +31,8 @@ export interface UseKnowledgeBaseResult {
}
export function useKnowledgeBase(): UseKnowledgeBaseResult {
const {
notifications: { toasts },
plugins: {
start: { ml },
},
} = useKibana().services;
const service = useObservabilityAIAssistantAppService();
const { notifications, ml } = useKibana().services;
const service = useAIAssistantAppService();
const status = useAbortableAsync(
({ signal }) => {
@ -75,8 +70,8 @@ export function useKnowledgeBase(): UseKnowledgeBaseResult {
return install();
}
setInstallError(error);
toasts.addError(error, {
title: i18n.translate('xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase', {
notifications!.toasts.addError(error, {
title: i18n.translate('xpack.aiAssistant.errorSettingUpKnowledgeBase', {
defaultMessage: 'Could not set up Knowledge Base',
}),
});
@ -92,5 +87,5 @@ export function useKnowledgeBase(): UseKnowledgeBaseResult {
isInstalling,
installError,
};
}, [status, isInstalling, installError, service, ml.mlApi?.savedObjects, toasts]);
}, [status, isInstalling, installError, service, ml, notifications]);
}

View file

@ -0,0 +1,36 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { ILicense, LicenseType } from '@kbn/licensing-plugin/public';
import { useCallback } from 'react';
import useObservable from 'react-use/lib/useObservable';
import { useKibana } from './use_kibana';
interface UseLicenseReturnValue {
getLicense: () => ILicense | null;
hasAtLeast: (level: LicenseType) => boolean | undefined;
}
export const useLicense = (): UseLicenseReturnValue => {
const {
services: { licensing },
} = useKibana();
const license = useObservable<ILicense | null>(licensing.license$);
return {
getLicense: () => license ?? null,
hasAtLeast: useCallback(
(level: LicenseType) => {
if (!license) return;
return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level);
},
[license]
),
};
};

View file

@ -11,11 +11,7 @@ const LICENSE_MANAGEMENT_LOCATOR = 'LICENSE_MANAGEMENT_LOCATOR';
export const useLicenseManagementLocator = () => {
const {
services: {
plugins: {
start: { share },
},
},
services: { share },
} = useKibana();
const locator = share.url.locators.get(LICENSE_MANAGEMENT_LOCATOR);

View file

@ -0,0 +1,77 @@
/*
* 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 { renderHook, act } from '@testing-library/react-hooks';
import { useLocalStorage } from './use_local_storage';
describe('useLocalStorage', () => {
const key = 'testKey';
const defaultValue = 'defaultValue';
beforeEach(() => {
localStorage.clear();
});
it('should return the default value when local storage is empty', () => {
const { result } = renderHook(() => useLocalStorage(key, defaultValue));
const [item] = result.current;
expect(item).toBe(defaultValue);
});
it('should return the stored value when local storage has a value', () => {
const storedValue = 'storedValue';
localStorage.setItem(key, JSON.stringify(storedValue));
const { result } = renderHook(() => useLocalStorage(key, defaultValue));
const [item] = result.current;
expect(item).toBe(storedValue);
});
it('should save the value to local storage', () => {
const { result } = renderHook(() => useLocalStorage(key, defaultValue));
const [, saveToStorage] = result.current;
const newValue = 'newValue';
act(() => {
saveToStorage(newValue);
});
expect(JSON.parse(localStorage.getItem(key) || '')).toBe(newValue);
});
it('should remove the value from local storage when the value is undefined', () => {
const { result } = renderHook(() => useLocalStorage(key, defaultValue));
const [, saveToStorage] = result.current;
act(() => {
saveToStorage(undefined as unknown as string);
});
expect(localStorage.getItem(key)).toBe(null);
});
it('should listen for storage events to window, and remove the listener upon unmount', () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
const { unmount } = renderHook(() => useLocalStorage(key, defaultValue));
expect(addEventListenerSpy).toHaveBeenCalled();
const eventTypes = addEventListenerSpy.mock.calls;
expect(eventTypes).toContainEqual(['storage', expect.any(Function)]);
unmount();
expect(removeEventListenerSpy).toHaveBeenCalled();
addEventListenerSpy.mockRestore();
removeEventListenerSpy.mockRestore();
});
});

View file

@ -0,0 +1,60 @@
/*
* 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 { useState, useEffect, useMemo, useCallback } from 'react';
export function useLocalStorage<T>(key: string, defaultValue: T) {
// This is necessary to fix a race condition issue.
// It guarantees that the latest value will be always returned after the value is updated
const [storageUpdate, setStorageUpdate] = useState(0);
const item = useMemo(() => {
return getFromStorage(key, defaultValue);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [key, storageUpdate, defaultValue]);
const saveToStorage = useCallback(
(value: T) => {
if (value === undefined) {
window.localStorage.removeItem(key);
} else {
window.localStorage.setItem(key, JSON.stringify(value));
setStorageUpdate(storageUpdate + 1);
}
},
[key, storageUpdate]
);
useEffect(() => {
function onUpdate(event: StorageEvent) {
if (event.key === key) {
setStorageUpdate(storageUpdate + 1);
}
}
window.addEventListener('storage', onUpdate);
return () => {
window.removeEventListener('storage', onUpdate);
};
}, [key, setStorageUpdate, storageUpdate]);
return useMemo(() => [item, saveToStorage] as const, [item, saveToStorage]);
}
function getFromStorage<T>(keyName: string, defaultValue: T) {
const storedItem = window.localStorage.getItem(keyName);
if (storedItem !== null) {
try {
return JSON.parse(storedItem) as T;
} catch (err) {
window.localStorage.removeItem(keyName);
// eslint-disable-next-line no-console
console.log(`Unable to decode: ${keyName}`);
}
}
return defaultValue;
}

View file

@ -13,7 +13,7 @@ export function useSimulatedFunctionCalling() {
services: { uiSettings },
} = useKibana();
const simulatedFunctionCallingEnabled = uiSettings.get<boolean>(
const simulatedFunctionCallingEnabled = uiSettings!.get<boolean>(
aiAssistantSimulatedFunctionCalling,
false
);

View file

@ -0,0 +1,20 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const ASSISTANT_SETUP_TITLE = i18n.translate('xpack.aiAssistant.assistantSetup.title', {
defaultMessage: 'Welcome to the Elastic AI Assistant',
});
export const EMPTY_CONVERSATION_TITLE = i18n.translate('xpack.aiAssistant.emptyConversationTitle', {
defaultMessage: 'New conversation',
});
export const UPGRADE_LICENSE_TITLE = i18n.translate('xpack.aiAssistant.incorrectLicense.title', {
defaultMessage: 'Upgrade your license',
});

View file

@ -0,0 +1,11 @@
/*
* 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.
*/
export * from './conversation/conversation_view';
export * from './service/create_app_service';
export * from './hooks';
export * from './chat';

View file

@ -8,13 +8,13 @@
import React from 'react';
import { ComponentStory, ComponentStoryObj } from '@storybook/react';
import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories';
import { PromptEditor as Component, PromptEditorProps } from './prompt_editor';
import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator.stories';
/*
JSON Schema validation in the PromptEditor compponent does not work
when rendering the component from within Storybook.
*/
export default {
component: Component,

View file

@ -14,10 +14,10 @@ import {
type TelemetryEventTypeWithPayload,
ObservabilityAIAssistantTelemetryEventType,
} from '@kbn/observability-ai-assistant-plugin/public';
import { useLastUsedPrompts } from '../hooks/use_last_used_prompts';
import { FunctionListPopover } from '../chat/function_list_popover';
import { PromptEditorFunction } from './prompt_editor_function';
import { PromptEditorNaturalLanguage } from './prompt_editor_natural_language';
import { useLastUsedPrompts } from '../../hooks/use_last_used_prompts';
export interface PromptEditorProps {
disabled: boolean;
@ -194,7 +194,7 @@ export function PromptEditor({
<EuiButtonIcon
data-test-subj="observabilityAiAssistantChatPromptEditorButtonIcon"
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatPromptEditor.euiButtonIcon.submitLabel',
'xpack.aiAssistant.chatPromptEditor.euiButtonIcon.submitLabel',
{ defaultMessage: 'Submit' }
)}
disabled={loading || disabled || invalid}

View file

@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n';
import { EuiCode, EuiPanel } from '@elastic/eui';
import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
import type { Message } from '@kbn/observability-ai-assistant-plugin/common';
import { useJsonEditorModel } from '../../hooks/use_json_editor_model';
import { useJsonEditorModel } from '../hooks/use_json_editor_model';
export interface Props {
functionName: string;
@ -92,7 +92,7 @@ export function PromptEditorFunction({
<EuiCode className={functionNameClassName}>{functionName}</EuiCode>
<CodeEditor
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel',
'xpack.aiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel',
{ defaultMessage: 'payloadEditor' }
)}
data-test-subj="observabilityAiAssistantChatPromptEditorCodeEditor"

View file

@ -122,7 +122,7 @@ export function PromptEditorNaturalLanguage({
disabled={disabled}
fullWidth
inputRef={textAreaRef}
placeholder={i18n.translate('xpack.observabilityAiAssistant.prompt.placeholder', {
placeholder={i18n.translate('xpack.aiAssistant.prompt.placeholder', {
defaultMessage: 'Send a message to the Assistant',
})}
resize="vertical"
@ -139,7 +139,7 @@ export function PromptEditorNaturalLanguage({
>
<EuiSelectable
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel',
'xpack.aiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel',
{ defaultMessage: 'Select an option' }
)}
className={selectableClassName}

View file

@ -9,8 +9,7 @@ import type {
ChatActionClickHandler,
Message,
} from '@kbn/observability-ai-assistant-plugin/public';
import { useObservabilityAIAssistantChatService } from '../hooks/use_observability_ai_assistant_chat_service';
import { useAIAssistantChatService } from './hooks';
interface Props {
name: string;
arguments: string | undefined;
@ -19,7 +18,7 @@ interface Props {
}
export function RenderFunction(props: Props) {
const chatService = useObservabilityAIAssistantChatService();
const chatService = useAIAssistantChatService();
return (
<>
{chatService.renderFunction(props.name, props.arguments, props.response, props.onActionClick)}

View file

@ -6,15 +6,15 @@
*/
import type { ObservabilityAIAssistantService } from '@kbn/observability-ai-assistant-plugin/public';
import type { ObservabilityAIAssistantAppPluginStartDependencies } from '../types';
import { AIAssistantPluginStartDependencies } from '../types';
export type ObservabilityAIAssistantAppService = ObservabilityAIAssistantService;
export type AIAssistantAppService = ObservabilityAIAssistantService;
export function createAppService({
pluginsStart,
}: {
pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies;
}): ObservabilityAIAssistantAppService {
pluginsStart: AIAssistantPluginStartDependencies;
}): AIAssistantAppService {
return {
...pluginsStart.observabilityAIAssistant.service,
};

View file

@ -0,0 +1,20 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import type { LicensingPluginStart } from '@kbn/licensing-plugin/public';
import type { MlPluginStart } from '@kbn/ml-plugin/public';
import type { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public';
import type { SharePluginStart } from '@kbn/share-plugin/public';
import type { TriggersAndActionsUIPublicPluginStart } from '@kbn/triggers-actions-ui-plugin/public';
export interface AIAssistantPluginStartDependencies {
licensing: LicensingPluginStart;
ml: MlPluginStart;
observabilityAIAssistant: ObservabilityAIAssistantPublicStart;
share: SharePluginStart;
triggersActionsUi: TriggersAndActionsUIPublicPluginStart;
}

View file

@ -10,21 +10,18 @@ import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public';
export function getRoleTranslation(role: MessageRole) {
if (role === MessageRole.User) {
return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.user.label', {
return i18n.translate('xpack.aiAssistant.chatTimeline.messages.user.label', {
defaultMessage: 'You',
});
}
if (role === MessageRole.System) {
return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.system.label', {
return i18n.translate('xpack.aiAssistant.chatTimeline.messages.system.label', {
defaultMessage: 'System',
});
}
return i18n.translate(
'xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label',
{
defaultMessage: 'Elastic Assistant',
}
);
return i18n.translate('xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label', {
defaultMessage: 'Elastic Assistant',
});
}

View file

@ -23,12 +23,8 @@ function Providers({ children }: { children: React.ReactElement }) {
<IntlProvider locale="en" messages={{}}>
<KibanaContextProvider
services={{
plugins: {
start: {
observabilityAIAssistant: {
useObservabilityAIAssistantChatService: () => mockChatService,
},
},
observabilityAIAssistant: {
useObservabilityAIAssistantChatService: () => mockChatService,
},
}}
>

View file

@ -18,9 +18,9 @@ import {
ObservabilityAIAssistantChatService,
} from '@kbn/observability-ai-assistant-plugin/public';
import type { ChatActionClickPayload } from '@kbn/observability-ai-assistant-plugin/public';
import type { ChatTimelineItem } from '../components/chat/chat_timeline';
import { RenderFunction } from '../components/render_function';
import { RenderFunction } from '../render_function';
import { safeJsonParse } from './safe_json_parse';
import type { ChatTimelineItem } from '../chat/chat_timeline';
function convertMessageToMarkdownCodeBlock(message: Message['message']) {
let value: object;
@ -95,7 +95,7 @@ export function getTimelineItemsfromConversation({
'@timestamp': new Date().toISOString(),
message: { role: MessageRole.User },
},
title: i18n.translate('xpack.observabilityAiAssistant.conversationStartTitle', {
title: i18n.translate('xpack.aiAssistant.conversationStartTitle', {
defaultMessage: 'started a conversation',
}),
role: MessageRole.User,
@ -149,7 +149,7 @@ export function getTimelineItemsfromConversation({
title = !isError ? (
<FormattedMessage
id="xpack.observabilityAiAssistant.userExecutedFunctionEvent"
id="xpack.aiAssistant.userExecutedFunctionEvent"
defaultMessage="executed the function {functionName}"
values={{
functionName: <FunctionName name={message.message.name} />,
@ -157,7 +157,7 @@ export function getTimelineItemsfromConversation({
/>
) : (
<FormattedMessage
id="xpack.observabilityAiAssistant.executedFunctionFailureEvent"
id="xpack.aiAssistant.executedFunctionFailureEvent"
defaultMessage="failed to execute the function {functionName}"
values={{
functionName: <FunctionName name={message.message.name} />,
@ -189,7 +189,7 @@ export function getTimelineItemsfromConversation({
// User suggested a function
title = (
<FormattedMessage
id="xpack.observabilityAiAssistant.userSuggestedFunctionEvent"
id="xpack.aiAssistant.userSuggestedFunctionEvent"
defaultMessage="requested the function {functionName}"
values={{
functionName: <FunctionName name={message.message.function_call.name} />,
@ -222,7 +222,7 @@ export function getTimelineItemsfromConversation({
if (message.message.function_call?.name) {
title = (
<FormattedMessage
id="xpack.observabilityAiAssistant.suggestedFunctionEvent"
id="xpack.aiAssistant.suggestedFunctionEvent"
defaultMessage="requested the function {functionName}"
values={{
functionName: <FunctionName name={message.message.function_call.name} />,

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import { HttpStart } from '@kbn/core/public';
export function getSettingsHref(http: HttpStart) {
return http!.basePath.prepend(`/app/management/kibana/observabilityAiAssistantManagement`);
export function nonNullable<T>(v: T): v is NonNullable<T> {
return v !== null && v !== undefined;
}

View file

@ -5,10 +5,10 @@
* 2.0.
*/
import { HttpStart } from '@kbn/core/public';
export function getSettingsKnowledgeBaseHref(http: HttpStart) {
return http!.basePath.prepend(
`/app/management/kibana/observabilityAiAssistantManagement?tab=knowledge_base`
);
export function safeJsonParse(jsonStr: string) {
try {
return JSON.parse(jsonStr);
} catch (err) {
return jsonStr;
}
}

View file

@ -13,10 +13,9 @@ import {
} from '@kbn/observability-ai-assistant-plugin/public';
import { Subject } from 'rxjs';
import { coreMock } from '@kbn/core/public/mocks';
import { ObservabilityAIAssistantAppService } from '../service/create_app_service';
import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider';
import { AIAssistantAppService } from '../service/create_app_service';
const mockService: ObservabilityAIAssistantAppService = {
const mockService: AIAssistantAppService = {
...createStorybookService(),
};
@ -38,25 +37,17 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) {
licensing: {
license$: new Subject(),
},
// observabilityAIAssistant: {
// ObservabilityAIAssistantChatServiceContext,
// ObservabilityAIAssistantMultipaneFlyoutContext,
// },
plugins: {
start: {
observabilityAIAssistant: {
ObservabilityAIAssistantMultipaneFlyoutContext,
},
triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} },
},
observabilityAIAssistant: {
ObservabilityAIAssistantChatServiceContext,
ObservabilityAIAssistantMultipaneFlyoutContext,
service: mockService,
},
triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} },
}}
>
<ObservabilityAIAssistantAppServiceProvider value={mockService}>
<ObservabilityAIAssistantChatServiceContext.Provider value={mockChatService}>
<Story />
</ObservabilityAIAssistantChatServiceContext.Provider>
</ObservabilityAIAssistantAppServiceProvider>
<ObservabilityAIAssistantChatServiceContext.Provider value={mockChatService}>
<Story />
</ObservabilityAIAssistantChatServiceContext.Provider>
</KibanaContextProvider>
);
}

View file

@ -0,0 +1,39 @@
{
"extends": "../../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types",
"types": [
"jest",
"node",
"react",
"@emotion/react/types/css-prop"
]
},
"include": [
"**/*.ts",
"**/*.tsx",
],
"exclude": [
"target/**/*"
],
"kbn_references": [
"@kbn/core-http-browser",
"@kbn/i18n",
"@kbn/triggers-actions-ui-plugin",
"@kbn/actions-plugin",
"@kbn/i18n-react",
"@kbn/ui-theme",
"@kbn/core",
"@kbn/observability-ai-assistant-plugin",
"@kbn/security-plugin",
"@kbn/user-profile-components",
"@kbn/std",
"@kbn/utility-types-jest",
"@kbn/kibana-react-plugin",
"@kbn/monaco",
"@kbn/licensing-plugin",
"@kbn/code-editor",
"@kbn/ml-plugin",
"@kbn/share-plugin",
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Some files were not shown because too many files have changed in this diff Show more