mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
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:
parent
e6c2750151
commit
8a3a05927b
130 changed files with 1414 additions and 978 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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?)',
|
||||
|
|
|
@ -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"],
|
||||
|
|
|
@ -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",
|
||||
|
|
3
x-pack/packages/kbn-ai-assistant/README.md
Normal file
3
x-pack/packages/kbn-ai-assistant/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/ai-assistant
|
||||
|
||||
Provides components, types and context to render the AI Assistant in plugins.
|
7
x-pack/packages/kbn-ai-assistant/index.ts
Normal file
7
x-pack/packages/kbn-ai-assistant/index.ts
Normal 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';
|
18
x-pack/packages/kbn-ai-assistant/jest.config.js
Normal file
18
x-pack/packages/kbn-ai-assistant/jest.config.js
Normal 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'],
|
||||
};
|
5
x-pack/packages/kbn-ai-assistant/kibana.jsonc
Normal file
5
x-pack/packages/kbn-ai-assistant/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"id": "@kbn/ai-assistant",
|
||||
"owner": "@elastic/search-kibana",
|
||||
"type": "shared-browser"
|
||||
}
|
7
x-pack/packages/kbn-ai-assistant/package.json
Normal file
7
x-pack/packages/kbn-ai-assistant/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/ai-assistant",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "Elastic License 2.0",
|
||||
"sideEffects": false
|
||||
}
|
9
x-pack/packages/kbn-ai-assistant/setup_tests.ts
Normal file
9
x-pack/packages/kbn-ai-assistant/setup_tests.ts
Normal 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 |
|
@ -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"
|
|
@ -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>
|
|
@ -18,7 +18,7 @@ export function NewChatButton(
|
|||
iconType="newChat"
|
||||
{...nextProps}
|
||||
>
|
||||
{i18n.translate('xpack.observabilityAiAssistant.newChatButton', {
|
||||
{i18n.translate('xpack.aiAssistant.newChatButton', {
|
||||
defaultMessage: 'New chat',
|
||||
})}
|
||||
</EuiButton>
|
|
@ -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>
|
||||
),
|
|
@ -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,
|
|
@ -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}>
|
|
@ -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'}
|
|
@ -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({});
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
}
|
|
@ -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;
|
|
@ -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}
|
|
@ -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}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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.`,
|
||||
},
|
||||
}),
|
|
@ -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'> {
|
|
@ -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>;
|
|
@ -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>
|
||||
) => {
|
|
@ -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.",
|
||||
})}
|
|
@ -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 {
|
|
@ -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',
|
||||
}),
|
||||
}}
|
|
@ -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>
|
11
x-pack/packages/kbn-ai-assistant/src/chat/index.ts
Normal file
11
x-pack/packages/kbn-ai-assistant/src/chat/index.ts
Normal 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';
|
|
@ -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> = {
|
|
@ -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>
|
|
@ -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.',
|
||||
})}
|
|
@ -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();
|
||||
|
|
@ -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
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
||||
),
|
||||
}}
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
};
|
10
x-pack/packages/kbn-ai-assistant/src/hooks/index.ts
Normal file
10
x-pack/packages/kbn-ai-assistant/src/hooks/index.ts
Normal 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';
|
|
@ -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]);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
});
|
||||
|
|
@ -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',
|
||||
}),
|
||||
});
|
|
@ -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',
|
||||
}),
|
||||
});
|
|
@ -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);
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
|
13
x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts
Normal file
13
x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts
Normal 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 };
|
|
@ -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]);
|
||||
}
|
36
x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts
Normal file
36
x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts
Normal 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]
|
||||
),
|
||||
};
|
||||
};
|
|
@ -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);
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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;
|
||||
}
|
|
@ -13,7 +13,7 @@ export function useSimulatedFunctionCalling() {
|
|||
services: { uiSettings },
|
||||
} = useKibana();
|
||||
|
||||
const simulatedFunctionCallingEnabled = uiSettings.get<boolean>(
|
||||
const simulatedFunctionCallingEnabled = uiSettings!.get<boolean>(
|
||||
aiAssistantSimulatedFunctionCalling,
|
||||
false
|
||||
);
|
20
x-pack/packages/kbn-ai-assistant/src/i18n.ts
Normal file
20
x-pack/packages/kbn-ai-assistant/src/i18n.ts
Normal 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',
|
||||
});
|
11
x-pack/packages/kbn-ai-assistant/src/index.ts
Normal file
11
x-pack/packages/kbn-ai-assistant/src/index.ts
Normal 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';
|
|
@ -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,
|
|
@ -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}
|
|
@ -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"
|
|
@ -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}
|
|
@ -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)}
|
|
@ -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,
|
||||
};
|
20
x-pack/packages/kbn-ai-assistant/src/types/index.ts
Normal file
20
x-pack/packages/kbn-ai-assistant/src/types/index.ts
Normal 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;
|
||||
}
|
|
@ -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',
|
||||
});
|
||||
}
|
|
@ -23,12 +23,8 @@ function Providers({ children }: { children: React.ReactElement }) {
|
|||
<IntlProvider locale="en" messages={{}}>
|
||||
<KibanaContextProvider
|
||||
services={{
|
||||
plugins: {
|
||||
start: {
|
||||
observabilityAIAssistant: {
|
||||
useObservabilityAIAssistantChatService: () => mockChatService,
|
||||
},
|
||||
},
|
||||
observabilityAIAssistant: {
|
||||
useObservabilityAIAssistantChatService: () => mockChatService,
|
||||
},
|
||||
}}
|
||||
>
|
|
@ -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} />,
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
);
|
||||
}
|
39
x-pack/packages/kbn-ai-assistant/tsconfig.json
Normal file
39
x-pack/packages/kbn-ai-assistant/tsconfig.json
Normal 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
Loading…
Add table
Add a link
Reference in a new issue