From 5c2bba5bbe383915358bb255f466d5f469fd2ca3 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Thu, 26 Sep 2024 13:26:50 +0200 Subject: [PATCH 01/23] Create ai-assistant packages --- package.json | 4 ++- .../kbn-ai-assistant-common/README.md | 3 ++ .../packages/kbn-ai-assistant-common/index.ts | 6 ++++ .../kbn-ai-assistant-common/jest.config.js | 19 +++++++++++ .../kbn-ai-assistant-common/kibana.jsonc | 5 +++ .../kbn-ai-assistant-common/package.json | 7 ++++ .../kbn-ai-assistant-common/setup_tests.ts | 9 ++++++ .../kbn-ai-assistant-common/tsconfig.json | 32 +++++++++++++++++++ x-pack/packages/kbn-ai-assistant/README.md | 3 ++ x-pack/packages/kbn-ai-assistant/index.ts | 6 ++++ .../packages/kbn-ai-assistant/jest.config.js | 18 +++++++++++ x-pack/packages/kbn-ai-assistant/kibana.jsonc | 5 +++ x-pack/packages/kbn-ai-assistant/package.json | 7 ++++ .../packages/kbn-ai-assistant/setup_tests.ts | 9 ++++++ .../packages/kbn-ai-assistant/tsconfig.json | 32 +++++++++++++++++++ 15 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 x-pack/packages/kbn-ai-assistant-common/README.md create mode 100644 x-pack/packages/kbn-ai-assistant-common/index.ts create mode 100644 x-pack/packages/kbn-ai-assistant-common/jest.config.js create mode 100644 x-pack/packages/kbn-ai-assistant-common/kibana.jsonc create mode 100644 x-pack/packages/kbn-ai-assistant-common/package.json create mode 100644 x-pack/packages/kbn-ai-assistant-common/setup_tests.ts create mode 100644 x-pack/packages/kbn-ai-assistant-common/tsconfig.json create mode 100644 x-pack/packages/kbn-ai-assistant/README.md create mode 100644 x-pack/packages/kbn-ai-assistant/index.ts create mode 100644 x-pack/packages/kbn-ai-assistant/jest.config.js create mode 100644 x-pack/packages/kbn-ai-assistant/kibana.jsonc create mode 100644 x-pack/packages/kbn-ai-assistant/package.json create mode 100644 x-pack/packages/kbn-ai-assistant/setup_tests.ts create mode 100644 x-pack/packages/kbn-ai-assistant/tsconfig.json diff --git a/package.json b/package.json index ccf36e1a7d53b..3f8eb394b8d15 100644 --- a/package.json +++ b/package.json @@ -156,6 +156,8 @@ "@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-common": "link:x-pack/packages/kbn-ai-assistant-common", "@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", @@ -1840,4 +1842,4 @@ "zod-to-json-schema": "^3.23.0" }, "packageManager": "yarn@1.22.21" -} \ No newline at end of file +} diff --git a/x-pack/packages/kbn-ai-assistant-common/README.md b/x-pack/packages/kbn-ai-assistant-common/README.md new file mode 100644 index 0000000000000..7117396d8ede6 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant-common/README.md @@ -0,0 +1,3 @@ +# @kbn/ai-assistant + +Provides types and other shared code for the AI assistant that is not limited to the browser. diff --git a/x-pack/packages/kbn-ai-assistant-common/index.ts b/x-pack/packages/kbn-ai-assistant-common/index.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant-common/index.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ diff --git a/x-pack/packages/kbn-ai-assistant-common/jest.config.js b/x-pack/packages/kbn-ai-assistant-common/jest.config.js new file mode 100644 index 0000000000000..6682c06e5e764 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant-common/jest.config.js @@ -0,0 +1,19 @@ +/* + * 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: + '/target/kibana-coverage/jest/x-pack/packages/kbn_ai_assistant_common_src', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/packages/kbn-ai-assistant-common/src/**/*.{ts,tsx}', + '!/x-pack/packages/kbn-ai-assistant-common/src/*.test.{ts,tsx}', + ], + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/packages/kbn-ai-assistant-common'], +}; diff --git a/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc b/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc new file mode 100644 index 0000000000000..8babdaccbd2df --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "id": "@kbn/ai-assistant-common", + "owner": "@elastic/search-kibana", + "type": "shared-common" +} diff --git a/x-pack/packages/kbn-ai-assistant-common/package.json b/x-pack/packages/kbn-ai-assistant-common/package.json new file mode 100644 index 0000000000000..49198d7ae7535 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant-common/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/ai-assistant-common", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0", + "sideEffects": false +} diff --git a/x-pack/packages/kbn-ai-assistant-common/setup_tests.ts b/x-pack/packages/kbn-ai-assistant-common/setup_tests.ts new file mode 100644 index 0000000000000..72e0edd0d07f7 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant-common/setup_tests.ts @@ -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'; diff --git a/x-pack/packages/kbn-ai-assistant-common/tsconfig.json b/x-pack/packages/kbn-ai-assistant-common/tsconfig.json new file mode 100644 index 0000000000000..d512efb71cfdc --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant-common/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core-http-browser", + "@kbn/i18n", + "@kbn/stack-connectors-plugin", + "@kbn/triggers-actions-ui-plugin", + "@kbn/core-http-browser-mocks", + "@kbn/cases-plugin", + "@kbn/actions-plugin", + "@kbn/core-notifications-browser", + "@kbn/i18n-react", + "@kbn/ui-theme", + "@kbn/core-doc-links-browser", + "@kbn/core", + ] +} diff --git a/x-pack/packages/kbn-ai-assistant/README.md b/x-pack/packages/kbn-ai-assistant/README.md new file mode 100644 index 0000000000000..d28f93431baa9 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/README.md @@ -0,0 +1,3 @@ +# @kbn/ai-assistant + +Provides components, types and context to render the AI Assistant in plugins. diff --git a/x-pack/packages/kbn-ai-assistant/index.ts b/x-pack/packages/kbn-ai-assistant/index.ts new file mode 100644 index 0000000000000..1fec1c76430eb --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/index.ts @@ -0,0 +1,6 @@ +/* + * 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. + */ diff --git a/x-pack/packages/kbn-ai-assistant/jest.config.js b/x-pack/packages/kbn-ai-assistant/jest.config.js new file mode 100644 index 0000000000000..37d30bae01fa9 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/jest.config.js @@ -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: '/target/kibana-coverage/jest/x-pack/packages/kbn_ai_assistant_src', + coverageReporters: ['text', 'html'], + collectCoverageFrom: [ + '/x-pack/packages/kbn-ai-assistant/src/**/*.{ts,tsx}', + '!/x-pack/packages/kbn-ai-assistant/src/*.test.{ts,tsx}', + ], + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/packages/kbn-ai-assistant'], +}; diff --git a/x-pack/packages/kbn-ai-assistant/kibana.jsonc b/x-pack/packages/kbn-ai-assistant/kibana.jsonc new file mode 100644 index 0000000000000..4cddd90431e39 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "id": "@kbn/ai-assistant", + "owner": "@elastic/search-kibana", + "type": "shared-browser" +} diff --git a/x-pack/packages/kbn-ai-assistant/package.json b/x-pack/packages/kbn-ai-assistant/package.json new file mode 100644 index 0000000000000..159ed64f288fd --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/package.json @@ -0,0 +1,7 @@ +{ + "name": "@kbn/ai-assistant", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0", + "sideEffects": false +} diff --git a/x-pack/packages/kbn-ai-assistant/setup_tests.ts b/x-pack/packages/kbn-ai-assistant/setup_tests.ts new file mode 100644 index 0000000000000..72e0edd0d07f7 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/setup_tests.ts @@ -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'; diff --git a/x-pack/packages/kbn-ai-assistant/tsconfig.json b/x-pack/packages/kbn-ai-assistant/tsconfig.json new file mode 100644 index 0000000000000..d512efb71cfdc --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/tsconfig.json @@ -0,0 +1,32 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ], + "exclude": [ + "target/**/*" + ], + "kbn_references": [ + "@kbn/core-http-browser", + "@kbn/i18n", + "@kbn/stack-connectors-plugin", + "@kbn/triggers-actions-ui-plugin", + "@kbn/core-http-browser-mocks", + "@kbn/cases-plugin", + "@kbn/actions-plugin", + "@kbn/core-notifications-browser", + "@kbn/i18n-react", + "@kbn/ui-theme", + "@kbn/core-doc-links-browser", + "@kbn/core", + ] +} From f0869fd4624870705fc65af233a358fb25425d5c Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 1 Oct 2024 13:13:38 +0200 Subject: [PATCH 02/23] Extract Observability AI Assistant into ai-assistant package --- package.json | 3 +- tsconfig.base.json | 2 + x-pack/.i18nrc.json | 67 ++++++++++---- .../kbn-ai-assistant-common/README.md | 3 - .../kbn-ai-assistant-common/jest.config.js | 19 ---- .../kbn-ai-assistant-common/kibana.jsonc | 5 - .../kbn-ai-assistant-common/package.json | 7 -- .../kbn-ai-assistant-common/tsconfig.json | 32 ------- x-pack/packages/kbn-ai-assistant/index.ts | 1 + .../src/assets/elastic_ai_assistant.png | Bin 0 -> 95099 bytes .../buttons/ask_assistant_button.stories.tsx | 0 .../src}/buttons/ask_assistant_button.tsx | 31 +++---- ...xpand_conversation_list_button.stories.tsx | 0 .../hide_expand_conversation_list_button.tsx | 4 +- .../src}/buttons/new_chat_button.stories.tsx | 0 .../src}/buttons/new_chat_button.tsx | 2 +- .../src}/chat/chat_actions_menu.tsx | 65 ++++++------- .../src}/chat/chat_body.stories.tsx | 4 +- .../src}/chat/chat_body.test.tsx | 0 .../kbn-ai-assistant/src}/chat/chat_body.tsx | 29 +++--- .../src}/chat/chat_consolidated_items.tsx | 13 +-- .../src}/chat/chat_flyout.stories.tsx | 5 +- .../src}/chat/chat_flyout.tsx | 37 ++++---- .../src}/chat/chat_header.stories.tsx | 0 .../src}/chat/chat_header.tsx | 50 +++++----- .../src}/chat/chat_inline_edit.tsx | 0 .../kbn-ai-assistant/src}/chat/chat_item.tsx | 2 +- .../src}/chat/chat_item_actions.tsx | 36 +++----- .../src}/chat/chat_item_avatar.tsx | 0 ...chat_item_content_inline_prompt_editor.tsx | 0 .../src}/chat/chat_item_title.tsx | 0 .../src}/chat/chat_timeline.stories.tsx | 8 +- .../src}/chat/chat_timeline.tsx | 4 +- .../src}/chat/conversation_list.stories.tsx | 4 +- .../src}/chat/conversation_list.tsx | 65 +++++-------- .../kbn-ai-assistant/src}/chat/disclaimer.tsx | 2 +- .../chat/function_list_popover.stories.tsx | 2 +- .../src}/chat/function_list_popover.tsx | 28 +++--- .../src}/chat/incorrect_license_panel.tsx | 19 ++-- .../src/chat}/index.ts | 5 + .../chat/knowledge_base_callout.stories.tsx | 2 +- .../src}/chat/knowledge_base_callout.tsx | 14 +-- .../simulated_function_calling_callout.tsx | 2 +- .../src}/chat/starter_prompts.tsx | 8 +- .../src}/chat/welcome_message.tsx | 26 +++--- .../src}/chat/welcome_message_connectors.tsx | 37 +++----- .../chat/welcome_message_knowledge_base.tsx | 37 +++----- ...ssage_knowledge_base_setup_error_panel.tsx | 30 +++--- .../ai_assistant_app_service_provider.tsx | 15 +++ .../src/conversation}/conversation_view.tsx | 69 ++++++-------- .../kbn-ai-assistant/src/hooks/index.ts | 10 ++ .../src/hooks/use_abortable_async.ts | 87 ++++++++++++++++++ .../src/hooks/use_ai_assistant_app_service.ts | 20 ++++ .../hooks/use_ai_assistant_chat_service.ts | 15 +++ .../src}/hooks/use_confirm_modal.tsx | 0 .../src}/hooks/use_conversation.test.tsx | 14 +-- .../src}/hooks/use_conversation.ts | 22 ++--- .../src}/hooks/use_conversation_key.ts | 0 .../src}/hooks/use_conversation_list.ts | 9 +- .../src}/hooks/use_current_user.ts | 4 +- .../src}/hooks/use_genai_connectors.ts | 11 +-- .../src}/hooks/use_json_editor_model.ts | 6 +- .../kbn-ai-assistant/src/hooks/use_kibana.ts | 13 +++ .../src}/hooks/use_knowledge_base.tsx | 17 ++-- .../src}/hooks/use_last_used_prompts.ts | 0 .../kbn-ai-assistant/src/hooks/use_license.ts | 36 ++++++++ .../hooks/use_license_management_locator.ts | 6 +- .../src/hooks/use_local_storage.test.ts | 77 ++++++++++++++++ .../src/hooks/use_local_storage.ts | 60 ++++++++++++ .../kbn-ai-assistant/src}/hooks/use_once.ts | 4 + .../hooks/use_simulated_function_calling.ts | 2 +- x-pack/packages/kbn-ai-assistant/src/i18n.ts | 20 ++++ x-pack/packages/kbn-ai-assistant/src/index.ts | 12 +++ .../prompt_editor/prompt_editor.stories.tsx | 4 +- .../src}/prompt_editor/prompt_editor.tsx | 4 +- .../prompt_editor/prompt_editor_function.tsx | 4 +- .../prompt_editor_natural_language.tsx | 4 +- .../kbn-ai-assistant/src}/render_function.tsx | 5 +- .../src}/service/create_app_service.ts | 8 +- .../kbn-ai-assistant/src/types/index.ts | 20 ++++ .../src}/utils/get_role_translation.ts | 13 +-- ..._timeline_items_from_conversation.test.tsx | 2 +- .../get_timeline_items_from_conversation.tsx | 14 +-- .../src/utils/non_nullable.ts} | 5 +- .../src/utils/safe_json_parse.ts | 14 +++ .../utils/storybook_decorator.stories.tsx | 25 ++--- .../public/hooks/use_once.ts | 3 + .../public/types.ts | 1 + .../public/application.tsx | 8 +- .../public/components/nav_control/index.tsx | 17 +++- .../nav_control/lazy_nav_control.tsx | 4 +- ...lity_ai_assistant_app_service_provider.tsx | 16 ---- .../__storybook_mocks__/use_knowledge_base.ts | 2 +- .../hooks/use_nav_control_screen_context.ts | 4 +- ..._observability_ai_assistant_app_service.ts | 20 ---- .../public/i18n.ts | 27 ------ .../public/plugin.tsx | 4 +- .../public/routes/config.tsx | 6 +- .../conversation_view_with_props.tsx | 43 +++++++++ .../public/utils/shared_providers.tsx | 9 +- .../tsconfig.json | 15 ++- x-pack/plugins/search_assistant/kibana.jsonc | 10 +- .../search_assistant/public/application.tsx | 18 ++-- .../public/components/page_template.tsx | 35 +++++++ .../public/components/routes/config.tsx | 59 ++++++++++++ .../conversation_view_with_props.tsx | 43 +++++++++ .../public/components/routes/router.tsx | 20 ++++ .../public/components/search_assistant.tsx | 24 ----- .../public/hooks/use_ai_assistant_params.ts | 14 +++ .../public/hooks/use_ai_assistant_router.ts | 52 +++++++++++ .../plugins/search_assistant/public/index.ts | 19 +++- .../plugins/search_assistant/public/plugin.ts | 56 ++++++++++- .../search_assistant/public/router.tsx | 20 ---- .../plugins/search_assistant/public/types.ts | 2 - .../plugins/search_assistant/server/config.ts | 10 +- yarn.lock | 4 + 116 files changed, 1225 insertions(+), 701 deletions(-) delete mode 100644 x-pack/packages/kbn-ai-assistant-common/README.md delete mode 100644 x-pack/packages/kbn-ai-assistant-common/jest.config.js delete mode 100644 x-pack/packages/kbn-ai-assistant-common/kibana.jsonc delete mode 100644 x-pack/packages/kbn-ai-assistant-common/package.json delete mode 100644 x-pack/packages/kbn-ai-assistant-common/tsconfig.json create mode 100644 x-pack/packages/kbn-ai-assistant/src/assets/elastic_ai_assistant.png rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/buttons/ask_assistant_button.stories.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/buttons/ask_assistant_button.tsx (71%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/buttons/hide_expand_conversation_list_button.stories.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/buttons/hide_expand_conversation_list_button.tsx (83%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/buttons/new_chat_button.stories.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/buttons/new_chat_button.tsx (92%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_actions_menu.tsx (65%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_body.stories.tsx (98%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_body.test.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_body.tsx (94%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_consolidated_items.tsx (89%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_flyout.stories.tsx (83%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_flyout.tsx (88%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_header.stories.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_header.tsx (82%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_inline_edit.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_item.tsx (98%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_item_actions.tsx (73%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_item_avatar.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_item_content_inline_prompt_editor.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_item_title.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_timeline.stories.tsx (98%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/chat_timeline.tsx (96%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/conversation_list.stories.tsx (92%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/conversation_list.tsx (80%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/disclaimer.tsx (91%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/function_list_popover.stories.tsx (91%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/function_list_popover.tsx (85%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/incorrect_license_panel.tsx (77%) rename x-pack/packages/{kbn-ai-assistant-common => kbn-ai-assistant/src/chat}/index.ts (65%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/knowledge_base_callout.stories.tsx (95%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/knowledge_base_callout.tsx (85%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/simulated_function_calling_callout.tsx (90%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/starter_prompts.tsx (85%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/welcome_message.tsx (83%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/welcome_message_connectors.tsx (65%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/welcome_message_knowledge_base.tsx (81%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/chat/welcome_message_knowledge_base_setup_error_panel.tsx (80%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations => packages/kbn-ai-assistant/src/conversation}/conversation_view.tsx (71%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/index.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/use_abortable_async.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_chat_service.ts rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_confirm_modal.tsx (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_conversation.test.tsx (96%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_conversation.ts (90%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_conversation_key.ts (100%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_conversation_list.ts (86%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_current_user.ts (84%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_genai_connectors.ts (66%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_json_editor_model.ts (88%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_knowledge_base.tsx (82%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_last_used_prompts.ts (100%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_license_management_locator.ts (89%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.test.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.ts rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_once.ts (90%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/hooks/use_simulated_function_calling.ts (89%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/i18n.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/index.ts rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/prompt_editor/prompt_editor.stories.tsx (96%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/prompt_editor/prompt_editor.tsx (97%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/prompt_editor/prompt_editor_function.tsx (96%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/prompt_editor/prompt_editor_natural_language.tsx (95%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public/components => packages/kbn-ai-assistant/src}/render_function.tsx (80%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/service/create_app_service.ts (63%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/types/index.ts rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/utils/get_role_translation.ts (61%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/utils/get_timeline_items_from_conversation.test.tsx (99%) rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/utils/get_timeline_items_from_conversation.tsx (94%) rename x-pack/packages/{kbn-ai-assistant-common/setup_tests.ts => kbn-ai-assistant/src/utils/non_nullable.ts} (71%) create mode 100644 x-pack/packages/kbn-ai-assistant/src/utils/safe_json_parse.ts rename x-pack/{plugins/observability_solution/observability_ai_assistant_app/public => packages/kbn-ai-assistant/src}/utils/storybook_decorator.stories.tsx (64%) delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/context/observability_ai_assistant_app_service_provider.tsx delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_observability_ai_assistant_app_service.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/i18n.ts create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view_with_props.tsx create mode 100644 x-pack/plugins/search_assistant/public/components/page_template.tsx create mode 100644 x-pack/plugins/search_assistant/public/components/routes/config.tsx create mode 100644 x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx create mode 100644 x-pack/plugins/search_assistant/public/components/routes/router.tsx delete mode 100644 x-pack/plugins/search_assistant/public/components/search_assistant.tsx create mode 100644 x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts create mode 100644 x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts delete mode 100644 x-pack/plugins/search_assistant/public/router.tsx diff --git a/package.json b/package.json index 3f8eb394b8d15..84180d700f1f5 100644 --- a/package.json +++ b/package.json @@ -157,7 +157,6 @@ "@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-common": "link:x-pack/packages/kbn-ai-assistant-common", "@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", @@ -1842,4 +1841,4 @@ "zod-to-json-schema": "^3.23.0" }, "packageManager": "yarn@1.22.21" -} +} \ No newline at end of file diff --git a/tsconfig.base.json b/tsconfig.base.json index 7a66911a4bee5..d7f8277205f74 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,6 +16,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"], diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 97aa05deb4a42..b4ca9c10259b8 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -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", @@ -45,9 +46,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", @@ -69,9 +76,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", @@ -86,7 +97,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", @@ -96,12 +109,21 @@ "xpack.observabilityLogsExplorer": "plugins/observability_solution/observability_logs_explorer", "xpack.observability_onboarding": "plugins/observability_solution/observability_onboarding", "xpack.observabilityShared": "plugins/observability_solution/observability_shared", - "xpack.osquery": ["plugins/osquery"], + "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.searchHomepage": "plugins/search_homepage", @@ -111,7 +133,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", @@ -123,20 +148,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", diff --git a/x-pack/packages/kbn-ai-assistant-common/README.md b/x-pack/packages/kbn-ai-assistant-common/README.md deleted file mode 100644 index 7117396d8ede6..0000000000000 --- a/x-pack/packages/kbn-ai-assistant-common/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @kbn/ai-assistant - -Provides types and other shared code for the AI assistant that is not limited to the browser. diff --git a/x-pack/packages/kbn-ai-assistant-common/jest.config.js b/x-pack/packages/kbn-ai-assistant-common/jest.config.js deleted file mode 100644 index 6682c06e5e764..0000000000000 --- a/x-pack/packages/kbn-ai-assistant-common/jest.config.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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: - '/target/kibana-coverage/jest/x-pack/packages/kbn_ai_assistant_common_src', - coverageReporters: ['text', 'html'], - collectCoverageFrom: [ - '/x-pack/packages/kbn-ai-assistant-common/src/**/*.{ts,tsx}', - '!/x-pack/packages/kbn-ai-assistant-common/src/*.test.{ts,tsx}', - ], - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/x-pack/packages/kbn-ai-assistant-common'], -}; diff --git a/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc b/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc deleted file mode 100644 index 8babdaccbd2df..0000000000000 --- a/x-pack/packages/kbn-ai-assistant-common/kibana.jsonc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "id": "@kbn/ai-assistant-common", - "owner": "@elastic/search-kibana", - "type": "shared-common" -} diff --git a/x-pack/packages/kbn-ai-assistant-common/package.json b/x-pack/packages/kbn-ai-assistant-common/package.json deleted file mode 100644 index 49198d7ae7535..0000000000000 --- a/x-pack/packages/kbn-ai-assistant-common/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "name": "@kbn/ai-assistant-common", - "private": true, - "version": "1.0.0", - "license": "Elastic License 2.0", - "sideEffects": false -} diff --git a/x-pack/packages/kbn-ai-assistant-common/tsconfig.json b/x-pack/packages/kbn-ai-assistant-common/tsconfig.json deleted file mode 100644 index d512efb71cfdc..0000000000000 --- a/x-pack/packages/kbn-ai-assistant-common/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "target/types", - "types": [ - "jest", - "node", - "react" - ] - }, - "include": [ - "**/*.ts", - "**/*.tsx", - ], - "exclude": [ - "target/**/*" - ], - "kbn_references": [ - "@kbn/core-http-browser", - "@kbn/i18n", - "@kbn/stack-connectors-plugin", - "@kbn/triggers-actions-ui-plugin", - "@kbn/core-http-browser-mocks", - "@kbn/cases-plugin", - "@kbn/actions-plugin", - "@kbn/core-notifications-browser", - "@kbn/i18n-react", - "@kbn/ui-theme", - "@kbn/core-doc-links-browser", - "@kbn/core", - ] -} diff --git a/x-pack/packages/kbn-ai-assistant/index.ts b/x-pack/packages/kbn-ai-assistant/index.ts index 1fec1c76430eb..cf53082cfa4b0 100644 --- a/x-pack/packages/kbn-ai-assistant/index.ts +++ b/x-pack/packages/kbn-ai-assistant/index.ts @@ -4,3 +4,4 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +export * from './src'; diff --git a/x-pack/packages/kbn-ai-assistant/src/assets/elastic_ai_assistant.png b/x-pack/packages/kbn-ai-assistant/src/assets/elastic_ai_assistant.png new file mode 100644 index 0000000000000000000000000000000000000000..af1064557968369b9d426fde8eb9891240436f55 GIT binary patch literal 95099 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≧cyL3ADuVDC5UQB;*_fCcrl zBa0g#X97R}&>Oh+iyaKqNuY5)fMR_FS`#5EQE#n7>)}?s{JBHOSw=;8mnr5H_$aP+ z;N{QmXCA%qRd~>~KZM*>)HfN<`P+qvkiIr06ga=Dr=-wyA+v}Aqn+(JDOt!Z2^~2K zFUb_~Q~a|1Ei8ZZe)=-#hAnFzLLH2+LdQyj6B!NMe%o-oxf7dvTJfu%ChR9?q?w`{ zO}t{?AEmqbxh^kiGR@dIs+wHWAEI9qaa81hZ4aO6pni#s=SN%h!SxyKF^e~pb*Ug*`F#l8hAWp(?cU>aqS${d!0v6CKX{n)4l<4qd)#Ja9!Yh}d_xKUiJbWuf0@a99 z8CVUrj6-}im@4#|PCbi$Jn^ypbZO~V>)Y-Q@DPVZgAu9FBpy>kyDfT?J#P^)7HLkH(!1D1M`>Z|j2xvR z>wKpNcTmqnX2Z?#sNvx?$n)=}h^K)OhcgL4`Y6IVO2=ziJU&4nBVm;kU#Bt|yF_Xw zW^$qKWj!SW9}1ut(axAgU0=PQ8cF-i9i^0@3|2TTX&%^NX15q`3!|w5oMI!rALf4jt+^x{YDn9 zsO&;P7IiPEt3AbCKVouC)QuL3bK(g%s&iU#U49?_r85JIX+}L#gB8)I>;{j)apnd?Pi|Nq-3rjdo~M-J;d185 zdoK4rK9Aa$+|RV~>x}I(y@95Kw^7f!lh!7)PBxo|3#Sw>hnjKDExbS0OqcW{SAMm5 z-8kLahRhpEUp#>qNiKosVar1sDRcT zbkZia$Hj-FN;ZuiZP{bPwTppyg;Wsdn0hDGQ%_tz-$@3z^f(eY+F6Ac8}n(g zMqo0E=E>~V034*ZBa4dXZ?@yuuNn+zXDQu{i<;(=&{BZqzQ9P^<*EC zD@qQG`t!^^!*z`EVII0q_lv6&?uz2j)2HZCK%SyaIo+9ihRbIZ?N8r2xfDo?aweC) z>D)O{j5H;=UA5BMWaB43hTY%zo}pKeS(J~>pZp3n^hl%M$4TSIvb4j zY$Nqc6vR)i)uv6kHXbUQ2qYfxucFF+Do4`^lhUI zb)SV}Xe3EFcG7&fsuK9jv)O15*yyT$6a#i7h0M@aEv8EbChw#aXZU7Ee)XxmuJPd_(43ZywEU5{W>D?0ZbK_59J@BaKBW667OLAd)Q+4)hroIdO7S_^d-pIud* zhY#Ph7Im=z>Z!nbgj^CiS=&Qyh^%v@^!fq1s9(uoe!JQyCnTT%X$jCF1CewvNhsa59(qY37))LZ^2( zqJC>VoVf&I-1Vwu)KWmdEf1dTK6LEghppfLA?9CqEyXtx`nU*DK{kZ0o%=EGu8rtv zp!d;}&4@OiruSGcBh1o6?&&(%y}4Z6Mz-&gUyl>@RxBt1u9y=t7VJwN_zmvtA&yh+ z^D%<&>ql3kHEv~?h9Pbo=_w9K_20PDMCHSZOh|pk-Jo;^W1+yv-rTX|oPH)v0r4Gd z6kd~n@ShL_b;m7e-+2fXH?BwV>g8}1=A!(r8?fuE|Bh&v-=I^?!}r2Y@s@AN zTrBy}!?3wIex%`Fey%FQW5qCJF?K!Lq$+={s+d@1s=P^FNx-K3GZmZisA-Z zrb&c(**E$U@3Yxc{pp;8XAhNUaZ59dsKTSzN(sy4WWimShvql-fN{pK{0|?12l+Vg z)GrLz^d-v{2KjxI#lCFX2Nj-68+XXHr><6DJ{w{*XU(RslThOkta#u($SlZ#CpU+Vy$TROkOAWOyR3-Lz38Bg zK+f=cGKf<$l%5U?IVVXqVICiUeXk9_+FNK=!>G$Sw_SEW=t+cJC18CSqkqR~Vr#xi zmt8y44Z8FwkOn4~UY}i3HP&K3Fw>8y-iFyBosz^N5p*-2&9e91iTSr~Kr=efR$GU? z-+PMY6f!9;L!?DJVl!Lm9eC)B;_$%^{H&{)Zs&tkesvmRoC5kQnNKs}?Y(UbT4qB& zL5i0;Ds$=eHH+U-@_glrevZ>U?V#j>M7qot4lRm-c5*yS-9&PVUt zlo>k?A3*rfQRG%uVI zg;avIan~$QulMKAF(L}LGO(Y_gs;yoK{drTiXMDlK^0chVkbmyNgp9e9l=ZyEqSu( z{dM%3CaGFX^!H5b{|Razn-!y|E=Kp}QdS*2swC2p*G|33c02nbW=F(Qjng~YQMO_c zx_PC95vEuF&O_Mw#eYNF{v)W`un~FHRao-TN3i%q55ndl$fPL8w{R}?O*}llu+53Q z!DeStv~yx?`1`CQ_GdCdqadA4ngT;oKwR!iB57Ko zEe2WoWcBl-eK-~B4G_Z34%xcKMjG57p_mJtj47pW(4ZHOlc0nNn&PK2iA9{vLaAjj z=AF2r8&7@Bhoh4GX2@p6v|3RgTrja8GOTiw#-7lQ^+FHux)fh+T!`-b&cD(;IE)|s z!JnY|?i;c0@!!U_PyY>eZr%^uwHx7D^*B813(?PLyMt+?op-b=f!!xtQ9P%T9Fle< zn6J1ghh-|v#qJe~a1!K@D0ttK?I(<5FCPpOw8+Vtn;aDRH5t56Ua*ambWx;A5-`a; zSkyMYFGRmds>h^cOFApkb^Ebagc!F(mKY{yjSb}ZO0qA^B+iM%Yy41T7o}LIt0`{0 zPp4nsf#na~hy0sX^rpxTCxmUu-JHG8(N(H7A|Y z6qrs5ToS-Upu)$ArHZphJ143-e!QGy=JKO=NMI$0$C_N!prgFrz)vyH+(Ao^0B4oy zm|IvhrQkp#>oT&Ccf9#b(uB;gD%&??*IWo1qaMA)m0#KO|{fcB-Wd0s@hBp@s zD+7TwzP*=@lA@sRD|l(0qTTGF=9BoqpZy*_S8*IK9`s|=s|Bdw4gNussFMUg%(xt% zh&XWzxgkGpYs8gQf~}{&5`hxwk|>}bhl;_r?w}(xB}E=q=>jWlbmXRf8>yRaBZCS4 z!)hAJuO)>(iW4{vn7`_0qi8}4^`)NF0`);}OAmo0>Fy=JO5!{d3Bgyen_tcAwyR!x>8T@hoPo86Jl}-bc zNUUR{IdL-1d_~M*663%z_2cgKapcc)B0~Qcfd;?nkSnzt8s^zc@w7&Lg8odh`HHSC z4l&d(1M%gUbJnHUo^+ir9}3KPU?PTYFF)((FzaTQ2hWEBxRSG4Ie!;&*L8$4X-#zR zcDf8j#yj}k$wl)xw>mzqFiU80A*HUVp3b#?xG7H99uDG$&UReM`U+HyF3~3g15VAn zG?I3R=u~24ROSrdJ?Qtp&efi?o{`@9yb*9K8bRUJtI@sZ2DTaLZ+wjH+lIp}TZC4l019GOqS@lt1spC>qHf8=PRydrvDU(2j zvv`0YMN8CFJI#{5gWQ_az;m3vb&1aqLzxYeQJim>P5P+RztkLn!VQ?HT6O||`q~ndNKQ78qU>)soiqLVz)8ev z4?DxrMZ7HlC)sN92%A$=iOvJHG%+_L%bEq5e^tDcKqU|U}h{*O`z~17e)hixiAw1S4~D_u!-D8}RMAEG%{> z=+9@I8P5b7rR#9XQb4ccj81wz788Ih;biuDB7t9ebI{72RTmdB>ZhTcItdOs9;mT9 zaEIH2rTot3OcUXxew=Q|y<*`HOz(P^r|x6!Zd3Eej3)A=R!)lzqsV$!AS3%4Do#js z(Nt_yv04jYj4fX>Q$OA}(uXFnG) z*kApZ@QEe_>WVSu zD?G%r zB0-nKi{t%1e5lk;u(}6fO9`$i+JjBEpP&-$Npi~;Aw-k%BQLy)thr^#D#*ven{PI< zIKF8MGAoKG-Z=|)6pE!RD~Y!c+VQ6!h~a-c+ylFVO4uo+JBTETcIz(8B&KaI&aqqTWds@73f+7eaez2zu#;$RguxqF{`= zCyeg}XZQf#W9`5XS3W{-onCls=7efG)Jh`U;zu66vt@D|$jy8f#eK!pCy^qofXHtc zZCD;*H6u@ii}$m2oE=BvG)YuB+!ChqgM01_RrpMTi>ja83y|lTVU^y8#zyK1_OX;; z$t{r*tljQ`)d(8tq_~ASQk%SzV4{#3@9~7x+Fe*%R*!Gqo`ZX7u@mtXA}iDdS8*wI zLz)Rr@{nIM7q!27#o*2f;nGbG!D&C}DhJySWa3MYHsE)ED;o}XE`6c&82T?~>O1H_ z;KbpzBbVUBU*C&Z4|Q11Wxx!&dlC;xDlwD#!i+3LPL6!OgxeHtnPm=1^b6M|gKS56 zzg=E(2v>NnK@+)?9@0p?l1^y~Oa}#Ke5@S3XY$rrCFN0aLMq7x>ET4yL7fx%s#xvJ zq)Tx|hNW&v3$H79+cJm4;FM%>5;6b^^B8Udoh+iO-cqDI&3b;D0IH02rQZ82PUgZR zts9%O5XIhJe0Y5wzWh)Cw=QqTqT;A9!a1gjB;T1NYYnS(Lb9$vW5G(ef&uCqw4jh6 zMD_@CH z#hDsR3E>KHOM>+Fy6wNVA;$MY=HZ}a$lw)Kx+Hdc7C&BI_ppz|6E!XC=%NM>EUd-q z@`Lo&E74wr(g!O_Gn;W+O&I(3M(|Qo2Ig4$khgj%7w!@q`sW`ayDA5n$tc3H44gU{ zMQ4{Cg^M!rns=wYnpEH2Xbys3%z==G99QA2Br718S6qxZG6TR=6 zmiAJy^)sF<%%#%n&mtidk|n<-62^TlH(I&tUQEF9CY@+;og&91LGPXnp-mA`lzdJp z!%8u7@FY1M#7>t`%l@juxL62dvcLLIppY{B5}1SBwRu z_YXQxAxbcrTC=;k&G#}yL6kxG_CD2sIahnQxN0SpD1kAG2uQ3}Cu0J+$+aNs%t)%0 z`R*Db24@0gzFI@vLY|s&hxCrZeVJI8AHn~AA6*F?uf$>R0vzA;8lqH!KhfJj)(AgX zg2g2#@csvvlmkxtf30wG$TBU~K?^g;w3tyD0U=&XrWPBk>a zmWVY%@St*J(tQoR?4IW4Vb|LyVelKs=}6K_$^;}Xa!0)EA5l>?)aTq3=BXvbE0sB2 zrXC0ZYAT6y5@sN}O8Y^4rsyEP`(_EAt`BmdEyvtXeH!cYPUHIi?_t}DFXR5oBUqZ) zga+GV$PB%JCH}8ti5i!=kqz3;I;a-~jNVxXqr3q^NHNw`kZCBP_MW0MwlL=%&8WjX zW;{EQe^`%b58d@s*F?!%A&*XUL@BY(s5vJJ;+MdNd8hH!x0b_}DTjjt4K;F2|4CC| z>L@Uyfr(xVdaTAOifY!8<@+~e{qEyLRzsKJy0()FuP#o0k7bGVD$5QoK>RMH<~`RY zign0T)^;$RW()4huG9X`1$NlOI%)H|48_YKUe z{|34lckPAZk7G|PA6`z#Nv?vK`2;2}H(>Q66#Hbm40rQd#wJ72Rd8r?ymL6_|Am`0 z%~ws1hZ!)uyF-BAxekHNqL*Qu+!YJouEEP4wW4D(4*&Eigt7?{gl*8H&sMcIVv-) zV=^?wC2lKX$3D}Yf&1rlq11btii`xCgLc5@K1t7??DGWY-c470iG8{N**$My|AOCx zi{_&~OCzjvR^kV{a!~4$0yLLYx-btkMp3lXhrF761JFq4VKS4Z@=NVA6g@;GS+fPm zO%d>yqN~6~H7%I}6r0M6ZZ9_o0wfg-gm_y{nPH9{;r(2v8H>nG04SXwQzVi@7w-|i zpU3;C1G23a4L>+Z;FwEKwprw6(E%Ux8xD}9>Sv`Kubr`6Zm+Dv4RiPK^w7_!i&Y03 zIW+}YtanL}BASD62cE@N_h*q4Yl1uPy(sAVC)`A_Ubfqgm$E+wcVaGlf$#F2weVR9 zxqJv&a|fV_wmT|sh$LbWBuVeYAs4EwXFNDXdM7NKBsWZlojw9Pfi+fBK&g-VCkc9T z9SulQtK^`-{{u`8Cz( z|I*)$EzvNZ2n7ufuMZKZ+{;fr`N}#@^6hkTLKG;`h9NL8IchI9r5~2d|9sU2QPOEJ z1tiXK(TWBwIjC)4Msb{WYo>T+@O@{Gxo_u+!+8=*>z{?UXA=^R)i_qR5dn73S68^pr zmxPa;Npwj=_5eNimbXxJq|?K*C5+3=yQe*g_Ilv0jB?Z!d zVn2er>t!5sl%dx`>mX*?>R1n3;uy!;VT3GM2xSxyYsmV?$n{Kp&N<*E>757^c*{J< zroR(8kz?~sf}V5p1pZK!*fH@y+;Hj7CL5ESK)J6nn}H1J=R_Saf+XHpqMyE6GUK?f zrWF5<=)Gv-B>4~(*rXp2qVC8VdVT$GD!={_6=9Fl66evb zcKk1ja{k2Yqw=8z3&=^Sq6AxWf+Edox91{DS-YqUOf3^m+9Y>Q5-p`f2a{E{DXN;d zCD+`&PB=N*se?Qe7tVRiYm(FAN&a&k_VF z#zOqg+Xv2203$Th(t)Dvb`HGxxA0y;qX?pxfj7_y9!?a_wh)}mytnTnP|op zwA^V&t@m*pabArpf>}%bAfR=P~5<<(q=?fG7_y!y50%P5=TVhoEX80Gn-r! zO?3P(4vqj)cWn=SuIR>L33cXzfTh4lRTjggQX_o;-=DdjK1fqwswgm{fr+LriExfl zTqCU#Ey4_~W$dzk7U-eXO#&~GlVYK7ldCAYY3F3;;r)_}YtoeZn&RX{^rB7`MF+}Z zkw)34X&I!Mv|BUQ1Sjn_9o(H-Ir&cnXGS^&(J&`6vgyNu761U{o6ZrM164Z4q z!>ZD4%u`VGLJJnHZU{CrG-XoRlm`fr(XxI1ow&L-gz5!*@zE<6;@b!Fu!w=-`lxFZ zX^G(E){`h%UPAZcWL7Kt5lqtKZr~-9fq+OheTUqcQ;OH=wm->0h9?L(-i`+NK3NQq zayS~Ke3#|h*u z{4O4@xEFOt+OXGqFYXHdm|PLgG11qgwagpmBeU>6)F%qDBO@2Pvp$CphPR-y?N8an zYPLfjf-n9ar}R#I)jq>wM{#yWBT~6|y9V7s;u0$wToVD4R1ycrPBE2t4n^N}{?zG< zd`Au4oox3Z$1hmHqWC|$miSu6NafHt7d|Z)T zl&y6)`*Gx@29&LJav{C=2;(*cK8hrEQ%M?`VSHh29qwQLE4q(wqdpSB>4<;{E6U0m z;gRZT-B%rgx8o(`cYPiUb6-L*wj8f#f03IV2d;5aKO}MrwzgklapDwKC7aOUxC`Dy z2Q!gAn*v~{ImZ zb4c$}DjhclFA9$2Xf2+|LV4~ne6%uxzuV=*(wqc2)e~FgbZ%)1T#N!U8knf^n!<88 z$)!@EF=;Zc15aqr)yX@`d09$px;M438c*=vvrndSn40fYT?DA4IyZUs5U)g~@uRPC zBw?FzW-+SLRbC-YhhJ|jN2A||IoUmiS?L97rCGQ?3TM)ZP4jQYwu&`)WJ49vIh4fnzBgaw^wZGYM z3TvHNMkMDgyw@L%qi%C8cKy5^b8nzyHU&ExCQ16DkDrRJyZuT0!SZ^%f9WndRNHCv zo1ChR7?h|C_O&IW4HkMg+K67a#BY0HnR;TK%kSya_ZC&qy!r2?}UrI!9AJ4 zF~}i~z92apvd*D%$ulpp>OjtkyNCfzDef^WhJ>w#Ndkl7ju6YB+{Iao0CL7J5W}=3 zefrX-z>K%b5qb3vr#`7EO(0&!eD{oR0f3=CinTKxAJs+Ws-CLj1|SoJ-AD^}*XxcD$4aKd@Pg-8BO;`Tgb`JczqmUG+yRtYZg%svbWnz%f9eTsa%`3)+9Do2b-DtK3}no&sq5+QGzF%D z0+aB1OhvWRWsIK!7nq&KpZSP$(85Sr6m~RLqLt=XHpNL9_G#{xc`=a@Mz`-~ z75n}MEqSX6N=o7PEXVQEyHJz66^}1%z`l^3%6!yOAWHNsv0(3y4{%}BmUY+yX3YhW zKy-GK-UU=#6&Fc*TwMenEi|{M?^QGqki1C|O*Tb^3VtYsSXtYMqarSg%;6>K>ESbb z31E82b=eyWVji8VN!0Tam1L{v#r5c`wKP{@C!}@~gQ%=J*x9LG0;9b>Hq7&c@Q>HM zjsLjkpK-&y*WhN|BJ}E-kr7~xboba&zn4vFo&tQ9uGsHv z`x5=6l;PiPE0G!PV8n7SHrvWkAM7y*)nO|^Bzq+XXc>QZvl645It{%O#@SI2K*O|G zJC_Me>#|@Cuk$(;SPt+o(vljc7{{^0{z8*I<`~caGEPUrk1>{l57GO8^L`02UQNQ8AHD#SuPAj-S zELn(R0)$Nz%RIn{&GPWtPL4@CwJz6D)FTc{8#yJnP|S0RoEC|9j)em#@?_$LHxJ{> zAKQfL`P6kdZ&j4YMt+LwVs?s#V)*Q;Q~2z4KgEsJJE(Li^Ho{bQMo zZhCq>iDbAI`xpKNc4pm%c*o0_-|_%L1%FFjiZHyMxhU)lz}@*(ERXL;PJb;GcpET} zx+d9-wbLJYo#Uo}RY~z~M_4Nnm+iOEd$H4ZUYs3W8*fq`tdf<{wWaiW4gwh{+n&i- zP8W%(W8O*-!-(B4AGsELdhGQU=XewOEScitL-kpUrU#4u>-E-ik)Nbl

$JcxZ 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 literal 0 HcmV?d00001 diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.stories.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.stories.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx similarity index 71% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx index e13ba34110434..060e829b4907e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/ask_assistant_button.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx @@ -39,6 +39,10 @@ export type AskAssistantButtonProps = ( onClick: () => void; }; +const AI_ASSISTANT_LABEL = i18n.translate('xpack.aiAssistant.aiAssistantLabel', { + defaultMessage: 'AI Assistant', +}); + export function AskAssistantButton({ fill, flush, @@ -46,12 +50,9 @@ 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', + }); switch (variant) { case 'basic': @@ -84,23 +85,13 @@ export function AskAssistantButton({ return ( {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', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.stories.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.stories.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.tsx similarity index 92% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.tsx rename to x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.tsx index 75cede6344c59..3e515e87c2197 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/buttons/new_chat_button.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/buttons/new_chat_button.tsx @@ -18,7 +18,7 @@ export function NewChatButton( iconType="newChat" {...nextProps} > - {i18n.translate('xpack.observabilityAiAssistant.newChatButton', { + {i18n.translate('xpack.aiAssistant.newChatButton', { defaultMessage: 'New chat', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_actions_menu.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_actions_menu.tsx similarity index 65% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_actions_menu.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_actions_menu.tsx index 713a0d2311e3c..ac25fe6c3703a 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_actions_menu.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_actions_menu.tsx @@ -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={ @@ -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: (

- {i18n.translate('xpack.observabilityAiAssistant.chatHeader.actions.connector', { + {i18n.translate('xpack.aiAssistant.chatHeader.actions.connector', { defaultMessage: 'Connector', })}{' '} @@ -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', + })} ), diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx index 4e71ecdfd2c12..4dded4e8c6d10 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx @@ -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 '@kbn/observability-ai-assistant-app-plugin/public/utils/builders'; +import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { ChatBody as Component } from './chat_body'; -import { buildSystemMessage } from '../../utils/builders'; const meta: ComponentMeta = { component: Component, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.test.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.test.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.test.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_body.test.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx similarity index 94% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx index f54dc021938d5..c3989f6971fff 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_body.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.tsx @@ -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,6 +110,7 @@ export function ChatBody({ showLinkToConversationsApp, onConversationUpdate, onToggleFlyoutPositionMode, + navigateToConversation, }: { connectors: ReturnType; currentUser?: Pick; @@ -121,13 +122,14 @@ export function ChatBody({ showLinkToConversationsApp: boolean; onConversationUpdate: (conversation: { conversation: Conversation['conversation'] }) => void; onToggleFlyoutPositionMode?: (flyoutPositionMode: FlyoutPositionMode) => 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(); @@ -438,12 +440,12 @@ export function ChatBody({ - {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 }, @@ -468,12 +470,12 @@ export function ChatBody({ {conversation.error ? ( - {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 }, @@ -498,6 +500,7 @@ export function ChatBody({ saveTitle(newTitle); }} onToggleFlyoutPositionMode={onToggleFlyoutPositionMode} + navigateToConversation={navigateToConversation} /> diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_consolidated_items.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_consolidated_items.tsx similarity index 89% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_consolidated_items.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_consolidated_items.tsx index f31796b8812d2..5771b1fd297d7 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_consolidated_items.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_consolidated_items.tsx @@ -90,11 +90,11 @@ export function ChatConsolidatedItems({ > {!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={ {}, + navigateToConversation: () => {}, }; export const ChatFlyout = Template.bind({}); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.tsx similarity index 88% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.tsx index 4194f9a2ca0c4..44e80fb765edf 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_flyout.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.tsx @@ -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({ > diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.stories.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_header.stories.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx similarity index 82% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx index d4ede58040391..78d2a13156c48 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_header.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx @@ -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,6 +49,7 @@ export function ChatHeader({ onCopyConversation, onSaveTitle, onToggleFlyoutPositionMode, + navigateToConversation, }: { connectors: UseGenAIConnectorsResult; conversationId?: string; @@ -60,30 +60,29 @@ export function ChatHeader({ onCopyConversation: () => void; onSaveTitle: (title: string) => void; onToggleFlyoutPositionMode?: (newFlyoutPositionMode: FlyoutPositionMode) => 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 (conversationId) { - router.push('/conversations/{conversationId}', { - path: { - conversationId, - }, - query: {}, - }); - } else { - router.push('/conversations/new', { path: {}, query: {} }); - } - }; + // const handleNavigateToConversations = () => { + // if (conversationId) { + // router.push('/conversations/{conversationId}', { + // path: { + // conversationId, + // }, + // query: {}, + // }); + // } else { + // router.push('/conversations/new', { path: {}, query: {} }); + // } + // }; const handleToggleFlyoutPositionMode = () => { if (flyoutPositionMode) { @@ -120,10 +119,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 || @@ -156,11 +154,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' } ) } @@ -168,7 +166,7 @@ export function ChatHeader({ > navigateToConversation(conversationId)} /> } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_inline_edit.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_inline_edit.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_inline_edit.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_inline_edit.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item.tsx similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item.tsx index a1f5d5eb88d2d..23bdbdaea3593 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item.tsx @@ -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 { onActionClick: ChatActionClickHandler; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_actions.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_actions.tsx similarity index 73% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_actions.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_actions.tsx index 4995b0163b7be..cb1196baa6bc1 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_actions.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_actions.tsx @@ -46,12 +46,9 @@ export function ChatItemActions({ <> {canEdit ? ( setIsPopoverOpen(undefined)} > - {i18n.translate( - 'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful', - { - defaultMessage: 'Copied message', - } - )} + {i18n.translate('xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful', { + defaultMessage: 'Copied message', + })} ) : null} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_avatar.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_avatar.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_avatar.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_avatar.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_content_inline_prompt_editor.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_content_inline_prompt_editor.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_content_inline_prompt_editor.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_content_inline_prompt_editor.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_title.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_item_title.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx similarity index 98% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx index 88354f41ba293..1c4e3fdc115bd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx @@ -18,7 +18,7 @@ import { buildFunctionResponseMessage, buildSystemMessage, buildUserMessage, -} from '../../utils/builders'; +} from '@kbn/observability-ai-assistant-app-plugin/public/utils/builders'; import { ChatTimeline as Component, type ChatTimelineProps } from './chat_timeline'; export default { @@ -86,11 +86,11 @@ const defaultProps: ComponentProps = { 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.`, }, }), diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.tsx similarity index 96% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.tsx index ec2cf2ca68e7c..9b349f49f3904 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/chat_timeline.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.tsx @@ -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 { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx similarity index 92% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx index b0f72e80c5721..82367b9277558 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx @@ -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 '@kbn/observability-ai-assistant-app-plugin/public/utils/builders'; +import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { ConversationList as Component } from './conversation_list'; type ConversationListProps = React.ComponentProps; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.tsx similarity index 80% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.tsx index 1b26922bcaf69..e4a7022edc763 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/conversation_list.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.tsx @@ -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({ - {i18n.translate('xpack.observabilityAiAssistant.conversationList.title', { + {i18n.translate('xpack.aiAssistant.conversationList.title', { defaultMessage: 'Previously', })} @@ -147,12 +138,9 @@ export function ConversationList({ - {i18n.translate( - 'xpack.observabilityAiAssistant.conversationList.errorMessage', - { - defaultMessage: 'Failed to load', - } - )} + {i18n.translate('xpack.aiAssistant.conversationList.errorMessage', { + defaultMessage: 'Failed to load', + })} @@ -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 ? ( - {i18n.translate( - 'xpack.observabilityAiAssistant.conversationList.noConversations', - { - defaultMessage: 'No conversations', - } - )} + {i18n.translate('xpack.aiAssistant.conversationList.noConversations', { + defaultMessage: 'No conversations', + })} ) : null} @@ -228,7 +213,7 @@ export function ConversationList({ | MouseEvent ) => { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/disclaimer.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/disclaimer.tsx similarity index 91% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/disclaimer.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/disclaimer.tsx index e4eb5176469de..8f9c3abca0e71 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/disclaimer.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/disclaimer.tsx @@ -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.", })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.stories.tsx similarity index 91% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.stories.tsx index a8f1e23b8173d..62da0b2d14ff8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.stories.tsx @@ -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 { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.tsx similarity index 85% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.tsx index 16df72f48c91b..d24aae12fd8c6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/function_list_popover.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/function_list_popover.tsx @@ -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" > {UPGRADE_LICENSE_TITLE} - {i18n.translate('xpack.observabilityAiAssistant.incorrectLicense.body', { + {i18n.translate('xpack.aiAssistant.incorrectLicense.body', { defaultMessage: 'You need an Enterprise license to use the Elastic AI Assistant.', })} @@ -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', + })} @@ -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', })} diff --git a/x-pack/packages/kbn-ai-assistant-common/index.ts b/x-pack/packages/kbn-ai-assistant/src/chat/index.ts similarity index 65% rename from x-pack/packages/kbn-ai-assistant-common/index.ts rename to x-pack/packages/kbn-ai-assistant/src/chat/index.ts index 1fec1c76430eb..4b04d7dec81c1 100644 --- a/x-pack/packages/kbn-ai-assistant-common/index.ts +++ b/x-pack/packages/kbn-ai-assistant/src/chat/index.ts @@ -4,3 +4,8 @@ * 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'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.stories.tsx similarity index 95% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.stories.tsx index d66729dc75a3d..e87aa161d80c3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.stories.tsx @@ -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 = { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.tsx similarity index 85% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.tsx index 36d6842286aa8..abb296713b2d2 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/knowledge_base_callout.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/knowledge_base_callout.tsx @@ -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 - {i18n.translate('xpack.observabilityAiAssistant.checkingKbAvailability', { + {i18n.translate('xpack.aiAssistant.checkingKbAvailability', { defaultMessage: 'Checking availability of knowledge base', })} @@ -43,7 +43,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow color = 'danger'; content = ( - {i18n.translate('xpack.observabilityAiAssistant.failedToGetStatus', { + {i18n.translate('xpack.aiAssistant.failedToGetStatus', { defaultMessage: 'Failed to get model status.', })} @@ -53,7 +53,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow content = ( {' '} - {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 - {i18n.translate('xpack.observabilityAiAssistant.installingKb', { + {i18n.translate('xpack.aiAssistant.installingKb', { defaultMessage: 'Setting up the knowledge base', })} @@ -81,7 +81,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow color = 'danger'; content = ( - {i18n.translate('xpack.observabilityAiAssistant.failedToSetupKnowledgeBase', { + {i18n.translate('xpack.aiAssistant.failedToSetupKnowledgeBase', { defaultMessage: 'Failed to set up knowledge base.', })} @@ -96,7 +96,7 @@ export function KnowledgeBaseCallout({ knowledgeBase }: { knowledgeBase: UseKnow > {' '} - {i18n.translate('xpack.observabilityAiAssistant.setupKb', { + {i18n.translate('xpack.aiAssistant.setupKb', { defaultMessage: 'Improve your experience by setting up the knowledge base.', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/simulated_function_calling_callout.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx similarity index 90% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/simulated_function_calling_callout.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx index 41b14e683dd64..26eb589b25dfc 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/simulated_function_calling_callout.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/simulated_function_calling_callout.tsx @@ -17,7 +17,7 @@ export function SimulatedFunctionCallingCallout() { - {i18n.translate('xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel', { + {i18n.translate('xpack.aiAssistant.simulatedFunctionCallingCalloutLabel', { defaultMessage: 'Simulated function calling is enabled. You might see degradated performance.', })} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/starter_prompts.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/starter_prompts.tsx similarity index 85% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/starter_prompts.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/starter_prompts.tsx index 1f5402978d41d..faaecc0024135 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/starter_prompts.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/starter_prompts.tsx @@ -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(); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message.tsx similarity index 83% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/welcome_message.tsx index 18f4c5598c6fd..a449235ba44e6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message.tsx @@ -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 ( <> {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' } )} @@ -72,21 +72,15 @@ export function WelcomeMessageConnectors({ return !connectors.loading && connectors.connectors?.length === 0 && onSetupConnectorClick ? (
- {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.', + })} - {i18n.translate( - 'xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel', - { - defaultMessage: 'Set up GenAI connector', - } - )} + {i18n.translate('xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel', { + defaultMessage: 'Set up GenAI connector', + })}
diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base.tsx similarity index 81% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base.tsx index afdbed9ed4c43..72653473c41ae 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base.tsx @@ -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 ? ( <> - {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.', + })} @@ -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', + })} ) : null} @@ -112,7 +108,7 @@ export function WelcomeMessageKnowledgeBase({ <> {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel', + 'xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel', { defaultMessage: `Your Knowledge base hasn't been set up.` } )} @@ -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', + })} @@ -149,7 +142,7 @@ export function WelcomeMessageKnowledgeBase({ onClick={() => setIsPopoverOpen(!isPopoverOpen)} > {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel', + 'xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel', { defaultMessage: 'Inspect issues' } )} @@ -180,7 +173,7 @@ export function WelcomeMessageKnowledgeBase({ {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel', + 'xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel', { defaultMessage: 'Knowledge base successfully installed' } )} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base_setup_error_panel.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base_setup_error_panel.tsx similarity index 80% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base_setup_error_panel.tsx rename to x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base_setup_error_panel.tsx index a9a6fcff85240..eeff9c8afd7f3 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/chat/welcome_message_knowledge_base_setup_error_panel.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/welcome_message_knowledge_base_setup_error_panel.tsx @@ -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({ - {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel', - { defaultMessage: 'Issues' } - )} + {i18n.translate('xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel', { + defaultMessage: 'Issues', + })} @@ -61,7 +60,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
  • {' '} {modelName}, @@ -75,7 +74,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
  • {' '} {modelName}, @@ -92,7 +91,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({
  • {' '} {modelName}, @@ -113,7 +112,7 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({ {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel', + 'xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel', { defaultMessage: 'Retry install' } )} @@ -133,13 +132,12 @@ export function WelcomeMessageKnowledgeBaseSetupErrorPanel({ - {i18n.translate( - 'xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel', - { defaultMessage: 'Trained Models' } - )} + {i18n.translate('xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel', { + defaultMessage: 'Trained Models', + })} ), }} diff --git a/x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx b/x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx new file mode 100644 index 0000000000000..7f4f629fbf719 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx @@ -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 { createContext } from 'react'; +import type { AIAssistantAppService } from '../service/create_app_service'; + +export const AIAssistantAppServiceContext = createContext( + undefined +); + +export const AIAssistantAppServiceProvider = AIAssistantAppServiceContext.Provider; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx b/x-pack/packages/kbn-ai-assistant/src/conversation/conversation_view.tsx similarity index 71% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx rename to x-pack/packages/kbn-ai-assistant/src/conversation/conversation_view.tsx index da34c98b86fbc..260a7cb5c10ed 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/conversation/conversation_view.tsx @@ -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 = ({ + 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(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} /> @@ -176,6 +164,7 @@ export function ConversationView() { knowledgeBase={knowledgeBase} showLinkToConversationsApp={false} onConversationUpdate={handleConversationUpdate} + navigateToConversation={navigateToConversation} />
    @@ -189,4 +178,4 @@ export function ConversationView() { )} ); -} +}; diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/index.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/index.ts new file mode 100644 index 0000000000000..ee630d1caec82 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/index.ts @@ -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'; diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_abortable_async.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_abortable_async.ts new file mode 100644 index 0000000000000..433ca877b0f62 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_abortable_async.ts @@ -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 { + error?: Error; + value?: T; + loading: boolean; +} + +export type AbortableAsyncState = (T extends Promise + ? State + : State) & { refresh: () => void }; + +export function useAbortableAsync( + fn: ({}: { signal: AbortSignal }) => T | Promise, + deps: any[], + options?: { clearValueOnNext?: boolean; defaultValue?: () => T } +): AbortableAsyncState { + const clearValueOnNext = options?.clearValueOnNext; + + const controllerRef = useRef(new AbortController()); + + const [refreshId, setRefreshId] = useState(0); + + const [error, setError] = useState(); + const [loading, setLoading] = useState(false); + const [value, setValue] = useState(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>(() => { + return { + error, + loading, + value, + refresh: () => { + setRefreshId((id) => id + 1); + }, + } as unknown as AbortableAsyncState; + }, [error, value, loading]); +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts new file mode 100644 index 0000000000000..ba28c3c05b8d9 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts @@ -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 { useContext } from 'react'; +import { AIAssistantAppServiceContext } from '../context/ai_assistant_app_service_provider'; + +export function useAIAssistantAppService() { + const services = useContext(AIAssistantAppServiceContext); + + if (!services) { + throw new Error( + 'AIAssistantAppServiceContext not set. Did you wrap your component in `AIAssistantAppServiceProvider`?' + ); + } + + return services; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_chat_service.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_chat_service.ts new file mode 100644 index 0000000000000..a3eefef196901 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_chat_service.ts @@ -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(); +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_confirm_modal.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_confirm_modal.tsx similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_confirm_modal.tsx rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_confirm_modal.tsx diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx similarity index 96% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx index 150847a011207..fa36dda14989e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx @@ -19,25 +19,25 @@ import { StreamingChatResponseEventType, StreamingChatResponseEventWithoutError, } from '@kbn/observability-ai-assistant-plugin/common'; -import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider'; +import { AIAssistantAppServiceProvider } from '../context/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, type UseConversationResult, } from './use_conversation'; import { ChatState } from '@kbn/observability-ai-assistant-plugin/public'; -import { createMockChatService } from '../utils/create_mock_chat_service'; +import { createMockChatService } from '@kbn/observability-ai-assistant-app-plugin/public/utils/create_mock_chat_service'; import { createUseChat } from '@kbn/observability-ai-assistant-plugin/public/hooks/use_chat'; import type { NotificationsStart } from '@kbn/core/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; let hookResult: RenderHookResult; -type MockedService = DeeplyMockedKeys> & { +type MockedService = DeeplyMockedKeys> & { conversations: DeeplyMockedKeys< - Omit + Omit > & { predefinedConversation$: Observable; }; @@ -88,9 +88,9 @@ describe('useConversation', () => { jest.clearAllMocks(); wrapper = ({ children }: PropsWithChildren) => ( - + {children} - + ); }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts similarity index 90% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts index 617b1b302473f..a937051f65d7d 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts @@ -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,21 +60,17 @@ 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(); - const initialConversationId = useOnce(initialConversationIdFromProps); + const initialConversationId = initialConversationIdFromProps; const initialMessages = useOnce(initialMessagesFromProps); const initialTitle = useOnce(initialTitleFromProps); @@ -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', }), }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_key.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_key.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_key.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_key.ts diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_list.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts similarity index 86% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_list.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts index 6fa6bc02e7b35..d6ba75568dfd1 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_conversation_list.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts @@ -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', }), }); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_current_user.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts similarity index 84% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_current_user.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts index 82c13eb876117..6721ee0360cbd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_current_user.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts @@ -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); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_genai_connectors.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_genai_connectors.ts similarity index 66% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_genai_connectors.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_genai_connectors.ts index 1b105513a2323..642bf9488f186 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_genai_connectors.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_genai_connectors.ts @@ -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(); return observabilityAIAssistant.useGenAIConnectors(); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_json_editor_model.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts similarity index 88% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_json_editor_model.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts index 6f4535d84acef..97c22b07e8b96 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_json_editor_model.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts @@ -6,8 +6,8 @@ */ 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 { createInitializedObject } from '@kbn/observability-ai-assistant-app-plugin/public/utils/create_initialized_object'; +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); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts new file mode 100644 index 0000000000000..44aec48a06467 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_kibana.ts @@ -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(); + +export { useTypedKibana as useKibana }; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_knowledge_base.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx similarity index 82% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_knowledge_base.tsx rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx index bca9b38485695..f5410ed671b95 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_knowledge_base.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx @@ -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]); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_last_used_prompts.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_last_used_prompts.ts similarity index 100% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_last_used_prompts.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_last_used_prompts.ts diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts new file mode 100644 index 0000000000000..6d146274c7f4d --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license.ts @@ -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(licensing.license$); + + return { + getLicense: () => license ?? null, + hasAtLeast: useCallback( + (level: LicenseType) => { + if (!license) return; + + return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level); + }, + [license] + ), + }; +}; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_license_management_locator.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license_management_locator.ts similarity index 89% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_license_management_locator.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_license_management_locator.ts index 1d5dd04203352..7e650affa2ca5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_license_management_locator.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_license_management_locator.ts @@ -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); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.test.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.test.ts new file mode 100644 index 0000000000000..ab1d00392fdb9 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.test.ts @@ -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(); + }); +}); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.ts new file mode 100644 index 0000000000000..ea9e13163e4b0 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_local_storage.ts @@ -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(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(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; +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_once.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts similarity index 90% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_once.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts index 00dab01456af0..3ae1eb58b32b6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_once.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts @@ -10,6 +10,10 @@ import { useRef } from 'react'; export function useOnce(variable: T): T { const ref = useRef(variable); + if (!ref.current && variable) { + ref.current = variable; + } + if (ref.current !== variable) { // eslint-disable-next-line no-console console.trace( diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_simulated_function_calling.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts similarity index 89% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_simulated_function_calling.ts rename to x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts index 4d441b03a3ddc..93287fc58aa79 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_simulated_function_calling.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts @@ -13,7 +13,7 @@ export function useSimulatedFunctionCalling() { services: { uiSettings }, } = useKibana(); - const simulatedFunctionCallingEnabled = uiSettings.get( + const simulatedFunctionCallingEnabled = uiSettings?.get( aiAssistantSimulatedFunctionCalling, false ); diff --git a/x-pack/packages/kbn-ai-assistant/src/i18n.ts b/x-pack/packages/kbn-ai-assistant/src/i18n.ts new file mode 100644 index 0000000000000..5c5be1633a07a --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/i18n.ts @@ -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', +}); diff --git a/x-pack/packages/kbn-ai-assistant/src/index.ts b/x-pack/packages/kbn-ai-assistant/src/index.ts new file mode 100644 index 0000000000000..48dc54d98e267 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/index.ts @@ -0,0 +1,12 @@ +/* + * 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 './context/ai_assistant_app_service_provider'; +export * from './service/create_app_service'; +export * from './hooks'; +export * from './chat'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.stories.tsx similarity index 96% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.stories.tsx index f951653b152cc..ed2948e50f15e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.stories.tsx @@ -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, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.tsx b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.tsx similarity index 97% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.tsx rename to x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.tsx index db7f3a8f11888..cc2fe761d6176 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/prompt_editor/prompt_editor.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/prompt_editor/prompt_editor.tsx @@ -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({ {functionName} {chatService.renderFunction(props.name, props.arguments, props.response, props.onActionClick)} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/service/create_app_service.ts b/x-pack/packages/kbn-ai-assistant/src/service/create_app_service.ts similarity index 63% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/service/create_app_service.ts rename to x-pack/packages/kbn-ai-assistant/src/service/create_app_service.ts index dfb9b703bc4ed..bd01ab39a6d5c 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/service/create_app_service.ts +++ b/x-pack/packages/kbn-ai-assistant/src/service/create_app_service.ts @@ -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, }; diff --git a/x-pack/packages/kbn-ai-assistant/src/types/index.ts b/x-pack/packages/kbn-ai-assistant/src/types/index.ts new file mode 100644 index 0000000000000..afebbafd7e643 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/types/index.ts @@ -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; +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_role_translation.ts b/x-pack/packages/kbn-ai-assistant/src/utils/get_role_translation.ts similarity index 61% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_role_translation.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/get_role_translation.ts index f74c9f842e402..95421a089dea0 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_role_translation.ts +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_role_translation.ts @@ -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', + }); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx similarity index 99% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.test.tsx rename to x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx index 6fb7e1a323d08..ffe7efc785a31 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { getTimelineItemsfromConversation } from './get_timeline_items_from_conversation'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { ChatState, Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; -import { createMockChatService } from './create_mock_chat_service'; +import { createMockChatService } from '@kbn/observability-ai-assistant-app-plugin/public/utils/create_mock_chat_service'; import { KibanaContextProvider } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'; import { CONTEXT_FUNCTION_NAME } from '@kbn/observability-ai-assistant-plugin/server/functions/context'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx similarity index 94% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.tsx rename to x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx index 9a3fed770b944..999ac4f095025 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_timeline_items_from_conversation.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx @@ -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 ? ( , @@ -157,7 +157,7 @@ export function getTimelineItemsfromConversation({ /> ) : ( , @@ -189,7 +189,7 @@ export function getTimelineItemsfromConversation({ // User suggested a function title = ( , @@ -222,7 +222,7 @@ export function getTimelineItemsfromConversation({ if (message.message.function_call?.name) { title = ( , diff --git a/x-pack/packages/kbn-ai-assistant-common/setup_tests.ts b/x-pack/packages/kbn-ai-assistant/src/utils/non_nullable.ts similarity index 71% rename from x-pack/packages/kbn-ai-assistant-common/setup_tests.ts rename to x-pack/packages/kbn-ai-assistant/src/utils/non_nullable.ts index 72e0edd0d07f7..8618e44dbb823 100644 --- a/x-pack/packages/kbn-ai-assistant-common/setup_tests.ts +++ b/x-pack/packages/kbn-ai-assistant/src/utils/non_nullable.ts @@ -5,5 +5,6 @@ * 2.0. */ -// eslint-disable-next-line import/no-extraneous-dependencies -import '@testing-library/jest-dom'; +export function nonNullable(v: T): v is NonNullable { + return v !== null && v !== undefined; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/safe_json_parse.ts b/x-pack/packages/kbn-ai-assistant/src/utils/safe_json_parse.ts new file mode 100644 index 0000000000000..a4f2dfa5c2503 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/utils/safe_json_parse.ts @@ -0,0 +1,14 @@ +/* + * 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 function safeJsonParse(jsonStr: string) { + try { + return JSON.parse(jsonStr); + } catch (err) { + return jsonStr; + } +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx similarity index 64% rename from x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx rename to x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx index 9dc2e7057b951..8fb328eb8e972 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/storybook_decorator.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx @@ -13,10 +13,10 @@ 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 { AIAssistantAppServiceProvider } from '../context/ai_assistant_app_service_provider'; +import { AIAssistantAppService } from '../service/create_app_service'; -const mockService: ObservabilityAIAssistantAppService = { +const mockService: AIAssistantAppService = { ...createStorybookService(), }; @@ -38,25 +38,18 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) { licensing: { license$: new Subject(), }, - // observabilityAIAssistant: { - // ObservabilityAIAssistantChatServiceContext, - // ObservabilityAIAssistantMultipaneFlyoutContext, - // }, - plugins: { - start: { - observabilityAIAssistant: { - ObservabilityAIAssistantMultipaneFlyoutContext, - }, - triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} }, - }, + observabilityAIAssistant: { + ObservabilityAIAssistantChatServiceContext, + ObservabilityAIAssistantMultipaneFlyoutContext, }, + triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} }, }} > - + - + ); } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts index 00dab01456af0..8ed8c861dfbba 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts @@ -9,6 +9,9 @@ import { useRef } from 'react'; export function useOnce(variable: T): T { const ref = useRef(variable); + if (!ref.current && variable) { + ref.current = variable; + } if (ref.current !== variable) { // eslint-disable-next-line no-console diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts index 8b265d433f515..f9e55f4f2a9b8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts @@ -86,6 +86,7 @@ export interface ObservabilityAIAssistantChatService { onActionClick: ChatActionClickHandler, scope?: AssistantScope ) => React.ReactNode; + setScope: (scope: AssistantScope) => void; } export interface ObservabilityAIAssistantConversationService { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx index c554fc81d5de7..ce043ef395ee4 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/application.tsx @@ -9,10 +9,10 @@ import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; import type { History } from 'history'; import React from 'react'; import type { Observable } from 'rxjs'; -import { observabilityAIAssistantRouter } from './routes/config'; -import type { ObservabilityAIAssistantAppService } from './service/create_app_service'; +import type { AIAssistantAppService } from '@kbn/ai-assistant'; import type { ObservabilityAIAssistantAppPluginStartDependencies } from './types'; import { SharedProviders } from './utils/shared_providers'; +import { observabilityAIAssistantRouter } from './routes/config'; // This is the Conversation application. @@ -26,7 +26,7 @@ export function Application({ coreStart: CoreStart; history: History; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; - service: ObservabilityAIAssistantAppService; + service: AIAssistantAppService; theme$: Observable; }) { return ( @@ -36,7 +36,7 @@ export function Application({ service={service} theme$={theme$} > - + diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx index 66a66ecc07dc0..2c2af65accb59 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/index.tsx @@ -12,17 +12,15 @@ import { v4 } from 'uuid'; import useObservable from 'react-use/lib/useObservable'; import { i18n } from '@kbn/i18n'; import { CoreStart } from '@kbn/core-lifecycle-browser'; -import { useObservabilityAIAssistantAppService } from '../../hooks/use_observability_ai_assistant_app_service'; -import { ChatFlyout } from '../chat/chat_flyout'; +import { AIAssistantAppService, useAIAssistantAppService, ChatFlyout } from '@kbn/ai-assistant'; import { useKibana } from '../../hooks/use_kibana'; import { useTheme } from '../../hooks/use_theme'; import { useNavControlScreenContext } from '../../hooks/use_nav_control_screen_context'; import { SharedProviders } from '../../utils/shared_providers'; -import { ObservabilityAIAssistantAppService } from '../../service/create_app_service'; import { ObservabilityAIAssistantAppPluginStartDependencies } from '../../types'; interface NavControlWithProviderDeps { - appService: ObservabilityAIAssistantAppService; + appService: AIAssistantAppService; coreStart: CoreStart; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; } @@ -45,10 +43,12 @@ export const NavControlWithProvider = ({ }; export function NavControl() { - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const { services: { + application, + http, notifications, plugins: { start: { @@ -162,6 +162,13 @@ export function NavControl() { onClose={() => { setIsOpen(false); }} + navigateToConversation={(conversationId: string) => { + application.navigateToUrl( + http.basePath.prepend( + `/app/observabilityAIAssistant/conversations/${conversationId || ''}` + ) + ); + }} /> ) : undefined} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx index bed86909af417..adef91ceea53e 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/components/nav_control/lazy_nav_control.tsx @@ -8,8 +8,8 @@ import { dynamic } from '@kbn/shared-ux-utility'; import React from 'react'; import { CoreStart } from '@kbn/core-lifecycle-browser'; +import { AIAssistantAppService } from '@kbn/ai-assistant'; import { useIsNavControlVisible } from '../../hooks/is_nav_control_visible'; -import { ObservabilityAIAssistantAppService } from '../../service/create_app_service'; import { ObservabilityAIAssistantAppPluginStartDependencies } from '../../types'; const LazyNavControlWithProvider = dynamic(() => @@ -17,7 +17,7 @@ const LazyNavControlWithProvider = dynamic(() => ); interface NavControlInitiatorProps { - appService: ObservabilityAIAssistantAppService; + appService: AIAssistantAppService; coreStart: CoreStart; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; } diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/context/observability_ai_assistant_app_service_provider.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/context/observability_ai_assistant_app_service_provider.tsx deleted file mode 100644 index 9de7f023b4d10..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/context/observability_ai_assistant_app_service_provider.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 { createContext } from 'react'; -import type { ObservabilityAIAssistantAppService } from '../service/create_app_service'; - -export const ObservabilityAIAssistantAppServiceContext = createContext< - ObservabilityAIAssistantAppService | undefined ->(undefined); - -export const ObservabilityAIAssistantAppServiceProvider = - ObservabilityAIAssistantAppServiceContext.Provider; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts index bcb1725d35109..208e3498bcddd 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { UseKnowledgeBaseResult } from '../use_knowledge_base'; +import { UseKnowledgeBaseResult } from '@kbn/ai-assistant'; export function useKnowledgeBase(): UseKnowledgeBaseResult { return { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts index 10195bf38651e..d068f592c4310 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_nav_control_screen_context.ts @@ -8,11 +8,11 @@ import { useEffect, useState } from 'react'; import datemath from '@elastic/datemath'; import moment from 'moment'; +import { useAIAssistantAppService } from '@kbn/ai-assistant'; import { useKibana } from './use_kibana'; -import { useObservabilityAIAssistantAppService } from './use_observability_ai_assistant_app_service'; export function useNavControlScreenContext() { - const service = useObservabilityAIAssistantAppService(); + const service = useAIAssistantAppService(); const { services: { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_observability_ai_assistant_app_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_observability_ai_assistant_app_service.ts deleted file mode 100644 index 9c86f29565f48..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/use_observability_ai_assistant_app_service.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 { useContext } from 'react'; -import { ObservabilityAIAssistantAppServiceContext } from '../context/observability_ai_assistant_app_service_provider'; - -export function useObservabilityAIAssistantAppService() { - const services = useContext(ObservabilityAIAssistantAppServiceContext); - - if (!services) { - throw new Error( - 'ObservabilityAIAssistantContext not set. Did you wrap your component in ``?' - ); - } - - return services; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/i18n.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/i18n.ts deleted file mode 100644 index dcc28d7ff531a..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/i18n.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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.observabilityAiAssistant.assistantSetup.title', - { - defaultMessage: 'Welcome to Elastic AI Assistant', - } -); - -export const EMPTY_CONVERSATION_TITLE = i18n.translate( - 'xpack.observabilityAiAssistant.emptyConversationTitle', - { defaultMessage: 'New conversation' } -); - -export const UPGRADE_LICENSE_TITLE = i18n.translate( - 'xpack.observabilityAiAssistant.incorrectLicense.title', - { - defaultMessage: 'Upgrade your license', - } -); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx index 9817cc65362d6..1904eebffb2a8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/plugin.tsx @@ -17,13 +17,13 @@ import { import type { Logger } from '@kbn/logging'; import { i18n } from '@kbn/i18n'; import { AI_ASSISTANT_APP_ID } from '@kbn/deeplinks-observability'; +import { createAppService, AIAssistantAppService } from '@kbn/ai-assistant'; import type { ObservabilityAIAssistantAppPluginSetupDependencies, ObservabilityAIAssistantAppPluginStartDependencies, ObservabilityAIAssistantAppPublicSetup, ObservabilityAIAssistantAppPublicStart, } from './types'; -import { createAppService, ObservabilityAIAssistantAppService } from './service/create_app_service'; import { getObsAIAssistantConnectorType } from './rule_connector'; import { NavControlInitiator } from './components/nav_control/lazy_nav_control'; @@ -40,7 +40,7 @@ export class ObservabilityAIAssistantAppPlugin > { logger: Logger; - appService: ObservabilityAIAssistantAppService | undefined; + appService: AIAssistantAppService | undefined; constructor(context: PluginInitializerContext) { this.logger = context.logger.get(); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx index ed0ac18302cc5..545c69a990ace 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/config.tsx @@ -9,8 +9,8 @@ import { createRouter, Outlet } from '@kbn/typed-react-router-config'; import * as t from 'io-ts'; import React from 'react'; import { Redirect } from 'react-router-dom'; +import { ConversationViewWithProps } from './conversations/conversation_view_with_props'; import { ObservabilityAIAssistantPageTemplate } from '../components/page_template'; -import { ConversationView } from './conversations/conversation_view'; /** * The array of route definitions to be used when the application @@ -28,7 +28,7 @@ const observabilityAIAssistantRoutes = { ), children: { '/conversations/new': { - element: , + element: , }, '/conversations/{conversationId}': { params: t.intersection([ @@ -43,7 +43,7 @@ const observabilityAIAssistantRoutes = { }), }), ]), - element: , + element: , }, '/conversations': { element: , diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view_with_props.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view_with_props.tsx new file mode 100644 index 0000000000000..c57b8e2c66c71 --- /dev/null +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/routes/conversations/conversation_view_with_props.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ConversationView } from '@kbn/ai-assistant'; +import { useObservabilityAIAssistantParams } from '../../hooks/use_observability_ai_assistant_params'; +import { useObservabilityAIAssistantRouter } from '../../hooks/use_observability_ai_assistant_router'; + +export function ConversationViewWithProps() { + const { path } = useObservabilityAIAssistantParams('/conversations/*'); + const conversationId = 'conversationId' in path ? path.conversationId : undefined; + const observabilityAIAssistantRouter = useObservabilityAIAssistantRouter(); + function navigateToConversation(nextConversationId?: string) { + if (nextConversationId) { + observabilityAIAssistantRouter.push('/conversations/{conversationId}', { + path: { + conversationId: nextConversationId, + }, + query: {}, + }); + } else { + observabilityAIAssistantRouter.push('/conversations/new', { path: {}, query: {} }); + } + } + return ( + + observabilityAIAssistantRouter.link(`/conversations/{conversationId}`, { + path: { + conversationId: id, + }, + }) + } + /> + ); +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx index eaa441b34a008..b69b6a7e898f5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx @@ -11,8 +11,7 @@ import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import React, { useMemo } from 'react'; import type { Observable } from 'rxjs'; -import { ObservabilityAIAssistantAppServiceProvider } from '../context/observability_ai_assistant_app_service_provider'; -import type { ObservabilityAIAssistantAppService } from '../service/create_app_service'; +import { AIAssistantAppServiceProvider, AIAssistantAppService } from '@kbn/ai-assistant'; import type { ObservabilityAIAssistantAppPluginStartDependencies } from '../types'; export function SharedProviders({ @@ -25,7 +24,7 @@ export function SharedProviders({ children: React.ReactElement; coreStart: CoreStart; pluginsStart: ObservabilityAIAssistantAppPluginStartDependencies; - service: ObservabilityAIAssistantAppService; + service: AIAssistantAppService; theme$: Observable; }) { const theme = useMemo(() => { @@ -46,9 +45,9 @@ export function SharedProviders({ > - + {children} - + diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index 84fe8f0b93911..ab2eec72bdfc8 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -10,7 +10,13 @@ "scripts/**/*", "typings/**/*", "public/**/*.json", - "server/**/*" + "server/**/*", + "../../../packages/kbn-ai-assistant/src/utils/get_role_translation.ts", + "../../../packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx", + "../../../packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx", + "../../../packages/kbn-ai-assistant/src/render_function.tsx", + "../../../packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts", + "../../../packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx", ], "kbn_references": [ "@kbn/es-types", @@ -73,7 +79,10 @@ "@kbn/alerting-comparators", "@kbn/core-lifecycle-browser", "@kbn/inference-plugin", - "@kbn/logs-data-access-plugin" + "@kbn/logs-data-access-plugin", + "@kbn/ai-assistant" ], - "exclude": ["target/**/*"] + "exclude": [ + "target/**/*" + ] } diff --git a/x-pack/plugins/search_assistant/kibana.jsonc b/x-pack/plugins/search_assistant/kibana.jsonc index 85579b76a1e80..8391ee14e0d88 100644 --- a/x-pack/plugins/search_assistant/kibana.jsonc +++ b/x-pack/plugins/search_assistant/kibana.jsonc @@ -12,13 +12,19 @@ "searchAssistant" ], "requiredPlugins": [ + "actions", + "licensing", "observabilityAIAssistant", - "observabilityAIAssistantApp" + "observabilityAIAssistantApp", + "triggersActionsUi", + "share" ], "optionalPlugins": [ "cloud", "usageCollection", ], - "requiredBundles": [] + "requiredBundles": [ + "kibanaReact" + ] } } diff --git a/x-pack/plugins/search_assistant/public/application.tsx b/x-pack/plugins/search_assistant/public/application.tsx index 071c51f4b6e13..a86954d51f0c8 100644 --- a/x-pack/plugins/search_assistant/public/application.tsx +++ b/x-pack/plugins/search_assistant/public/application.tsx @@ -7,31 +7,31 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import type { CoreStart } from '@kbn/core/public'; +import type { AppMountParameters, CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; -import { Router } from '@kbn/shared-ux-router'; +import { AIAssistantAppServiceContext } from '@kbn/ai-assistant'; import type { SearchAssistantPluginStartDependencies } from './types'; -import { SearchAssistantRouter } from './router'; +import { SearchAssistantRouter } from './components/routes/router'; export const renderApp = ( core: CoreStart, services: SearchAssistantPluginStartDependencies, - element: HTMLElement + appMountParameters: AppMountParameters ) => { ReactDOM.render( - - - + + + , - element + appMountParameters.element ); - return () => ReactDOM.unmountComponentAtNode(element); + return () => ReactDOM.unmountComponentAtNode(appMountParameters.element); }; diff --git a/x-pack/plugins/search_assistant/public/components/page_template.tsx b/x-pack/plugins/search_assistant/public/components/page_template.tsx new file mode 100644 index 0000000000000..4e8d5e64f7407 --- /dev/null +++ b/x-pack/plugins/search_assistant/public/components/page_template.tsx @@ -0,0 +1,35 @@ +/* + * 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 { css } from '@emotion/css'; +import React from 'react'; +import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; + +const pageSectionContentClassName = css` + width: 100%; + display: flex; + flex-grow: 1; + padding-top: 0; + padding-bottom: 0; + max-block-size: calc(100vh - 96px); +`; + +export function SearchAIAssistantPageTemplate({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/x-pack/plugins/search_assistant/public/components/routes/config.tsx b/x-pack/plugins/search_assistant/public/components/routes/config.tsx new file mode 100644 index 0000000000000..d3ad0ef97267a --- /dev/null +++ b/x-pack/plugins/search_assistant/public/components/routes/config.tsx @@ -0,0 +1,59 @@ +/* + * 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 { createRouter, Outlet } from '@kbn/typed-react-router-config'; +import * as t from 'io-ts'; +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { ConversationViewWithProps } from './conversations/conversation_view_with_props'; +import { SearchAIAssistantPageTemplate } from '../page_template'; + +/** + * The array of route definitions to be used when the application + * creates the routes. + */ +const searchAIAssistantRoutes = { + '/': { + element: , + }, + '/conversations': { + element: ( + + + + ), + children: { + '/conversations/new': { + element: , + }, + '/conversations/{conversationId}': { + params: t.intersection([ + t.type({ + path: t.type({ + conversationId: t.string, + }), + }), + t.partial({ + state: t.partial({ + prevConversationKey: t.string, + }), + }), + ]), + element: , + }, + '/conversations': { + element: , + }, + }, + }, +}; + +export type SearchAIAssistantRoutes = typeof searchAIAssistantRoutes; + +export const searchAIAssistantRouter = createRouter(searchAIAssistantRoutes); + +export type SearchAIAssistantRouter = typeof searchAIAssistantRouter; diff --git a/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx b/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx new file mode 100644 index 0000000000000..aa4b6eb723d57 --- /dev/null +++ b/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import React from 'react'; +import { ConversationView } from '@kbn/ai-assistant'; +import { useSearchAIAssistantParams } from '../../../hooks/use_ai_assistant_params'; +import { useSearchAIAssistantRouter } from '../../../hooks/use_ai_assistant_router'; + +export function ConversationViewWithProps() { + const { path } = useSearchAIAssistantParams('/conversations/*'); + const conversationId = 'conversationId' in path ? path.conversationId : undefined; + const searchAIAssistantRouter = useSearchAIAssistantRouter(); + function navigateToConversation(nextConversationId?: string) { + if (nextConversationId) { + searchAIAssistantRouter.push('/conversations/{conversationId}', { + path: { + conversationId: nextConversationId, + }, + query: {}, + }); + } else { + searchAIAssistantRouter.push('/conversations/new', { path: {}, query: {} }); + } + } + return ( + + searchAIAssistantRouter.link(`/conversations/{conversationId}`, { + path: { + conversationId: id, + }, + }) + } + /> + ); +} diff --git a/x-pack/plugins/search_assistant/public/components/routes/router.tsx b/x-pack/plugins/search_assistant/public/components/routes/router.tsx new file mode 100644 index 0000000000000..5f61754991749 --- /dev/null +++ b/x-pack/plugins/search_assistant/public/components/routes/router.tsx @@ -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 React from 'react'; +import { History } from 'history'; +import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; +import { useSearchAIAssistantRouter } from '../../hooks/use_ai_assistant_router'; + +export const SearchAssistantRouter: React.FC<{ history: History }> = ({ history }) => { + const router = useSearchAIAssistantRouter(); + return ( + + + + ); +}; diff --git a/x-pack/plugins/search_assistant/public/components/search_assistant.tsx b/x-pack/plugins/search_assistant/public/components/search_assistant.tsx deleted file mode 100644 index 9c227a4e7b73f..0000000000000 --- a/x-pack/plugins/search_assistant/public/components/search_assistant.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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 { EuiPageTemplate } from '@elastic/eui'; -import React from 'react'; -import { App } from './app'; - -export const SearchAssistantPage: React.FC = () => { - return ( - - - - ); -}; diff --git a/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts b/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts new file mode 100644 index 0000000000000..3e0efe5992135 --- /dev/null +++ b/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts @@ -0,0 +1,14 @@ +/* + * 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 PathsOf, type TypeOf, useParams } from '@kbn/typed-react-router-config'; +import type { SearchAIAssistantRoutes } from '../components/routes/config'; + +export function useSearchAIAssistantParams>( + path: TPath +): TypeOf { + return useParams(path)! as TypeOf; +} diff --git a/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts b/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts new file mode 100644 index 0000000000000..d99cd7a4522f1 --- /dev/null +++ b/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts @@ -0,0 +1,52 @@ +/* + * 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 { PathsOf, TypeAsArgs, TypeOf } from '@kbn/typed-react-router-config'; +import { useMemo } from 'react'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; +import { SearchAIAssistantRouter, SearchAIAssistantRoutes } from '../components/routes/config'; +import { searchAIAssistantRouter } from '../components/routes/config'; + +interface StatefulSearchAIAssistantRouter extends SearchAIAssistantRouter { + push>( + path: T, + ...params: TypeAsArgs> + ): void; + replace>( + path: T, + ...params: TypeAsArgs> + ): void; +} + +export function useSearchAIAssistantRouter(): StatefulSearchAIAssistantRouter { + const { + services: { http, application }, + } = useKibana(); + + const link = (...args: any[]) => { + // @ts-expect-error + return searchAIAssistantRouter.link(...args); + }; + + return useMemo( + () => ({ + ...searchAIAssistantRouter, + push: (...args) => { + const next = link(...args); + application?.navigateToApp('searchAssistant', { path: next, replace: false }); + }, + replace: (path, ...args) => { + const next = link(path, ...args); + application?.navigateToApp('searchAssistant', { path: next, replace: true }); + }, + link: (path, ...args) => { + return http?.basePath.prepend('/app/searchAssistant' + link(path, ...args)) || ''; + }, + }), + [application, http] + ); +} diff --git a/x-pack/plugins/search_assistant/public/index.ts b/x-pack/plugins/search_assistant/public/index.ts index c2b16e857b53e..0d4aa25177e5c 100644 --- a/x-pack/plugins/search_assistant/public/index.ts +++ b/x-pack/plugins/search_assistant/public/index.ts @@ -5,9 +5,22 @@ * 2.0. */ -import { SearchAssistantPlugin } from './plugin'; +import { PluginInitializer, PluginInitializerContext } from '@kbn/core/public'; +import { PublicConfigType, SearchAssistantPlugin } from './plugin'; +import { + SearchAssistantPluginSetup, + SearchAssistantPluginStart, + SearchAssistantPluginStartDependencies, +} from './types'; -export function plugin() { - return new SearchAssistantPlugin(); +export function plugin( + context: PluginInitializerContext +): PluginInitializer< + SearchAssistantPluginSetup, + SearchAssistantPluginStart, + {}, + SearchAssistantPluginStartDependencies +> { + return new SearchAssistantPlugin(context); } export type { SearchAssistantPluginSetup, SearchAssistantPluginStart } from './types'; diff --git a/x-pack/plugins/search_assistant/public/plugin.ts b/x-pack/plugins/search_assistant/public/plugin.ts index 8ba22a48df9ff..1c09502c154ad 100644 --- a/x-pack/plugins/search_assistant/public/plugin.ts +++ b/x-pack/plugins/search_assistant/public/plugin.ts @@ -5,19 +5,71 @@ * 2.0. */ -import type { CoreSetup, Plugin } from '@kbn/core/public'; +import { + DEFAULT_APP_CATEGORIES, + type CoreSetup, + type Plugin, + CoreStart, + AppMountParameters, + PluginInitializerContext, +} from '@kbn/core/public'; +import { i18n } from '@kbn/i18n'; import type { SearchAssistantPluginSetup, SearchAssistantPluginStart, SearchAssistantPluginStartDependencies, } from './types'; +export interface PublicConfigType { + ui: { + enabled: boolean; + }; +} + export class SearchAssistantPlugin - implements Plugin + implements + Plugin< + SearchAssistantPluginSetup, + SearchAssistantPluginStart, + {}, + SearchAssistantPluginStartDependencies + > { + private readonly config: PublicConfigType; + + constructor(private readonly context: PluginInitializerContext) { + this.config = this.context.config.get(); + } + public setup( core: CoreSetup ): SearchAssistantPluginSetup { + if (!this.config.ui.enabled) { + return {}; + } + + core.application.register({ + id: 'searchAssistant', + title: i18n.translate('xpack.searchAssistant.appTitle', { + defaultMessage: 'Search Assistant', + }), + euiIconType: 'logoEnterpriseSearch', + appRoute: '/app/searchAssistant', + category: DEFAULT_APP_CATEGORIES.search, + visibleIn: [], + deepLinks: [], + mount: async (appMountParameters: AppMountParameters) => { + // Load application bundle and Get start services + const [{ renderApp }, [coreStart, pluginsStart]] = await Promise.all([ + import('./application'), + core.getStartServices() as Promise< + [CoreStart, SearchAssistantPluginStartDependencies, unknown] + >, + ]); + + return renderApp(coreStart, pluginsStart, appMountParameters); + }, + }); return {}; } diff --git a/x-pack/plugins/search_assistant/public/router.tsx b/x-pack/plugins/search_assistant/public/router.tsx deleted file mode 100644 index a25f865b4f74a..0000000000000 --- a/x-pack/plugins/search_assistant/public/router.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 { Route, Routes } from '@kbn/shared-ux-router'; -import React from 'react'; -import { SearchAssistantPage } from './components/search_assistant'; - -export const SearchAssistantRouter: React.FC = () => { - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/search_assistant/public/types.ts b/x-pack/plugins/search_assistant/public/types.ts index f05592414a9dc..b1a5d6164b1f1 100644 --- a/x-pack/plugins/search_assistant/public/types.ts +++ b/x-pack/plugins/search_assistant/public/types.ts @@ -5,7 +5,6 @@ * 2.0. */ -import { AppMountParameters } from '@kbn/core/public'; import { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import { ObservabilityAIAssistantPublicStart } from '@kbn/observability-ai-assistant-plugin/public'; @@ -16,7 +15,6 @@ export interface SearchAssistantPluginSetup {} export interface SearchAssistantPluginStart {} export interface SearchAssistantPluginStartDependencies { - history: AppMountParameters['history']; observabilityAIAssistant: ObservabilityAIAssistantPublicStart; usageCollection?: UsageCollectionStart; } diff --git a/x-pack/plugins/search_assistant/server/config.ts b/x-pack/plugins/search_assistant/server/config.ts index a09b7ac51b7b7..5ca081ec8a667 100644 --- a/x-pack/plugins/search_assistant/server/config.ts +++ b/x-pack/plugins/search_assistant/server/config.ts @@ -9,11 +9,19 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { PluginConfigDescriptor } from '@kbn/core/server'; const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: false }), + enabled: schema.boolean({ defaultValue: true }), + ui: schema.object({ + enabled: schema.boolean({ defaultValue: false }), + }), }); export type SearchAssistantConfig = TypeOf; export const config: PluginConfigDescriptor = { + exposeToBrowser: { + ui: { + enabled: true, + }, + }, schema: configSchema, }; diff --git a/yarn.lock b/yarn.lock index 5498cb13a015d..a401c36f17f27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3307,6 +3307,10 @@ version "0.0.0" uid "" +"@kbn/ai-assistant@link:x-pack/packages/kbn-ai-assistant": + version "0.0.0" + uid "" + "@kbn/aiops-change-point-detection@link:x-pack/packages/ml/aiops_change_point_detection": version "0.0.0" uid "" From 30c6fd086638b7acf4e0f9c29a3ff74c1ff968c3 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:26:10 +0000 Subject: [PATCH 03/23] [CI] Auto-commit changed files from 'node scripts/lint_packages --fix' --- .github/CODEOWNERS | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index ac6d8a03d2d2b..f4e5643bdd88c 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -11,6 +11,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 From e1db802d05c84ef11a2cb3d06e7b4be39ca430bf Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:26:48 +0000 Subject: [PATCH 04/23] [CI] Auto-commit changed files from 'node scripts/notice' --- x-pack/packages/kbn-ai-assistant/tsconfig.json | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/x-pack/packages/kbn-ai-assistant/tsconfig.json b/x-pack/packages/kbn-ai-assistant/tsconfig.json index d512efb71cfdc..09fe63617d159 100644 --- a/x-pack/packages/kbn-ai-assistant/tsconfig.json +++ b/x-pack/packages/kbn-ai-assistant/tsconfig.json @@ -18,15 +18,22 @@ "kbn_references": [ "@kbn/core-http-browser", "@kbn/i18n", - "@kbn/stack-connectors-plugin", "@kbn/triggers-actions-ui-plugin", - "@kbn/core-http-browser-mocks", - "@kbn/cases-plugin", "@kbn/actions-plugin", - "@kbn/core-notifications-browser", "@kbn/i18n-react", "@kbn/ui-theme", - "@kbn/core-doc-links-browser", "@kbn/core", + "@kbn/observability-ai-assistant-plugin", + "@kbn/observability-ai-assistant-app-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", ] } From 9a7f907b2d657f065e33b62ac9676459cdd3fd79 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 1 Oct 2024 11:27:23 +0000 Subject: [PATCH 05/23] [CI] Auto-commit changed files from 'node scripts/yarn_deduplicate' --- .../observability_ai_assistant_app/tsconfig.json | 7 ++----- x-pack/plugins/search_assistant/tsconfig.json | 6 ++++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index ab2eec72bdfc8..495db1f199aca 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -27,14 +27,10 @@ "@kbn/i18n", "@kbn/management-settings-ids", "@kbn/security-plugin", - "@kbn/ui-theme", "@kbn/actions-plugin", - "@kbn/user-profile-components", - "@kbn/core-http-browser", "@kbn/triggers-actions-ui-plugin", "@kbn/shared-ux-utility", "@kbn/i18n-react", - "@kbn/code-editor", "@kbn/monaco", "@kbn/data-views-plugin", "@kbn/lens-embeddable-utils", @@ -80,7 +76,8 @@ "@kbn/core-lifecycle-browser", "@kbn/inference-plugin", "@kbn/logs-data-access-plugin", - "@kbn/ai-assistant" + "@kbn/ai-assistant", + "@kbn/observability-ai-assistant-app-plugin" ], "exclude": [ "target/**/*" diff --git a/x-pack/plugins/search_assistant/tsconfig.json b/x-pack/plugins/search_assistant/tsconfig.json index 090356cf1f440..9a43830cadc7f 100644 --- a/x-pack/plugins/search_assistant/tsconfig.json +++ b/x-pack/plugins/search_assistant/tsconfig.json @@ -16,11 +16,13 @@ "@kbn/react-kibana-context-render", "@kbn/kibana-react-plugin", "@kbn/i18n-react", - "@kbn/shared-ux-router", "@kbn/shared-ux-page-kibana-template", "@kbn/usage-collection-plugin", "@kbn/observability-ai-assistant-plugin", - "@kbn/config-schema" + "@kbn/config-schema", + "@kbn/ai-assistant", + "@kbn/typed-react-router-config", + "@kbn/i18n" ], "exclude": [ "target/**/*", From 6d555cff7faab0ee9f2c027613886c5cff5be9e5 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 1 Oct 2024 14:27:47 +0200 Subject: [PATCH 06/23] fix build error --- .../src/chat/chat_body.stories.tsx | 2 +- .../src/chat/chat_flyout.stories.tsx | 2 +- .../src/chat/chat_timeline.stories.tsx | 2 +- .../src/chat/conversation_list.stories.tsx | 2 +- .../src/hooks/use_conversation.test.tsx | 2 +- .../src/hooks/use_json_editor_model.ts | 2 +- ..._timeline_items_from_conversation.test.tsx | 2 +- .../public/types.ts | 1 - .../use_conversation_list.ts | 2 +- .../__storybook_mocks__/use_conversations.ts | 2 +- .../public/utils/builders.ts | 127 ----------------- .../utils/create_initialized_object.test.ts | 134 ------------------ .../public/utils/create_initialized_object.ts | 42 ------ .../public/utils/create_mock_chat_service.ts | 34 ----- .../public/utils/get_settings_href.ts | 12 -- .../public/utils/get_settings_kb_href.ts | 14 -- .../tsconfig.json | 6 - 17 files changed, 9 insertions(+), 379 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/builders.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.test.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_mock_chat_service.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_href.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_kb_href.ts diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx index 4dded4e8c6d10..182cb046cba70 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_body.stories.tsx @@ -8,7 +8,7 @@ import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import React from 'react'; import { MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; -import { buildSystemMessage } from '@kbn/observability-ai-assistant-app-plugin/public/utils/builders'; +import { buildSystemMessage } from '../utils/builders'; import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { ChatBody as Component } from './chat_body'; diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.stories.tsx index ed8857cea42f9..72546d770e6a6 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_flyout.stories.tsx @@ -7,7 +7,7 @@ import { ComponentStory } from '@storybook/react'; import React from 'react'; -import { buildSystemMessage } from '@kbn/observability-ai-assistant-app-plugin/public/utils/builders'; +import { buildSystemMessage } from '../utils/builders'; import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { ChatFlyout as Component, FlyoutPositionMode } from './chat_flyout'; diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx index 1c4e3fdc115bd..0afb0c7e79fc0 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_timeline.stories.tsx @@ -18,7 +18,7 @@ import { buildFunctionResponseMessage, buildSystemMessage, buildUserMessage, -} from '@kbn/observability-ai-assistant-app-plugin/public/utils/builders'; +} from '../utils/builders'; import { ChatTimeline as Component, type ChatTimelineProps } from './chat_timeline'; export default { diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx index 82367b9277558..7405b477647fd 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/conversation_list.stories.tsx @@ -7,7 +7,7 @@ import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; import React from 'react'; -import { buildConversation } from '@kbn/observability-ai-assistant-app-plugin/public/utils/builders'; +import { buildConversation } from '../utils/builders'; import { KibanaReactStorybookDecorator } from '../utils/storybook_decorator.stories'; import { ConversationList as Component } from './conversation_list'; diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx index fa36dda14989e..aa401cd38c6f5 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx @@ -28,7 +28,7 @@ import { type UseConversationResult, } from './use_conversation'; import { ChatState } from '@kbn/observability-ai-assistant-plugin/public'; -import { createMockChatService } from '@kbn/observability-ai-assistant-app-plugin/public/utils/create_mock_chat_service'; +import { createMockChatService } from '../utils/create_mock_chat_service'; import { createUseChat } from '@kbn/observability-ai-assistant-plugin/public/hooks/use_chat'; import type { NotificationsStart } from '@kbn/core/public'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts index 97c22b07e8b96..1b14c504d935d 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts @@ -6,7 +6,7 @@ */ import { useEffect, useMemo, useState } from 'react'; import { monaco } from '@kbn/monaco'; -import { createInitializedObject } from '@kbn/observability-ai-assistant-app-plugin/public/utils/create_initialized_object'; +import { createInitializedObject } from '../utils/create_initialized_object'; import { useAIAssistantChatService } from './use_ai_assistant_chat_service'; import { safeJsonParse } from '../utils/safe_json_parse'; diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx index ffe7efc785a31..6fb7e1a323d08 100644 --- a/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx @@ -10,7 +10,7 @@ import { render } from '@testing-library/react'; import { getTimelineItemsfromConversation } from './get_timeline_items_from_conversation'; import { __IntlProvider as IntlProvider } from '@kbn/i18n-react'; import { ChatState, Message, MessageRole } from '@kbn/observability-ai-assistant-plugin/public'; -import { createMockChatService } from '@kbn/observability-ai-assistant-app-plugin/public/utils/create_mock_chat_service'; +import { createMockChatService } from './create_mock_chat_service'; import { KibanaContextProvider } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana'; import { CONTEXT_FUNCTION_NAME } from '@kbn/observability-ai-assistant-plugin/server/functions/context'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts index f9e55f4f2a9b8..8b265d433f515 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/types.ts @@ -86,7 +86,6 @@ export interface ObservabilityAIAssistantChatService { onActionClick: ChatActionClickHandler, scope?: AssistantScope ) => React.ReactNode; - setScope: (scope: AssistantScope) => void; } export interface ObservabilityAIAssistantConversationService { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts index 0fc7f6d64401a..f569374d5bd49 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { buildConversation } from '../../utils/builders'; +import { buildConversation } from '@kbn/ai-assistant/src/utils/builders'; export function useConversationList() { return { diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts index d41fd4aac1b29..24f20834d785f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts @@ -6,7 +6,7 @@ */ import { uniqueId } from 'lodash'; -import { buildConversation } from '../../utils/builders'; +import { buildConversation } from '@kbn/ai-assistant/src/utils/builders'; export function useConversations() { return [ diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/builders.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/builders.ts deleted file mode 100644 index 0f614f37642f1..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/builders.ts +++ /dev/null @@ -1,127 +0,0 @@ -/* - * 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 { merge, uniqueId } from 'lodash'; -import type { DeepPartial } from 'utility-types'; -import { - type Conversation, - type Message, - MessageRole, -} from '@kbn/observability-ai-assistant-plugin/common'; - -type BuildMessageProps = DeepPartial & { - message: { - role: MessageRole; - function_call?: { - name: string; - trigger: MessageRole.Assistant | MessageRole.User | MessageRole.Elastic; - }; - }; -}; - -export function buildMessage(params: BuildMessageProps): Message { - return merge( - { - '@timestamp': new Date().toISOString(), - }, - params - ); -} - -export function buildSystemMessage( - params?: Omit & { - message: DeepPartial>; - } -) { - return buildMessage( - // @ts-expect-error upgrade typescript v5.1.6 - merge({}, params, { - message: { role: MessageRole.System }, - }) - ); -} - -export function buildUserMessage( - params?: Omit & { - message?: DeepPartial>; - } -) { - return buildMessage( - // @ts-expect-error upgrade typescript v5.1.6 - merge( - { - message: { - content: "What's a function?", - }, - }, - params, - { - message: { role: MessageRole.User }, - } - ) - ); -} - -export function buildAssistantMessage( - params?: Omit & { - message: DeepPartial>; - } -) { - return buildMessage( - // @ts-expect-error upgrade typescript v5.1.6 - merge( - { - message: { - content: `In computer programming and mathematics, a function is a fundamental concept that represents a relationship between input values and output values. It takes one or more input values (also known as arguments or parameters) and processes them to produce a result, which is the output of the function. The input values are passed to the function, and the function performs a specific set of operations or calculations on those inputs to produce the desired output. - A function is often defined with a name, which serves as an identifier to call and use the function in the code. It can be thought of as a reusable block of code that can be executed whenever needed, and it helps in organizing code and making it more modular and maintainable.`, - }, - }, - params, - { - message: { role: MessageRole.Assistant }, - } - ) - ); -} - -export function buildFunctionResponseMessage( - params?: Omit & { - message: DeepPartial>; - } -) { - return buildUserMessage( - merge( - {}, - { - message: { - name: 'leftpad', - }, - ...params, - } - ) - ); -} - -export function buildConversation(params?: Partial): Conversation { - return { - '@timestamp': '', - user: { - name: 'foo', - }, - conversation: { - id: uniqueId(), - title: '', - last_updated: '', - }, - messages: [buildSystemMessage()], - labels: {}, - numeric_labels: {}, - namespace: '', - public: false, - ...params, - }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.test.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.test.ts deleted file mode 100644 index 5dcaa01af1ed0..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * 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 { createInitializedObject } from './create_initialized_object'; - -describe('createInitializedObject', () => { - it('should return an object with properties of type "string" set to a default value of ""', () => { - expect( - createInitializedObject({ - type: 'object', - properties: { - foo: { - type: 'string', - }, - }, - required: ['foo'], - }) - ).toStrictEqual({ foo: '' }); - }); - - it('should return an object with properties of type "number" set to a default value of 1', () => { - expect( - createInitializedObject({ - type: 'object', - properties: { - foo: { - type: 'number', - }, - }, - required: ['foo'], - }) - ).toStrictEqual({ foo: 1 }); - }); - - it('should return an object with properties of type "array" set to a default value of []', () => { - expect( - createInitializedObject({ - type: 'object', - properties: { - foo: { - type: 'array', - }, - }, - required: ['foo'], - }) - ).toStrictEqual({ foo: [] }); - }); - - it('should return an object with default values for properties that are required', () => { - expect( - createInitializedObject({ - type: 'object', - properties: { - requiredProperty: { - type: 'string', - }, - notRequiredProperty: { - type: 'string', - }, - }, - required: ['requiredProperty'], - }) - ).toStrictEqual({ requiredProperty: '' }); - }); - - it('should return an object with nested fields if they are present in the schema', () => { - expect( - createInitializedObject({ - type: 'object', - properties: { - foo: { - type: 'object', - properties: { - bar: { - type: 'object', - properties: { - baz: { - type: 'string', - }, - }, - required: ['baz'], - }, - }, - required: ['bar'], - }, - }, - }) - ).toStrictEqual({ foo: { bar: { baz: '' } } }); - - expect( - createInitializedObject({ - type: 'object', - properties: { - foo: { - type: 'object', - properties: { - bar: { - type: 'string', - }, - }, - }, - }, - }) - ).toStrictEqual({ foo: {} }); - }); - - it('should handle a real life example', () => { - expect( - createInitializedObject({ - type: 'object', - properties: { - method: { - type: 'string', - description: 'The HTTP method of the Elasticsearch endpoint', - enum: ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'] as const, - }, - path: { - type: 'string', - description: 'The path of the Elasticsearch endpoint, including query parameters', - }, - notRequired: { - type: 'string', - description: 'This property is not required.', - }, - }, - required: ['method', 'path'] as const, - }) - ).toStrictEqual({ method: '', path: '' }); - }); -}); diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.ts deleted file mode 100644 index e06800aca07a0..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_initialized_object.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { FunctionDefinition } from '@kbn/observability-ai-assistant-plugin/common'; - -type Params = FunctionDefinition['parameters']; - -export function createInitializedObject(parameters: Params) { - const emptyObject: Record = {}; - - function traverseProperties({ properties, required }: Params) { - for (const propName in properties) { - if (Object.hasOwn(properties, propName)) { - const prop = properties[propName] as Params; - - if (prop.type === 'object') { - emptyObject[propName] = createInitializedObject(prop); - } else if (required?.includes(propName)) { - if (prop.type === 'array') { - emptyObject[propName] = []; - } - - if (prop.type === 'number') { - emptyObject[propName] = 1; - } - - if (prop.type === 'string') { - emptyObject[propName] = ''; - } - } - } - } - } - - traverseProperties(parameters); - - return emptyObject; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_mock_chat_service.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_mock_chat_service.ts deleted file mode 100644 index 460722c49be64..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/create_mock_chat_service.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import { - MessageRole, - ObservabilityAIAssistantChatService, -} from '@kbn/observability-ai-assistant-plugin/public'; - -type MockedChatService = DeeplyMockedKeys; - -export const createMockChatService = (): MockedChatService => { - const mockChatService: MockedChatService = { - chat: jest.fn(), - complete: jest.fn(), - sendAnalyticsEvent: jest.fn(), - getFunctions: jest.fn().mockReturnValue([]), - hasFunction: jest.fn().mockReturnValue(false), - hasRenderFunction: jest.fn().mockReturnValue(true), - renderFunction: jest.fn(), - getSystemMessage: jest.fn().mockReturnValue({ - '@timestamp': new Date().toISOString(), - message: { - role: MessageRole.System, - content: '', - }, - }), - }; - return mockChatService; -}; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_href.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_href.ts deleted file mode 100644 index 45a3083d66327..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_href.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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 { HttpStart } from '@kbn/core/public'; - -export function getSettingsHref(http: HttpStart) { - return http!.basePath.prepend(`/app/management/kibana/observabilityAiAssistantManagement`); -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_kb_href.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_kb_href.ts deleted file mode 100644 index 2aa625da08d17..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/get_settings_kb_href.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 { HttpStart } from '@kbn/core/public'; - -export function getSettingsKnowledgeBaseHref(http: HttpStart) { - return http!.basePath.prepend( - `/app/management/kibana/observabilityAiAssistantManagement?tab=knowledge_base` - ); -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index 495db1f199aca..bc6a6f23dabe1 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -11,12 +11,6 @@ "typings/**/*", "public/**/*.json", "server/**/*", - "../../../packages/kbn-ai-assistant/src/utils/get_role_translation.ts", - "../../../packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.tsx", - "../../../packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx", - "../../../packages/kbn-ai-assistant/src/render_function.tsx", - "../../../packages/kbn-ai-assistant/src/hooks/use_json_editor_model.ts", - "../../../packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx", ], "kbn_references": [ "@kbn/es-types", From 71ad9edb001a81d9563f86d3ce680cef6db8cb9b Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 1 Oct 2024 14:28:12 +0200 Subject: [PATCH 07/23] add missing files --- .../kbn-ai-assistant/src/utils/builders.ts | 127 +++++++++++++++++ .../utils/create_initialized_object.test.ts | 134 ++++++++++++++++++ .../src/utils/create_initialized_object.ts | 42 ++++++ .../src/utils/create_mock_chat_service.ts | 34 +++++ 4 files changed, 337 insertions(+) create mode 100644 x-pack/packages/kbn-ai-assistant/src/utils/builders.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.test.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/utils/create_mock_chat_service.ts diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/builders.ts b/x-pack/packages/kbn-ai-assistant/src/utils/builders.ts new file mode 100644 index 0000000000000..0f614f37642f1 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/utils/builders.ts @@ -0,0 +1,127 @@ +/* + * 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 { merge, uniqueId } from 'lodash'; +import type { DeepPartial } from 'utility-types'; +import { + type Conversation, + type Message, + MessageRole, +} from '@kbn/observability-ai-assistant-plugin/common'; + +type BuildMessageProps = DeepPartial & { + message: { + role: MessageRole; + function_call?: { + name: string; + trigger: MessageRole.Assistant | MessageRole.User | MessageRole.Elastic; + }; + }; +}; + +export function buildMessage(params: BuildMessageProps): Message { + return merge( + { + '@timestamp': new Date().toISOString(), + }, + params + ); +} + +export function buildSystemMessage( + params?: Omit & { + message: DeepPartial>; + } +) { + return buildMessage( + // @ts-expect-error upgrade typescript v5.1.6 + merge({}, params, { + message: { role: MessageRole.System }, + }) + ); +} + +export function buildUserMessage( + params?: Omit & { + message?: DeepPartial>; + } +) { + return buildMessage( + // @ts-expect-error upgrade typescript v5.1.6 + merge( + { + message: { + content: "What's a function?", + }, + }, + params, + { + message: { role: MessageRole.User }, + } + ) + ); +} + +export function buildAssistantMessage( + params?: Omit & { + message: DeepPartial>; + } +) { + return buildMessage( + // @ts-expect-error upgrade typescript v5.1.6 + merge( + { + message: { + content: `In computer programming and mathematics, a function is a fundamental concept that represents a relationship between input values and output values. It takes one or more input values (also known as arguments or parameters) and processes them to produce a result, which is the output of the function. The input values are passed to the function, and the function performs a specific set of operations or calculations on those inputs to produce the desired output. + A function is often defined with a name, which serves as an identifier to call and use the function in the code. It can be thought of as a reusable block of code that can be executed whenever needed, and it helps in organizing code and making it more modular and maintainable.`, + }, + }, + params, + { + message: { role: MessageRole.Assistant }, + } + ) + ); +} + +export function buildFunctionResponseMessage( + params?: Omit & { + message: DeepPartial>; + } +) { + return buildUserMessage( + merge( + {}, + { + message: { + name: 'leftpad', + }, + ...params, + } + ) + ); +} + +export function buildConversation(params?: Partial): Conversation { + return { + '@timestamp': '', + user: { + name: 'foo', + }, + conversation: { + id: uniqueId(), + title: '', + last_updated: '', + }, + messages: [buildSystemMessage()], + labels: {}, + numeric_labels: {}, + namespace: '', + public: false, + ...params, + }; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.test.ts b/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.test.ts new file mode 100644 index 0000000000000..5dcaa01af1ed0 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.test.ts @@ -0,0 +1,134 @@ +/* + * 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 { createInitializedObject } from './create_initialized_object'; + +describe('createInitializedObject', () => { + it('should return an object with properties of type "string" set to a default value of ""', () => { + expect( + createInitializedObject({ + type: 'object', + properties: { + foo: { + type: 'string', + }, + }, + required: ['foo'], + }) + ).toStrictEqual({ foo: '' }); + }); + + it('should return an object with properties of type "number" set to a default value of 1', () => { + expect( + createInitializedObject({ + type: 'object', + properties: { + foo: { + type: 'number', + }, + }, + required: ['foo'], + }) + ).toStrictEqual({ foo: 1 }); + }); + + it('should return an object with properties of type "array" set to a default value of []', () => { + expect( + createInitializedObject({ + type: 'object', + properties: { + foo: { + type: 'array', + }, + }, + required: ['foo'], + }) + ).toStrictEqual({ foo: [] }); + }); + + it('should return an object with default values for properties that are required', () => { + expect( + createInitializedObject({ + type: 'object', + properties: { + requiredProperty: { + type: 'string', + }, + notRequiredProperty: { + type: 'string', + }, + }, + required: ['requiredProperty'], + }) + ).toStrictEqual({ requiredProperty: '' }); + }); + + it('should return an object with nested fields if they are present in the schema', () => { + expect( + createInitializedObject({ + type: 'object', + properties: { + foo: { + type: 'object', + properties: { + bar: { + type: 'object', + properties: { + baz: { + type: 'string', + }, + }, + required: ['baz'], + }, + }, + required: ['bar'], + }, + }, + }) + ).toStrictEqual({ foo: { bar: { baz: '' } } }); + + expect( + createInitializedObject({ + type: 'object', + properties: { + foo: { + type: 'object', + properties: { + bar: { + type: 'string', + }, + }, + }, + }, + }) + ).toStrictEqual({ foo: {} }); + }); + + it('should handle a real life example', () => { + expect( + createInitializedObject({ + type: 'object', + properties: { + method: { + type: 'string', + description: 'The HTTP method of the Elasticsearch endpoint', + enum: ['GET', 'PUT', 'POST', 'DELETE', 'PATCH'] as const, + }, + path: { + type: 'string', + description: 'The path of the Elasticsearch endpoint, including query parameters', + }, + notRequired: { + type: 'string', + description: 'This property is not required.', + }, + }, + required: ['method', 'path'] as const, + }) + ).toStrictEqual({ method: '', path: '' }); + }); +}); diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.ts b/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.ts new file mode 100644 index 0000000000000..e06800aca07a0 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/utils/create_initialized_object.ts @@ -0,0 +1,42 @@ +/* + * 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 { FunctionDefinition } from '@kbn/observability-ai-assistant-plugin/common'; + +type Params = FunctionDefinition['parameters']; + +export function createInitializedObject(parameters: Params) { + const emptyObject: Record = {}; + + function traverseProperties({ properties, required }: Params) { + for (const propName in properties) { + if (Object.hasOwn(properties, propName)) { + const prop = properties[propName] as Params; + + if (prop.type === 'object') { + emptyObject[propName] = createInitializedObject(prop); + } else if (required?.includes(propName)) { + if (prop.type === 'array') { + emptyObject[propName] = []; + } + + if (prop.type === 'number') { + emptyObject[propName] = 1; + } + + if (prop.type === 'string') { + emptyObject[propName] = ''; + } + } + } + } + } + + traverseProperties(parameters); + + return emptyObject; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/create_mock_chat_service.ts b/x-pack/packages/kbn-ai-assistant/src/utils/create_mock_chat_service.ts new file mode 100644 index 0000000000000..460722c49be64 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/utils/create_mock_chat_service.ts @@ -0,0 +1,34 @@ +/* + * 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 { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { + MessageRole, + ObservabilityAIAssistantChatService, +} from '@kbn/observability-ai-assistant-plugin/public'; + +type MockedChatService = DeeplyMockedKeys; + +export const createMockChatService = (): MockedChatService => { + const mockChatService: MockedChatService = { + chat: jest.fn(), + complete: jest.fn(), + sendAnalyticsEvent: jest.fn(), + getFunctions: jest.fn().mockReturnValue([]), + hasFunction: jest.fn().mockReturnValue(false), + hasRenderFunction: jest.fn().mockReturnValue(true), + renderFunction: jest.fn(), + getSystemMessage: jest.fn().mockReturnValue({ + '@timestamp': new Date().toISOString(), + message: { + role: MessageRole.System, + content: '', + }, + }), + }; + return mockChatService; +}; From b539fe52d02a035fac07a516f89d9ae07b2cb910 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:51:15 +0000 Subject: [PATCH 08/23] [CI] Auto-commit changed files from 'node scripts/notice' --- x-pack/packages/kbn-ai-assistant/tsconfig.json | 1 - .../observability_ai_assistant_app/tsconfig.json | 4 ---- 2 files changed, 5 deletions(-) diff --git a/x-pack/packages/kbn-ai-assistant/tsconfig.json b/x-pack/packages/kbn-ai-assistant/tsconfig.json index 09fe63617d159..574422363ffca 100644 --- a/x-pack/packages/kbn-ai-assistant/tsconfig.json +++ b/x-pack/packages/kbn-ai-assistant/tsconfig.json @@ -24,7 +24,6 @@ "@kbn/ui-theme", "@kbn/core", "@kbn/observability-ai-assistant-plugin", - "@kbn/observability-ai-assistant-app-plugin", "@kbn/security-plugin", "@kbn/user-profile-components", "@kbn/std", diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index bc6a6f23dabe1..81acb9e5b226f 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -24,8 +24,6 @@ "@kbn/actions-plugin", "@kbn/triggers-actions-ui-plugin", "@kbn/shared-ux-utility", - "@kbn/i18n-react", - "@kbn/monaco", "@kbn/data-views-plugin", "@kbn/lens-embeddable-utils", "@kbn/lens-plugin", @@ -34,7 +32,6 @@ "@kbn/esql-utils", "@kbn/visualization-utils", "@kbn/ai-assistant-management-plugin", - "@kbn/utility-types-jest", "@kbn/kibana-react-plugin", "@kbn/licensing-plugin", "@kbn/logging", @@ -71,7 +68,6 @@ "@kbn/inference-plugin", "@kbn/logs-data-access-plugin", "@kbn/ai-assistant", - "@kbn/observability-ai-assistant-app-plugin" ], "exclude": [ "target/**/*" From c6cf9d73593677db0beb29bfdd455ed1824c5f01 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 1 Oct 2024 15:24:04 +0200 Subject: [PATCH 09/23] Fix i18n --- .../translations/translations/fr-FR.json | 94 ------------------- .../translations/translations/ja-JP.json | 94 ------------------- .../translations/translations/zh-CN.json | 94 ------------------- 3 files changed, 282 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d1d4e79320a21..a7904cd07ab4e 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -32600,92 +32600,27 @@ "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.prompt": "Que signifie \"SLO\" ?", "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.title": "SLO", "xpack.observabilityAiAssistant.appTitle": "Assistant d'intelligence artificielle d'Observability", - "xpack.observabilityAiAssistant.askAssistantButton.buttonLabel": "Demander à l'assistant", - "xpack.observabilityAiAssistant.askAssistantButton.popoverContent": "Obtenez des informations relatives à vos données grâce à l'assistant d'Elastic", - "xpack.observabilityAiAssistant.askAssistantButton.popoverTitle": "Assistant d'IA pour Observability", - "xpack.observabilityAiAssistant.assistantSetup.title": "Bienvenue sur l'assistant d'intelligence artificielle d'Elastic", "xpack.observabilityAiAssistant.changesList.dotImpactHigh": "Élevé", "xpack.observabilityAiAssistant.changesList.dotImpactLow": "Bas", "xpack.observabilityAiAssistant.changesList.dotImpactMedium": "Moyenne", "xpack.observabilityAiAssistant.changesList.labelColumnTitle": "Étiquette", "xpack.observabilityAiAssistant.changesList.noChangesDetected": "Aucun changement détecté", "xpack.observabilityAiAssistant.changesList.trendColumnTitle": "Tendance", - "xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "Menu", - "xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "Plus d'actions", - "xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents": "Masquer {count} événements", - "xpack.observabilityAiAssistant.chatCollapsedItems.showEvents": "Montrer {count} événements", - "xpack.observabilityAiAssistant.chatCollapsedItems.toggleButtonLabel": "Afficher/masquer les éléments", "xpack.observabilityAiAssistant.chatCompletionError.conversationNotFoundError": "Conversation introuvable", "xpack.observabilityAiAssistant.chatCompletionError.internalServerError": "Une erreur s'est produite au niveau du serveur interne", "xpack.observabilityAiAssistant.chatCompletionError.tokenLimitReachedError": "Limite de token atteinte. La limite de token est {tokenLimit}, mais la conversation actuelle a {tokenCount} tokens.", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "Développer la liste des conversations", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "Nouveau chat", - "xpack.observabilityAiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantForObservabilityLabel": "Menu volant du chat de l'assistant d'IA pour Observability", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "Réduire la liste des conversations", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "Développer la liste des conversations", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel": "Nouveau chat", - "xpack.observabilityAiAssistant.chatHeader.actions.connector": "Connecteur", - "xpack.observabilityAiAssistant.chatHeader.actions.copyConversation": "Copier la conversation", - "xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase": "Gérer la base de connaissances", - "xpack.observabilityAiAssistant.chatHeader.actions.settings": "Réglages de l'assistant d'IA", - "xpack.observabilityAiAssistant.chatHeader.actions.title": "Actions", - "xpack.observabilityAiAssistant.chatHeader.editConversationInput": "Modifier la conversation", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "Accéder aux conversations", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "Afficher / Masquer le mode menu volant", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "Ancrer le chat", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "Désancrer le chat", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "Accéder aux conversations", - "xpack.observabilityAiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", - "xpack.observabilityAiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "Envoyer", "xpack.observabilityAiAssistant.chatService.div.helloLabel": "Bonjour", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage": "Copier le message", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful": "Message copié", - "xpack.observabilityAiAssistant.chatTimeline.actions.editPrompt": "Modifier l'invite", - "xpack.observabilityAiAssistant.chatTimeline.actions.inspectPrompt": "Inspecter l'invite", - "xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Assistant d'Elastic", - "xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "Système", - "xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "Vous", - "xpack.observabilityAiAssistant.checkingKbAvailability": "Vérification de la disponibilité de la base de connaissances", "xpack.observabilityAiAssistant.connectorSelector.connectorSelectLabel": "Connecteur :", "xpack.observabilityAiAssistant.connectorSelector.empty": "Aucun connecteur", "xpack.observabilityAiAssistant.connectorSelector.error": "Impossible de charger les connecteurs", - "xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel": "Supprimer", - "xpack.observabilityAiAssistant.conversationList.errorMessage": "Échec de chargement", - "xpack.observabilityAiAssistant.conversationList.noConversations": "Aucune conversation", - "xpack.observabilityAiAssistant.conversationList.title": "Précédemment", "xpack.observabilityAiAssistant.conversationsDeepLinkTitle": "Conversations", - "xpack.observabilityAiAssistant.conversationStartTitle": "a démarré une conversation", - "xpack.observabilityAiAssistant.couldNotFindConversationContent": "Impossible de trouver une conversation avec l'ID {conversationId}. Assurez-vous que la conversation existe et que vous y avez accès.", - "xpack.observabilityAiAssistant.couldNotFindConversationTitle": "Conversation introuvable", - "xpack.observabilityAiAssistant.disclaimer.disclaimerLabel": "Ce chat est soutenu par une intégration avec votre fournisseur LLM. Il arrive que les grands modèles de langage (LLM) présentent comme correctes des informations incorrectes. Elastic prend en charge la configuration ainsi que la connexion au fournisseur LLM et à votre base de connaissances, mais n'est pas responsable des réponses fournies par le LLM.", - "xpack.observabilityAiAssistant.emptyConversationTitle": "Nouvelle conversation", - "xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase": "Impossible de configurer la base de connaissances", - "xpack.observabilityAiAssistant.errorUpdatingConversation": "Impossible de mettre à jour la conversation", - "xpack.observabilityAiAssistant.executedFunctionFailureEvent": "impossible d'exécuter la fonction {functionName}", "xpack.observabilityAiAssistant.experimentalTitle": "Version d'évaluation technique", "xpack.observabilityAiAssistant.experimentalTooltip": "Cette fonctionnalité est en version d'évaluation technique et pourra être modifiée ou retirée complètement dans une future version. Elastic s'efforcera de corriger tout problème, mais les fonctionnalités des versions d'évaluation technique ne sont pas soumises aux SLA de support des fonctionnalités officielles en disponibilité générale.", "xpack.observabilityAiAssistant.failedLoadingResponseText": "Échec de chargement de la réponse", - "xpack.observabilityAiAssistant.failedToGetStatus": "Échec de l'obtention du statut du modèle.", "xpack.observabilityAiAssistant.failedToLoadResponse": "Échec du chargement d'une réponse de l'assistant d'intelligence artificielle", - "xpack.observabilityAiAssistant.failedToSetupKnowledgeBase": "Échec de la configuration de la base de connaissances.", "xpack.observabilityAiAssistant.featureRegistry.featureName": "Assistant d'intelligence artificielle d'Observability", "xpack.observabilityAiAssistant.feedbackButtons.em.thanksForYourFeedbackLabel": "Merci pour vos retours", - "xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText": "Supprimer la conversation", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent": "Cette action ne peut pas être annulée.", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle": "Supprimer cette conversation ?", - "xpack.observabilityAiAssistant.flyout.failedToDeleteConversation": "Impossible de supprimer la conversation", "xpack.observabilityAiAssistant.functionCallLimitExceeded": "\n\nRemarque : l'Assistant a essayé d'appeler une fonction, même si la limite a été dépassée", - "xpack.observabilityAiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "Sélectionner la fonction", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction": "Effacer la fonction", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "Sélectionner une fonction", - "xpack.observabilityAiAssistant.hideExpandConversationButton.hide": "Masquer les chats", - "xpack.observabilityAiAssistant.hideExpandConversationButton.show": "Afficher les chats", - "xpack.observabilityAiAssistant.incorrectLicense.body": "Une licence d'entreprise est requise pour utiliser l'assistant d'intelligence artificielle d'Elastic.", - "xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "Gérer la licence", - "xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "Plans d'abonnement", - "xpack.observabilityAiAssistant.incorrectLicense.title": "Mettez votre licence à niveau", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "Configurer un connecteur GenAI", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "Commencez à travailler avec l'assistant AI Elastic en configurant un connecteur pour votre fournisseur d'IA. Le modèle doit prendre en charge les appels de fonction. Lorsque vous utilisez OpenAI ou Azure, nous vous recommandons d'utiliser GPT4.", "xpack.observabilityAiAssistant.insight.actions": "Actions", "xpack.observabilityAiAssistant.insight.actions.connector": "Connecteur", "xpack.observabilityAiAssistant.insight.actions.editPrompt": "Modifier l'invite", @@ -32703,7 +32638,6 @@ "xpack.observabilityAiAssistant.insight.response.startChat": "Lancer le chat", "xpack.observabilityAiAssistant.insight.sendPromptEdit": "Envoyer l'invite", "xpack.observabilityAiAssistant.insightModifiedPrompt": "Cette information a été modifiée.", - "xpack.observabilityAiAssistant.installingKb": "Configuration de la base de connaissances", "xpack.observabilityAiAssistant.lensESQLFunction.displayChart": "Afficher le graphique", "xpack.observabilityAiAssistant.lensESQLFunction.displayTable": "Afficher le tableau", "xpack.observabilityAiAssistant.lensESQLFunction.edit": "Modifier la visualisation", @@ -32718,42 +32652,14 @@ "xpack.observabilityAiAssistant.missingCredentialsCallout.title": "Informations d'identification manquantes", "xpack.observabilityAiAssistant.navControl.initFailureErrorTitle": "Échec de l'initialisation de l'assistant d'IA d'Observability", "xpack.observabilityAiAssistant.navControl.openTheAIAssistantPopoverLabel": "Ouvrir l'assistant d'IA", - "xpack.observabilityAiAssistant.newChatButton": "Nouveau chat", - "xpack.observabilityAiAssistant.poweredByModel": "Alimenté par {model}", - "xpack.observabilityAiAssistant.prompt.functionList.filter": "Filtre", - "xpack.observabilityAiAssistant.prompt.functionList.functionList": "Liste de fonctions", - "xpack.observabilityAiAssistant.prompt.placeholder": "Envoyer un message à l'assistant", - "xpack.observabilityAiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "Sélectionner une option", "xpack.observabilityAiAssistant.regenerateResponseButtonLabel": "Régénérer", "xpack.observabilityAiAssistant.requiredConnectorField": "Connecteur obligatoire.", "xpack.observabilityAiAssistant.requiredMessageTextField": "Le message est requis.", "xpack.observabilityAiAssistant.resetDefaultPrompt": "Réinitialiser à la valeur par défaut", "xpack.observabilityAiAssistant.runThisQuery": "Afficher les résultats", - "xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel": "Gérer les connecteurs", - "xpack.observabilityAiAssistant.setupKb": "Améliorez votre expérience en configurant la base de connaissances.", - "xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel": "L'appel de fonctions simulées est activé. Vous risquez de voir les performances se dégrader.", "xpack.observabilityAiAssistant.stopGeneratingButtonLabel": "Arrêter la génération", - "xpack.observabilityAiAssistant.suggestedFunctionEvent": "a demandé la fonction {functionName}", - "xpack.observabilityAiAssistant.technicalPreviewBadgeDescription": "GTP4 est nécessaire pour bénéficier d'une meilleure expérience avec les appels de fonctions (par exemple lors de la réalisation d'analyse de la cause d'un problème, de la visualisation de données et autres). GPT3.5 peut fonctionner pour certains des workflows les plus simples comme les explications d'erreurs ou pour bénéficier d'une expérience comparable à ChatGPT au sein de Kibana à partir du moment où les appels de fonctions ne sont pas fréquents.", "xpack.observabilityAiAssistant.tokenLimitError": "La conversation dépasse la limite de token. La limite de token maximale est **{tokenLimit}**, mais la conversation a **{tokenCount}** tokens. Veuillez démarrer une nouvelle conversation pour continuer.", - "xpack.observabilityAiAssistant.userExecutedFunctionEvent": "a exécuté la fonction {functionName}", - "xpack.observabilityAiAssistant.userSuggestedFunctionEvent": "a demandé la fonction {functionName}", "xpack.observabilityAiAssistant.visualizeThisQuery": "Visualiser cette requête", - "xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} ou vérifiez {trainedModelsLink} pour vous assurer que {modelName} est déployé et en cours d'exécution.", - "xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "Configuration de la base de connaissances", - "xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "Inspecter les problèmes", - "xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "Problèmes", - "xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "La base de connaissances a été installée avec succès", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel": "Le modèle {modelName} n'est pas déployé", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "L'état d'allocation de {modelName} est {allocationState}", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel": "L'état de déploiement de {modelName} est {deploymentState}", - "xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel": "Installer la base de connaissances", - "xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel": "Modèles entraînés", - "xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel": "Nous configurons votre base de connaissances. Cette opération peut prendre quelques minutes. Vous pouvez continuer à utiliser l'Assistant lors de ce processus.", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "Impossible de charger les connecteurs", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "Vous n'avez pas les autorisations requises pour charger les connecteurs", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "Votre base de connaissances n'a pas été configurée.", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "Réessayer l'installation", "xpack.observabilityAiAssistantManagement.apmSettings.save.error": "Une erreur s'est produite lors de l'enregistrement des paramètres", "xpack.observabilityAiAssistantManagement.app.description": "Gérer l'Assistant d'IA pour Observability.", "xpack.observabilityAiAssistantManagement.app.title": "Assistant d'IA pour Observability", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 75cf864832ceb..286502a55dcdb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -32346,92 +32346,27 @@ "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.prompt": "SLOとは何ですか?", "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.title": "SLO", "xpack.observabilityAiAssistant.appTitle": "オブザーバビリティAI Assistant", - "xpack.observabilityAiAssistant.askAssistantButton.buttonLabel": "アシスタントに聞く", - "xpack.observabilityAiAssistant.askAssistantButton.popoverContent": "Elastic Assistantでデータに関するインサイトを得ましょう", - "xpack.observabilityAiAssistant.askAssistantButton.popoverTitle": "AI Assistant for Observability", - "xpack.observabilityAiAssistant.assistantSetup.title": "Elastic AI Assistantへようこそ", "xpack.observabilityAiAssistant.changesList.dotImpactHigh": "高", "xpack.observabilityAiAssistant.changesList.dotImpactLow": "低", "xpack.observabilityAiAssistant.changesList.dotImpactMedium": "中", "xpack.observabilityAiAssistant.changesList.labelColumnTitle": "ラベル", "xpack.observabilityAiAssistant.changesList.noChangesDetected": "変更が検出されません", "xpack.observabilityAiAssistant.changesList.trendColumnTitle": "傾向", - "xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "メニュー", - "xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "さらにアクションを表示", - "xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents": "{count}件のイベントを非表示", - "xpack.observabilityAiAssistant.chatCollapsedItems.showEvents": "{count}件のイベントを表示", - "xpack.observabilityAiAssistant.chatCollapsedItems.toggleButtonLabel": "アイテムを表示/非表示", "xpack.observabilityAiAssistant.chatCompletionError.conversationNotFoundError": "会話が見つかりません", "xpack.observabilityAiAssistant.chatCompletionError.internalServerError": "内部サーバーエラーが発生しました", "xpack.observabilityAiAssistant.chatCompletionError.tokenLimitReachedError": "トークンの上限に達しました。トークンの上限は{tokenLimit}ですが、現在の会話には{tokenCount}個のトークンがあります。", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "会話リストを展開", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新しいチャット", - "xpack.observabilityAiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantForObservabilityLabel": "AI assistant for Observabilityチャットフライアウト", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "会話リストを折りたたむ", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "会話リストを展開", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel": "新しいチャット", - "xpack.observabilityAiAssistant.chatHeader.actions.connector": "コネクター", - "xpack.observabilityAiAssistant.chatHeader.actions.copyConversation": "会話をコピー", - "xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase": "ナレッジベースを管理", - "xpack.observabilityAiAssistant.chatHeader.actions.settings": "AI Assistant設定", - "xpack.observabilityAiAssistant.chatHeader.actions.title": "アクション", - "xpack.observabilityAiAssistant.chatHeader.editConversationInput": "会話を編集", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "会話に移動", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "フライアウトモードを切り替え", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "チャットを固定", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "チャットを固定解除", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "会話に移動", - "xpack.observabilityAiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", - "xpack.observabilityAiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "送信", "xpack.observabilityAiAssistant.chatService.div.helloLabel": "こんにちは", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage": "メッセージをコピー", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful": "コピーされたメッセージ", - "xpack.observabilityAiAssistant.chatTimeline.actions.editPrompt": "プロンプトを編集", - "xpack.observabilityAiAssistant.chatTimeline.actions.inspectPrompt": "プロンプトを検査", - "xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic Assistant", - "xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "システム", - "xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "あなた", - "xpack.observabilityAiAssistant.checkingKbAvailability": "ナレッジベースの利用可能性を確認中", "xpack.observabilityAiAssistant.connectorSelector.connectorSelectLabel": "コネクター:", "xpack.observabilityAiAssistant.connectorSelector.empty": "コネクターなし", "xpack.observabilityAiAssistant.connectorSelector.error": "コネクターを読み込めませんでした", - "xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel": "削除", - "xpack.observabilityAiAssistant.conversationList.errorMessage": "の読み込みに失敗しました", - "xpack.observabilityAiAssistant.conversationList.noConversations": "会話なし", - "xpack.observabilityAiAssistant.conversationList.title": "以前", "xpack.observabilityAiAssistant.conversationsDeepLinkTitle": "会話", - "xpack.observabilityAiAssistant.conversationStartTitle": "会話を開始しました", - "xpack.observabilityAiAssistant.couldNotFindConversationContent": "id {conversationId}の会話が見つかりませんでした。会話が存在し、それにアクセスできることを確認してください。", - "xpack.observabilityAiAssistant.couldNotFindConversationTitle": "会話が見つかりません", - "xpack.observabilityAiAssistant.disclaimer.disclaimerLabel": "このチャットは、LLMプロバイダーとの統合によって提供されています。LLMは、正しくない情報を正しい情報であるかのように表示する場合があることが知られています。Elasticは、構成やLLMプロバイダーへの接続、お客様のナレッジベースへの接続はサポートしますが、LLMの応答については責任を負いません。", - "xpack.observabilityAiAssistant.emptyConversationTitle": "新しい会話", - "xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase": "ナレッジベースをセットアップできませんでした", - "xpack.observabilityAiAssistant.errorUpdatingConversation": "会話を更新できませんでした", - "xpack.observabilityAiAssistant.executedFunctionFailureEvent": "関数{functionName}の実行に失敗しました", "xpack.observabilityAiAssistant.experimentalTitle": "テクニカルプレビュー", "xpack.observabilityAiAssistant.experimentalTooltip": "この機能はテクニカルプレビュー中であり、将来のリリースでは変更されたり完全に削除されたりする場合があります。Elasticはすべての問題の修正に努めますが、テクニカルプレビュー中の機能には正式なGA機能のサポートSLAが適用されません。", "xpack.observabilityAiAssistant.failedLoadingResponseText": "応答の読み込みに失敗しました", - "xpack.observabilityAiAssistant.failedToGetStatus": "モデルステータスを取得できませんでした。", "xpack.observabilityAiAssistant.failedToLoadResponse": "AIアシスタントからの応答を読み込めませんでした", - "xpack.observabilityAiAssistant.failedToSetupKnowledgeBase": "ナレッジベースをセットアップできませんでした。", "xpack.observabilityAiAssistant.featureRegistry.featureName": "オブザーバビリティAI Assistant", "xpack.observabilityAiAssistant.feedbackButtons.em.thanksForYourFeedbackLabel": "フィードバックをご提供いただき、ありがとうございました。", - "xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText": "会話を削除", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent": "この操作は元に戻すことができません。", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle": "この会話を削除しますか?", - "xpack.observabilityAiAssistant.flyout.failedToDeleteConversation": "会話を削除できませんでした", "xpack.observabilityAiAssistant.functionCallLimitExceeded": "\n\n注:Assistantは、上限を超過しても、関数を呼び出そうとします。", - "xpack.observabilityAiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "関数を選択", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction": "関数を消去", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "関数を選択", - "xpack.observabilityAiAssistant.hideExpandConversationButton.hide": "チャットを非表示", - "xpack.observabilityAiAssistant.hideExpandConversationButton.show": "チャットを表示", - "xpack.observabilityAiAssistant.incorrectLicense.body": "Elastic AI Assistantを使用するにはEnterpriseライセンスが必要です。", - "xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "ライセンスの管理", - "xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "サブスクリプションオプション", - "xpack.observabilityAiAssistant.incorrectLicense.title": "ライセンスをアップグレード", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "GenAIコネクターをセットアップ", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "Elastic AI Assistantの使用を開始するには、AIプロバイダーのコネクターを設定します。モデルは関数呼び出しをサポートしている必要があります。OpenAIまたはAzureを使用するときには、GPT4を使用することをお勧めします。", "xpack.observabilityAiAssistant.insight.actions": "アクション", "xpack.observabilityAiAssistant.insight.actions.connector": "コネクター", "xpack.observabilityAiAssistant.insight.actions.editPrompt": "プロンプトを編集", @@ -32449,7 +32384,6 @@ "xpack.observabilityAiAssistant.insight.response.startChat": "チャットを開始", "xpack.observabilityAiAssistant.insight.sendPromptEdit": "プロンプトを送信", "xpack.observabilityAiAssistant.insightModifiedPrompt": "このインサイトは修正されました。", - "xpack.observabilityAiAssistant.installingKb": "ナレッジベースをセットアップ中", "xpack.observabilityAiAssistant.lensESQLFunction.displayChart": "グラフを表示", "xpack.observabilityAiAssistant.lensESQLFunction.displayTable": "表を表示", "xpack.observabilityAiAssistant.lensESQLFunction.edit": "ビジュアライゼーションを編集", @@ -32464,42 +32398,14 @@ "xpack.observabilityAiAssistant.missingCredentialsCallout.title": "資格情報がありません", "xpack.observabilityAiAssistant.navControl.initFailureErrorTitle": "オブザーバビリティAI Assistantを初期化できませんでした", "xpack.observabilityAiAssistant.navControl.openTheAIAssistantPopoverLabel": "AI Assistantを開く", - "xpack.observabilityAiAssistant.newChatButton": "新しいチャット", - "xpack.observabilityAiAssistant.poweredByModel": "{model}で駆動", - "xpack.observabilityAiAssistant.prompt.functionList.filter": "フィルター", - "xpack.observabilityAiAssistant.prompt.functionList.functionList": "関数リスト", - "xpack.observabilityAiAssistant.prompt.placeholder": "アシスタントにメッセージを送信", - "xpack.observabilityAiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "オプションを選択", "xpack.observabilityAiAssistant.regenerateResponseButtonLabel": "再生成", "xpack.observabilityAiAssistant.requiredConnectorField": "コネクターが必要です。", "xpack.observabilityAiAssistant.requiredMessageTextField": "メッセージが必要です。", "xpack.observabilityAiAssistant.resetDefaultPrompt": "デフォルトにリセット", "xpack.observabilityAiAssistant.runThisQuery": "結果を表示", - "xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel": "コネクターを管理", - "xpack.observabilityAiAssistant.setupKb": "ナレッジベースを設定することで、エクスペリエンスが改善されます。", - "xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel": "シミュレートされた関数呼び出しが有効です。パフォーマンスが劣化する場合があります。", "xpack.observabilityAiAssistant.stopGeneratingButtonLabel": "生成を停止", - "xpack.observabilityAiAssistant.suggestedFunctionEvent": "関数{functionName}を要求しました", - "xpack.observabilityAiAssistant.technicalPreviewBadgeDescription": "関数呼び出し(根本原因分析やデータの視覚化など)を使用する際に、より一貫性のあるエクスペリエンスを実現するために、GPT4が必要です。GPT3.5は、エラーの説明などのシンプルなワークフローの一部や、頻繁な関数呼び出しの使用が必要とされないKibana内のエクスペリエンスなどのChatGPTで機能します。", "xpack.observabilityAiAssistant.tokenLimitError": "会話はトークンの上限を超えました。トークンの最大上限は**{tokenLimit}**ですが、現在の会話には**{tokenCount}**個のトークンがあります。続行するには、新しい会話を開始してください。", - "xpack.observabilityAiAssistant.userExecutedFunctionEvent": "関数{functionName}を実行しました", - "xpack.observabilityAiAssistant.userSuggestedFunctionEvent": "関数{functionName}を要求しました", "xpack.observabilityAiAssistant.visualizeThisQuery": "このクエリーを可視化", - "xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink}か、{trainedModelsLink}を確認して、{modelName}がデプロイされ、実行中であることを確かめてください。", - "xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "ナレッジベースをセットアップ中", - "xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "問題を検査", - "xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "問題", - "xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "ナレッジベースは正常にインストールされました", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel": "モデル\"{modelName}\"はデプロイされていません", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "\"{modelName}\"の割り当て状態は{allocationState}です", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel": "\"{modelName}\"のデプロイ状態は{deploymentState}です", - "xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel": "ナレッジベースをインストール", - "xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel": "学習済みモデル", - "xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel": "ナレッジベースをセットアップしています。これには数分かかる場合があります。この処理の実行中には、アシスタントを使用し続けることができます。", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "コネクターを読み込めませんでした", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "コネクターを取得するために必要な権限が不足しています", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "ナレッジベースはセットアップされていません。", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "インストールを再試行", "xpack.observabilityAiAssistantManagement.apmSettings.save.error": "設定の保存中にエラーが発生しました", "xpack.observabilityAiAssistantManagement.app.description": "AI Assistant for Observabilityを管理します。", "xpack.observabilityAiAssistantManagement.app.title": "AI Assistant for Observability", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d7f138cdc84a2..db29db979550f 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -32389,92 +32389,27 @@ "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.prompt": "什么是 SLO?", "xpack.observabilityAiAssistant.app.starterPrompts.whatAreSlos.title": "SLO", "xpack.observabilityAiAssistant.appTitle": "Observability AI 助手", - "xpack.observabilityAiAssistant.askAssistantButton.buttonLabel": "询问助手", - "xpack.observabilityAiAssistant.askAssistantButton.popoverContent": "使用 Elastic 助手深入了解您的数据", - "xpack.observabilityAiAssistant.askAssistantButton.popoverTitle": "适用于 Observability 的 AI 助手", - "xpack.observabilityAiAssistant.assistantSetup.title": "欢迎使用 Elastic AI 助手", "xpack.observabilityAiAssistant.changesList.dotImpactHigh": "高", "xpack.observabilityAiAssistant.changesList.dotImpactLow": "低", "xpack.observabilityAiAssistant.changesList.dotImpactMedium": "中", "xpack.observabilityAiAssistant.changesList.labelColumnTitle": "标签", "xpack.observabilityAiAssistant.changesList.noChangesDetected": "未检测到更改", "xpack.observabilityAiAssistant.changesList.trendColumnTitle": "趋势", - "xpack.observabilityAiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "菜单", - "xpack.observabilityAiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "更多操作", - "xpack.observabilityAiAssistant.chatCollapsedItems.hideEvents": "隐藏 {count} 个事件", - "xpack.observabilityAiAssistant.chatCollapsedItems.showEvents": "显示 {count} 个事件", - "xpack.observabilityAiAssistant.chatCollapsedItems.toggleButtonLabel": "显示/隐藏项目", "xpack.observabilityAiAssistant.chatCompletionError.conversationNotFoundError": "未找到对话", "xpack.observabilityAiAssistant.chatCompletionError.internalServerError": "发生内部服务器错误", "xpack.observabilityAiAssistant.chatCompletionError.tokenLimitReachedError": "达到了词元限制。词元限制为 {tokenLimit},但当前对话具有 {tokenCount} 个词元。", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "展开对话列表", - "xpack.observabilityAiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新聊天", - "xpack.observabilityAiAssistant.chatFlyout.euiFlyoutResizable.aiAssistantForObservabilityLabel": "适用于 Observability 聊天浮出控件的 AI 助手", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "折叠对话列表", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "展开对话列表", - "xpack.observabilityAiAssistant.chatFlyout.euiToolTip.newChatLabel": "新聊天", - "xpack.observabilityAiAssistant.chatHeader.actions.connector": "连接器", - "xpack.observabilityAiAssistant.chatHeader.actions.copyConversation": "复制对话", - "xpack.observabilityAiAssistant.chatHeader.actions.knowledgeBase": "管理知识库", - "xpack.observabilityAiAssistant.chatHeader.actions.settings": "AI 助手设置", - "xpack.observabilityAiAssistant.chatHeader.actions.title": "操作", - "xpack.observabilityAiAssistant.chatHeader.editConversationInput": "编辑对话", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "导航到对话", - "xpack.observabilityAiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "切换浮出控件模式", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "停靠聊天", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "取消停靠聊天", - "xpack.observabilityAiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "导航到对话", - "xpack.observabilityAiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", - "xpack.observabilityAiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "提交", "xpack.observabilityAiAssistant.chatService.div.helloLabel": "您好", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage": "复制消息", - "xpack.observabilityAiAssistant.chatTimeline.actions.copyMessageSuccessful": "已复制消息", - "xpack.observabilityAiAssistant.chatTimeline.actions.editPrompt": "编辑提示", - "xpack.observabilityAiAssistant.chatTimeline.actions.inspectPrompt": "检查提示", - "xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic 助手", - "xpack.observabilityAiAssistant.chatTimeline.messages.system.label": "系统", - "xpack.observabilityAiAssistant.chatTimeline.messages.user.label": "您", - "xpack.observabilityAiAssistant.checkingKbAvailability": "正在检查知识库的可用性", "xpack.observabilityAiAssistant.connectorSelector.connectorSelectLabel": "连接器:", "xpack.observabilityAiAssistant.connectorSelector.empty": "无连接器", "xpack.observabilityAiAssistant.connectorSelector.error": "无法加载连接器", - "xpack.observabilityAiAssistant.conversationList.deleteConversationIconLabel": "删除", - "xpack.observabilityAiAssistant.conversationList.errorMessage": "无法加载", - "xpack.observabilityAiAssistant.conversationList.noConversations": "无对话", - "xpack.observabilityAiAssistant.conversationList.title": "以前", "xpack.observabilityAiAssistant.conversationsDeepLinkTitle": "对话", - "xpack.observabilityAiAssistant.conversationStartTitle": "已开始对话", - "xpack.observabilityAiAssistant.couldNotFindConversationContent": "找不到 ID 为 {conversationId} 的对话。请确保该对话存在并且您具有访问权限。", - "xpack.observabilityAiAssistant.couldNotFindConversationTitle": "未找到对话", - "xpack.observabilityAiAssistant.disclaimer.disclaimerLabel": "通过集成 LLM 提供商来支持此聊天。众所周知,LLM 有时会提供错误信息,好像它是正确的。Elastic 支持配置并连接到 LLM 提供商和知识库,但不对 LLM 响应负责。", - "xpack.observabilityAiAssistant.emptyConversationTitle": "新对话", - "xpack.observabilityAiAssistant.errorSettingUpKnowledgeBase": "无法设置知识库", - "xpack.observabilityAiAssistant.errorUpdatingConversation": "无法更新对话", - "xpack.observabilityAiAssistant.executedFunctionFailureEvent": "无法执行函数 {functionName}", "xpack.observabilityAiAssistant.experimentalTitle": "技术预览", "xpack.observabilityAiAssistant.experimentalTooltip": "此功能处于技术预览状态,在未来版本中可能会更改或完全移除。Elastic 将努力修复任何问题,但处于技术预览状态的功能不受正式 GA 功能支持 SLA 的约束。", "xpack.observabilityAiAssistant.failedLoadingResponseText": "无法加载响应", - "xpack.observabilityAiAssistant.failedToGetStatus": "无法获取模型状态。", "xpack.observabilityAiAssistant.failedToLoadResponse": "无法加载来自 AI 助手的响应", - "xpack.observabilityAiAssistant.failedToSetupKnowledgeBase": "无法设置知识库。", "xpack.observabilityAiAssistant.featureRegistry.featureName": "Observability AI 助手", "xpack.observabilityAiAssistant.feedbackButtons.em.thanksForYourFeedbackLabel": "感谢您提供反馈", - "xpack.observabilityAiAssistant.flyout.confirmDeleteButtonText": "删除对话", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationContent": "此操作无法撤消。", - "xpack.observabilityAiAssistant.flyout.confirmDeleteConversationTitle": "删除此对话?", - "xpack.observabilityAiAssistant.flyout.failedToDeleteConversation": "无法删除对话", "xpack.observabilityAiAssistant.functionCallLimitExceeded": "\n\n注意:即使超出了限制,助手仍尝试调用了函数", - "xpack.observabilityAiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "选择函数", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.clearFunction": "清除函数", - "xpack.observabilityAiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "选择函数", - "xpack.observabilityAiAssistant.hideExpandConversationButton.hide": "隐藏聊天", - "xpack.observabilityAiAssistant.hideExpandConversationButton.show": "显示聊天", - "xpack.observabilityAiAssistant.incorrectLicense.body": "您需要企业级许可证才能使用 Elastic AI 助手。", - "xpack.observabilityAiAssistant.incorrectLicense.manageLicense": "管理许可证", - "xpack.observabilityAiAssistant.incorrectLicense.subscriptionPlansButton": "订阅计划", - "xpack.observabilityAiAssistant.incorrectLicense.title": "升级您的许可证", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.buttonLabel": "设置 GenAI 连接器", - "xpack.observabilityAiAssistant.initialSetupPanel.setupConnector.description2": "通过为您的 AI 提供商设置连接器,开始使用 Elastic AI 助手。此模型需要支持函数调用。使用 OpenAI 或 Azure 时,建议使用 GPT4。", "xpack.observabilityAiAssistant.insight.actions": "操作", "xpack.observabilityAiAssistant.insight.actions.connector": "连接器", "xpack.observabilityAiAssistant.insight.actions.editPrompt": "编辑提示", @@ -32492,7 +32427,6 @@ "xpack.observabilityAiAssistant.insight.response.startChat": "开始聊天", "xpack.observabilityAiAssistant.insight.sendPromptEdit": "发送提示", "xpack.observabilityAiAssistant.insightModifiedPrompt": "此洞察已被修改。", - "xpack.observabilityAiAssistant.installingKb": "正在设置知识库", "xpack.observabilityAiAssistant.lensESQLFunction.displayChart": "显示图表", "xpack.observabilityAiAssistant.lensESQLFunction.displayTable": "显示表", "xpack.observabilityAiAssistant.lensESQLFunction.edit": "编辑可视化", @@ -32507,42 +32441,14 @@ "xpack.observabilityAiAssistant.missingCredentialsCallout.title": "凭据缺失", "xpack.observabilityAiAssistant.navControl.initFailureErrorTitle": "无法初始化 Observability AI 助手", "xpack.observabilityAiAssistant.navControl.openTheAIAssistantPopoverLabel": "打开 AI 助手", - "xpack.observabilityAiAssistant.newChatButton": "新聊天", - "xpack.observabilityAiAssistant.poweredByModel": "由 {model} 提供支持", - "xpack.observabilityAiAssistant.prompt.functionList.filter": "筛选", - "xpack.observabilityAiAssistant.prompt.functionList.functionList": "函数列表", - "xpack.observabilityAiAssistant.prompt.placeholder": "向助手发送消息", - "xpack.observabilityAiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "选择选项", "xpack.observabilityAiAssistant.regenerateResponseButtonLabel": "重新生成", "xpack.observabilityAiAssistant.requiredConnectorField": "“连接器”必填。", "xpack.observabilityAiAssistant.requiredMessageTextField": "“消息”必填。", "xpack.observabilityAiAssistant.resetDefaultPrompt": "重置为默认值", "xpack.observabilityAiAssistant.runThisQuery": "显示结果", - "xpack.observabilityAiAssistant.settingsPage.goToConnectorsButtonLabel": "管理连接器", - "xpack.observabilityAiAssistant.setupKb": "通过设置知识库来改进体验。", - "xpack.observabilityAiAssistant.simulatedFunctionCallingCalloutLabel": "模拟函数调用已启用。您可能会面临性能降级。", "xpack.observabilityAiAssistant.stopGeneratingButtonLabel": "停止生成", - "xpack.observabilityAiAssistant.suggestedFunctionEvent": "已请求函数 {functionName}", - "xpack.observabilityAiAssistant.technicalPreviewBadgeDescription": "需要 GPT4 以在使用函数调用时(例如,执行根本原因分析、数据可视化等时候)获得更加一致的体验。GPT3.5 可作用于某些更简单的工作流(如解释错误),或在 Kibana 中获得不需要频繁使用函数调用的与 ChatGPT 类似的体验。", "xpack.observabilityAiAssistant.tokenLimitError": "此对话已超出词元限制。最大词元限制为 **{tokenLimit}**,但当前对话具有 **{tokenCount}** 个词元。请启动新对话以继续。", - "xpack.observabilityAiAssistant.userExecutedFunctionEvent": "已执行函数 {functionName}", - "xpack.observabilityAiAssistant.userSuggestedFunctionEvent": "已请求函数 {functionName}", "xpack.observabilityAiAssistant.visualizeThisQuery": "可视化此查询", - "xpack.observabilityAiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} 或检查 {trainedModelsLink},确保 {modelName} 已部署并正在运行。", - "xpack.observabilityAiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "正在设置知识库", - "xpack.observabilityAiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "检查问题", - "xpack.observabilityAiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "问题", - "xpack.observabilityAiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "已成功安装知识库", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotDeployedLabel": "未部署模型 {modelName}", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "{modelName} 的分配状态为 {allocationState}", - "xpack.observabilityAiAssistant.welcomeMessage.modelIsNotStartedLabel": "{modelName} 的部署状态为 {deploymentState}", - "xpack.observabilityAiAssistant.welcomeMessage.retryButtonLabel": "安装知识库", - "xpack.observabilityAiAssistant.welcomeMessage.trainedModelsLinkLabel": "已训练模型", - "xpack.observabilityAiAssistant.welcomeMessage.weAreSettingUpTextLabel": "我们正在设置您的知识库。这可能需要若干分钟。此进程处于运行状态时,您可以继续使用该助手。", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "无法加载连接器", - "xpack.observabilityAiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "缺少获取连接器所需的权限", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "尚未设置您的知识库。", - "xpack.observabilityAiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "重试安装", "xpack.observabilityAiAssistantManagement.apmSettings.save.error": "保存设置时出错", "xpack.observabilityAiAssistantManagement.app.description": "管理适用于 Observability 的 AI 助手。", "xpack.observabilityAiAssistantManagement.app.title": "适用于 Observability 的 AI 助手", From 59a6324977878c9474aba4c7cf1b62c4b0a6606b Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 1 Oct 2024 17:54:26 +0200 Subject: [PATCH 10/23] Fix typing and jest tests --- .../test_suites/core_plugins/rendering.ts | 1 + .../src/chat/chat_item_title.tsx | 12 ++-- .../src/chat/incorrect_license_panel.tsx | 4 +- .../src/hooks/use_conversation.test.tsx | 20 +++--- ..._timeline_items_from_conversation.test.tsx | 8 +-- .../packages/kbn-ai-assistant/tsconfig.json | 3 +- .../public/index.ts | 3 + .../hooks/__storybook_mocks__/use_chat.ts | 10 --- .../__storybook_mocks__/use_conversation.ts | 20 ------ .../use_conversation_list.ts | 31 --------- .../__storybook_mocks__/use_conversations.ts | 63 ------------------- .../__storybook_mocks__/use_current_user.ts | 16 ----- .../use_genai_connectors.ts | 19 ------ .../__storybook_mocks__/use_knowledge_base.ts | 23 ------- .../tsconfig.json | 1 - 15 files changed, 25 insertions(+), 209 deletions(-) delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_current_user.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_genai_connectors.ts delete mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index f1922eee52380..8c7f165597227 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -205,6 +205,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'vis_type_xy.readOnly (boolean?|never)', 'vis_type_vega.enableExternalUrls (boolean?)', 'xpack.actions.email.domain_allowlist (array)', + 'xpack.aiAssistant.ui.enabled (boolean?)', 'xpack.apm.serviceMapEnabled (boolean?)', 'xpack.apm.ui.enabled (boolean?)', 'xpack.apm.ui.maxTraceItems (number?)', diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx index 2749ef3635f40..d6a26b0287e46 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_item_title.tsx @@ -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 ? ( -
    - {actionsTrigger} -
    - ) : null} + {actionsTrigger ?
    {actionsTrigger}
    : null} ); } diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx index 8cc4c68cd338d..c7d9c649f49c2 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/incorrect_license_panel.tsx @@ -19,8 +19,8 @@ import { import { css } from '@emotion/css'; import { i18n } from '@kbn/i18n'; import { euiThemeVars } from '@kbn/ui-theme'; +import { elasticAiAssistantImage } from '@kbn/observability-ai-assistant-plugin/public'; import { UPGRADE_LICENSE_TITLE } from '../i18n'; -import ctaImage from '../assets/elastic_ai_assistant.png'; import { useLicenseManagementLocator } from '../hooks/use_license_management_locator'; const incorrectLicenseContainer = css` @@ -39,7 +39,7 @@ export function IncorrectLicensePanel() { justifyContent="center" className={incorrectLicenseContainer} > - +

    {UPGRADE_LICENSE_TITLE}

    diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx index aa401cd38c6f5..7edc789a0994c 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx @@ -66,18 +66,14 @@ 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, + }), }, }; diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx index 6fb7e1a323d08..337c11419209e 100644 --- a/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/get_timeline_items_from_conversation.test.tsx @@ -23,12 +23,8 @@ function Providers({ children }: { children: React.ReactElement }) { mockChatService, - }, - }, + observabilityAIAssistant: { + useObservabilityAIAssistantChatService: () => mockChatService, }, }} > diff --git a/x-pack/packages/kbn-ai-assistant/tsconfig.json b/x-pack/packages/kbn-ai-assistant/tsconfig.json index 574422363ffca..c8d91c9d37450 100644 --- a/x-pack/packages/kbn-ai-assistant/tsconfig.json +++ b/x-pack/packages/kbn-ai-assistant/tsconfig.json @@ -5,7 +5,8 @@ "types": [ "jest", "node", - "react" + "react", + "@emotion/react/types/css-prop" ] }, "include": [ diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts index ab2dea089dcf1..76e643c6ae0d5 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/index.ts @@ -19,6 +19,7 @@ import type { RenderFunction, DiscoveredDataset, } from './types'; +import elasticAiAssistantImg from './assets/elastic_ai_assistant.png'; export type { ObservabilityAIAssistantPublicSetup, @@ -101,6 +102,8 @@ export { aiAssistantPreferredAIAssistantType, } from '../common/ui_settings/settings_keys'; +export const elasticAiAssistantImage = elasticAiAssistantImg; + export const plugin: PluginInitializer< ObservabilityAIAssistantPublicSetup, ObservabilityAIAssistantPublicStart, diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts deleted file mode 100644 index 538293f5607ad..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_chat.ts +++ /dev/null @@ -1,10 +0,0 @@ -/* - * 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 function useChat() { - return { next: () => {}, messages: [], setMessages: () => {}, state: undefined, stop: () => {} }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts deleted file mode 100644 index 8bc8f54e9ac8d..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * 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 { Subject } from 'rxjs'; - -export function useConversation() { - return { - conversation: {}, - state: 'idle', - next: new Subject(), - stop: () => {}, - messages: [], - saveTitle: () => {}, - scope: 'all', - }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts deleted file mode 100644 index f569374d5bd49..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversation_list.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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 { buildConversation } from '@kbn/ai-assistant/src/utils/builders'; - -export function useConversationList() { - return { - deleteConversation: () => {}, - conversations: { - loading: false, - error: undefined, - value: { - conversations: [ - buildConversation({ - conversation: { - id: 'foo', - title: 'Why is database service responding with errors after I did rm -rf /postgres', - last_updated: '', - }, - }), - ], - }, - refresh: () => {}, - }, - isLoading: false, - }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts deleted file mode 100644 index 24f20834d785f..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_conversations.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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 { uniqueId } from 'lodash'; -import { buildConversation } from '@kbn/ai-assistant/src/utils/builders'; - -export function useConversations() { - return [ - buildConversation({ - conversation: { - id: uniqueId(), - title: 'Investigation into Cart service degradation', - last_updated: '', - }, - }), - buildConversation({ - conversation: { - id: uniqueId(), - title: 'Why is database service responding with errors after I did rm -rf /postgres', - last_updated: '', - }, - }), - buildConversation({ - conversation: { - id: uniqueId(), - title: 'Why is database service responding with errors after I did rm -rf /postgres', - last_updated: '', - }, - }), - buildConversation({ - conversation: { - id: uniqueId(), - title: 'Why is database service responding with errors after I did rm -rf /postgres', - last_updated: '', - }, - }), - buildConversation({ - conversation: { - id: uniqueId(), - title: 'Why is database service responding with errors after I did rm -rf /postgres', - last_updated: '', - }, - }), - buildConversation({ - conversation: { - id: uniqueId(), - title: 'Why is database service responding with errors after I did rm -rf /postgres', - last_updated: '', - }, - }), - buildConversation({ - conversation: { - id: uniqueId(), - title: 'Why is database service responding with errors after I did rm -rf /postgres', - last_updated: '', - }, - }), - ]; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_current_user.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_current_user.ts deleted file mode 100644 index cbeba226f3835..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_current_user.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * 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 function useCurrentUser() { - return { - username: 'john_doe', - email: 'john.doe@example.com', - full_name: 'John Doe', - roles: ['user', 'editor'], - enabled: true, - }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_genai_connectors.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_genai_connectors.ts deleted file mode 100644 index 9d158b7474c75..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_genai_connectors.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * 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 { UseGenAIConnectorsResult } from '../use_genai_connectors'; - -export function useGenAIConnectors(): UseGenAIConnectorsResult { - return { - connectors: [], - loading: false, - error: undefined, - selectedConnector: 'foo', - selectConnector: (id: string) => {}, - reloadConnectors: () => {}, - }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts deleted file mode 100644 index 208e3498bcddd..0000000000000 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_knowledge_base.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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 { UseKnowledgeBaseResult } from '@kbn/ai-assistant'; - -export function useKnowledgeBase(): UseKnowledgeBaseResult { - return { - install: async () => {}, - isInstalling: false, - status: { - loading: false, - refresh: () => {}, - error: undefined, - value: { - ready: true, - }, - }, - }; -} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index 81acb9e5b226f..aeab635d676d6 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -10,7 +10,6 @@ "scripts/**/*", "typings/**/*", "public/**/*.json", - "server/**/*", ], "kbn_references": [ "@kbn/es-types", From c5c19d874e7b5ec9ad201e5a112707ae4b971b1a Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 1 Oct 2024 17:55:22 +0200 Subject: [PATCH 11/23] Fix typing and jest tests --- .../src/hooks/__storybook_mocks__/use_chat.ts | 10 +++ .../__storybook_mocks__/use_conversation.ts | 20 ++++++ .../use_conversation_list.ts | 31 +++++++++ .../__storybook_mocks__/use_conversations.ts | 63 ++++++++++++++++++ .../__storybook_mocks__/use_current_user.ts | 16 +++++ .../use_genai_connectors.ts | 19 ++++++ .../__storybook_mocks__/use_knowledge_base.ts | 23 +++++++ .../public/assets/elastic_ai_assistant.png | Bin 0 -> 95099 bytes 8 files changed, 182 insertions(+) create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_chat.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation_list.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversations.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_current_user.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_genai_connectors.ts create mode 100644 x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_knowledge_base.ts create mode 100644 x-pack/plugins/observability_solution/observability_ai_assistant/public/assets/elastic_ai_assistant.png diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_chat.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_chat.ts new file mode 100644 index 0000000000000..538293f5607ad --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_chat.ts @@ -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 function useChat() { + return { next: () => {}, messages: [], setMessages: () => {}, state: undefined, stop: () => {} }; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation.ts new file mode 100644 index 0000000000000..8bc8f54e9ac8d --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation.ts @@ -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 { Subject } from 'rxjs'; + +export function useConversation() { + return { + conversation: {}, + state: 'idle', + next: new Subject(), + stop: () => {}, + messages: [], + saveTitle: () => {}, + scope: 'all', + }; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation_list.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation_list.ts new file mode 100644 index 0000000000000..0fc7f6d64401a --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversation_list.ts @@ -0,0 +1,31 @@ +/* + * 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 { buildConversation } from '../../utils/builders'; + +export function useConversationList() { + return { + deleteConversation: () => {}, + conversations: { + loading: false, + error: undefined, + value: { + conversations: [ + buildConversation({ + conversation: { + id: 'foo', + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + ], + }, + refresh: () => {}, + }, + isLoading: false, + }; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversations.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversations.ts new file mode 100644 index 0000000000000..d41fd4aac1b29 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_conversations.ts @@ -0,0 +1,63 @@ +/* + * 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 { uniqueId } from 'lodash'; +import { buildConversation } from '../../utils/builders'; + +export function useConversations() { + return [ + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Investigation into Cart service degradation', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + ]; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_current_user.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_current_user.ts new file mode 100644 index 0000000000000..cbeba226f3835 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_current_user.ts @@ -0,0 +1,16 @@ +/* + * 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 function useCurrentUser() { + return { + username: 'john_doe', + email: 'john.doe@example.com', + full_name: 'John Doe', + roles: ['user', 'editor'], + enabled: true, + }; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_genai_connectors.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_genai_connectors.ts new file mode 100644 index 0000000000000..9d158b7474c75 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_genai_connectors.ts @@ -0,0 +1,19 @@ +/* + * 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 { UseGenAIConnectorsResult } from '../use_genai_connectors'; + +export function useGenAIConnectors(): UseGenAIConnectorsResult { + return { + connectors: [], + loading: false, + error: undefined, + selectedConnector: 'foo', + selectConnector: (id: string) => {}, + reloadConnectors: () => {}, + }; +} diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_knowledge_base.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_knowledge_base.ts new file mode 100644 index 0000000000000..bcb1725d35109 --- /dev/null +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/__storybook_mocks__/use_knowledge_base.ts @@ -0,0 +1,23 @@ +/* + * 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 { UseKnowledgeBaseResult } from '../use_knowledge_base'; + +export function useKnowledgeBase(): UseKnowledgeBaseResult { + return { + install: async () => {}, + isInstalling: false, + status: { + loading: false, + refresh: () => {}, + error: undefined, + value: { + ready: true, + }, + }, + }; +} diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/assets/elastic_ai_assistant.png b/x-pack/plugins/observability_solution/observability_ai_assistant/public/assets/elastic_ai_assistant.png new file mode 100644 index 0000000000000000000000000000000000000000..af1064557968369b9d426fde8eb9891240436f55 GIT binary patch literal 95099 zcmY&<1y~(Hujo0rJH_3lxVy{29ZGR`cXxMpha$yVT#6pNKyi0>_wwky|Gn?M-EY6i zW;4lTzL8{-jZ#*WMn=F#0002UvN95?000Cv000z)gZaRSFPQRv6i^nT3Zei&LjvNf z5%kA9sfmoL0s!FsK^Pbc06c%70uKQIHx>Zk*bo5V%LD*$9CO=M1U^25nrX|LD<}Zy zKVUcjG!O#-`2hhxZU7J;@Q>RE1ONdE{ufpS(*B1A0sx4x0zmzT=F3OoOH{0}t*bs^;cz`&2T0|3dY`V=1pyrYbc3jlzK`R@S&vU71ixanA_YrASI z$n%*v*fANIIvATVdD=Pts|677h5&VPj@v`}{%i*~QD=)yVU+y$i*EGx;Ar5@s$Y&Q^}DRu1;0|M(giJGi+D zl9T@v^uO1Cx6{?i{QpX_clnQ69||)6`@+o1#KQc)yg#Z6{KN7oIa`^1NdCuPh*jXf zDE~j)f8+=-{}cZIH0HlG{V(i?szL|?%>UbMLI`;JTd)9t2tZasRNWJJ+6C*GyWmCr ztXLrGK-r5>52~?F{br6mrqKMAgnz-wn196lhVr-ho9(r!3C&4r&gmM@)w0LJRr$sQ z*hyuZhe_2|F4}a2K2;*BSSA{aA3m9MdId0Z(0##sgGmaX3~6W%7kH2u@^rvl;d}6U z;4`@pU+1@SaP2svV%V+Rb096m8ECt(u!-10Hg%2CgJXO`&oO?3XX=wLBIFWo77*xW zX+DjYev!kB<$l&m4!yYW#K>krygy|lQFtWOV=!3ZtmW(M&FyIW5Bj%b1hH-#iGZu@LwMgdV`EnAeF^ z(_0;puS}dM8)d%cXl+S)ga>k)b^85e=i0I(?K5V(>CW~@Z4CF@E~dR2K$ya=h05GecUk%>$>OH#mEQWNz`sC4wqTuMzck#BG)G&t80 zaM9^MI#WOEVi0E3k|8KhBoUDbZPF|%1CX$BA6SKF3&B3fVm^|a)sM~Q$ zH_Ps0mPYrOP${%T;4xI?s9cS6iH9(S>#j-*r`b*BkeH}g3UyX094|1oGa!{x{yBo% z10pfNvzo{~Df0Si6!(Ml+I(E6{>wVJ5h;LExCsgs5) zg}f;fI1rKbdbwL3UjwGt{Me|A)3iyV5ud$u`?`5n#7EQXu4O#zG6)ivFvw&|T{6iX z?$Ab0)1{|&vUA;7wJSqXu~|HLHI1O{`6fX~kS?p`HptFuSTuVqUS;D<@$y70sQ6Nu z6BWSq!-w9oh0_Bzr22`2Ryj(?8-LC@;bXW2LnMD4cFXK7XG0Q7t1A66C|x2_45UhfD5aM!P>pBbm&R7KuL+^g#fg+$-mKdIm!rF8 zu?phw_0Kugs`LO;+U8AayVVKoEh*SC0Dy0F=H$WTd+dD z;}PJ{aAv`49L)ZZGA_E(0wpR#L0{wh;Pqjq=?%PPbPWb$08dtX-1p@=lAk-i6}z&^ zG0AaxIDxSodrZNUu6X162v`6JEsyTQJoQT2a#>WGQxE{E(xtTuiMiP2x6n008?xH% zMB6%*bda-34^9mg^*(Mv!&wA0)n@Aiihy*~j!Koodf)K#*|=_PFT0fB9(mB12eI;p z7G6_PweT4wfdB}TY>A`poU&bVr*Lj9<3*e8a(cSP9te!3tliNzkC8pg&00Mogs;$8 z^!%8RAahlBV5+Hf(ish{Y`3V%LI<2YS5j=6r$?=#Lz}7vD)cICj-cO`wJH|^8mR={ z4CT-Co?R%dfPO~Va3A)p&X{kEgHQfp6{s~5Oiqcf!Qd9`q>*V+|SRp z!6N;1IRuAxy-F-JGBI?mUY$xr>R9~1AmEo%q$wn8HSrS{{FS1)y6()8^%~SZi-AP9 zsWT5rtp@CYRu-c(cHc$bjUcHAb>e(Q_Fy4Iv@0W|bqZ+@{-;UsfV-rgB!cpHWA``Y zI&ptn)B~OKXB&z2y_%G~Q@r#zXvxWFjwzO{ zNV%y+@l_@}aMn3Q=|&J<%}=~#(-Co~PHkhnqlgz4e>ZT1@Q_k#P*pjwCsc(s&I5&J z-S?H0J#RGHx!sMlsNnpQlB zJxBAC)k>fBo)li7<0dQ01pXrd$)=}94F)U-iFK_YTJ2*bN2jb}d(P34y2Tr3OV}3; z>|zNg>#Of$*XKMpHOdcW7UEZ)-hbAL8?7}I2Ri$JvC!`)2qtqufrjaHba9snJ<6>2 z@`UegDpGDlp4pRJ=&7r6glk|VdKCk1bwX(B3K28&y^%3@3nFNG86dlEPSgsH-NAsL zv6y^{vtl1(2A?-3G*_RXPqG6>E{jChJ%>|D0QAeRKFNj^KY#B=%SAeWg|O#r1WMi1 z(Ju1Gy}#TLRBuY(k~v(vhJurg0db8UA7fem-w2QobzAzai7*V;996xP1_e(8uZ)}2 zbShc`=j&^fE|=%m9Mcop@k=vOWnx}GbK?$UYHt?5a6p2tKkk)&c7qzLi*A+_)5Z0Q zBu@I=SH8)(w&0i`EW0iTckXVRW))952&DSaWgyj+G`BRm!nx8?oX=43)jX`f1_o`Z zN^$5jd=4xp^Lr4|<&@$WAsy0)(Dcll4NeB-6b|ch3P;-rwP}G3Sp1UtBJ|XsFLhZ$ z1(|J7Q(akSU)c#WtTeB_b9~;C%1~5wCN5h|sFntPY-Yq~dLvqK5V<`jay;lS<5fxI zNb{xR@*xc7gPE6MIBH=(V|c>20N`%PYMYFG5@;v@Pq}4_5e0HaM?sqK*bQbT%br=Q zb?0yDc$>MXbEn@YMPhmSTgDt-StSx`T~THh6#eMD75{kWM1^o?Tyvi5~{3iBG&;))7|TMtd&T168Kmp8Tjb zaAmu=Sn0r7j5-X`4H@j;$b>0KM#7b&70GUHX1oZwN*|JbS46zHDJYAO_A2Ehxcf`f zavI5FmKX>-MCllVSRB&Hl#mTllkxG__kU1OG4-2q`_LA$N=;S+#0L&6U$gfrPJ`~$ zsfTm||A;_e=S0fK>asv0#tSGh_9@@owOk6>v%%3c&rA`|ktIWHGB6dn8QFmmY|Y6Q303WWX{l0s*zDK6O%U+MmY z^j4tVEvdyGIM#@f2DwwOx_t@?!>V*)7p2&K$Q;e3KFqE3<;tNwee^L(!&?Z^*R7m2 z+~L&pptsy3`T_3Veiqx8O@X5!bBLsNIfmX+Jp0TdvF|Q_9!%k za9iCv{iouOI;F31AM+SmNL;(NKh+R-4#aB!&&J2R_{9~`PWS%H%O@P$@4HGjqrl#1D=Z;2y)+P&l4EAv^mAgVhhi|nOWGR0 z^f>td8;vuu8Ld)o@Zvt}?3P3*CW@Xt+pPART)vN?U`tDLF^*XMWObB|rDQ zW|{V+G=j&r-4J!2r5Hf4#&jue+(MPrZJ-F;L*fW^k}PV%fe2l+0Y)OCYJM_0Ciiae z4K+zHp@Cr%u*jYLmvj7QAay*m6kan1=BJ#^eB~&u&Fc0r6U7D}w)^~;<6}i0gmGeo z)SJ&)g5O;^KPGc31O$9oCN)`=3)uaWNU(m4vDRRUxo=pCsqfdeXYyF69N(_`Y{)&Yd|o|nkcbrlg*sCcEAa^Ci36h$gGzfOdRqJAaVRxnd!ic1Sd$a_TsjvfSy}XZYm%CG8|ffT zolgm5zc5i@PyZ10_SDPW4I7lH34$>(%&QnM?~IMP$bZTb(~WCEF5{5}$t4f*{^nBY z5^pgRugJ>Sc6P*~<%Xu`#7yrXVhWHBI#LrEXKxQ~4QzjUBv#zS22j(pORR)U;e)K> z>*YUo0ZKMzwe(EqqvT1Y!oeD7kylY-_7io1AtjiuI%Jc_N8AUhQEU#RRJP%qrk@&_ zrL4D&WVneSFa(5dA*XFq3*sy#reJDutTHIM_}Dy@lq%Tp+o!+5Nj2&sNAUD{SHTD( zo2yZ>_yezgjXEPU`QJ{7wHXamlSqSb!VA>_PANETEjg?2!KIBul()^z|h8e&=fJp-bi+O0G zvev~uceb^*o9Ahh^nJV)_}fgS4BJ)YueF z43_t*fhn!zjo{YC@dveFr(y^VY|J7AISDNFE+V50*5=O&W*%>v5R; ziiRUyoQEsip)W!{h+F}sM0%5`JBJCjJCUF`;c%zc=T zN(Wgf8+xaLKPtf`b~AVTwg%=#c(LZf^* zdIs$}W8Ii4EgrU@1RUwLak6AO1t~ci2K);;B|FjCZ_=D`lS4-g2cemWf`D(xB#+-a z8jkzN5+4%CPAADUu|>v~p*{EIe5PGm2~6IcY~RNya0NI68`e3tyFze}&c$C~jUU5E zx{)28el1X>$Q#!%#7+N>1=9RYEX8Fn3wch!zyJwA$p}Dd$?}$N%a%RI(GRK34d)L? zFD%QH4npAL_l~mDjQQNjQu|~nfdPqz-?9M|MrU%`v6P=RtJw4pI&C9LN3WPIJTPzZ z+i#wsta3%7p%t~PYnD~VJ!cn<^;&;4M9r}46M5{?ve3|Os`I)O=X|9alLIR3Lcw#O z-Ab+`lDsmNA?U*4AYZlZQ4$*D!BGiL zmBU-D@*SH=inT~u&lBaIgAB^)Yu!0RbbOXU6}s(k=-n>&e%*b4W4(qou~dA%y0jqK z34sqyXYQMSsOejIANy|kbv@M44UNJ6yY$R4eoc1^%EsSWC8Xh{>nkos?H{-$eiS@U z%kZzaS{u^kB;W=AeXH3a@^5+sQs1(##3fRSGaJuy1iT6d*wxdBWJz(^dnD+ID{V|o zigzoZBn!!LYfXGPK+UvnNl;Xv>`MQ9Z`u@MNgj;yP%4FlTFSny`VsM`s%|TgX>c<5 z5ieW63OgTBI{UFx-4Yst-C(EcbowiHD{1Aq4*t$Dp3OHOsz3~y))FXXm2l*%QMN*+ zZ)Gn>w95GZ4o2|#+Q06@L%&X!&AQi=P7J9$R%v}`!sWsil% zfGa-R(QP>JsVnvtXZTHc+E=pSh4uc|%e^?5y)8~0P&nspRl_$~=wJIrEUL7|jESrsPhzfn) zn7<|P634@@Ggz0|Rq+cRf86!2O8xY$as9|uU^*CBE&}hBia8{EApQLr$&KTo+ypKV zrK~E7-7!k1yD7z1@PZJQDiLRStj3tg9I+J$DHv_o(*%GG2)wk^V-XfB=-a z1&z6XE)QBtAz#Ee>7=2qjBN$Zm7CO@F`g)Iplp%+c}21kDuf9$TB$8#st$2Ol;-J^ ztFWHAVxogxF)Xqj)4-v28j^-UUWk=xxU;49aQ$fz63Y877JgmA_CD%bO6TqE}_isgurqN+Nx z$B9ZSLaox3U6rOsO>aZmp1Hc|Ts@1yp~GMV0$rCt(MDnZbSa7+WFd9RR;AvTX2zz# z#$OR-&LrM#E&feowNgJi(Ay0}2r*HE8#%KDf?&WG=ULnGpcgKObiI`$)&rdt+zTFmx!N^B<9Pyt08 z_J`-#fqfr1!hWE)0!7(5amaCjW&PcwrKG(LX5(p&BpKa8rG+old)brF>zVdB;m+h> zL2xK`UX z0zd%60gfq@mGN2Y!;G>!uD_RPdZ1@BC>2tA!v3v z0(Z%$3Z{ev)%w=iM%TuAjE<^;H1l(-Y<~|3?`c7gZRaI%HrQyl+(Y*_CGf|k!&v30}cM3MkE=W_RHPD zpCXl`dpSUUT5CjDS$CtrV5Yj9D0mm*(?9O1Txh=>-S`#WA=HK4@-~V=`1~7h+3|6@ zvk`i08ScwG2ZbuZ;Eln0{MyEP)VW}YD>jO;Ep2?hiAjwC+QoB-#}2q)J+x#JWs~Ek z+6JY_ZiRl9*>1nf^`T78i7TZXDjcR#y>ggkE=JV&j7x&m>Cok?_b4R?1{S0u_=cQ= zl&8=GH7Mj%8S6y?KwdDW8Yhm8)z$9$nbm#X^GGHeqb!<9vuRNzit&QIZ7&6Svmc-B z7pa|yKq1AYiLD;el*$_-=}0Ve5OSRY+p2;>r=|_w(0q-q!D$h(XcOuaM4I`0CHq+$sfsL`O!P#+1yps}|9B@{_Eb z@08#rvzt?3+f^;~W&>05dOi~zUse2iL*Zbq6(2($W1)9QX+q`^Oa(Uwv;Y}4x`Rlm zV=^XPjVE5xH=CHd0;dSN^`r6-qiRjAO4~6#KKYicT>sr=)iK=c z>tkzX^k^x%ts$9)jI`Bz!|&XV%iqTSF;KwZBshxtd_1zBg*F%4?)cU)$&Z6R&6^wf zUU5r|605?Yy{Dw}8`I&0dO@&!J}MpNK>Ncl@l?9$6snm0-6W@dGXX7-;t^74N@n$5 zk}wxfH6LCjm29)h8R)hp;$iy>R$;@b6m;Q=poN+w(3=Ch2^#V!jvwr{&^ z_c0`}B*LH<4E*k9Nd<|;<1mfeR--9y7z<1-A_1=Gh=470j+VKS_OyF!C^bC?_wBSg z;Ru?57md}|O3wYZcpN2i84uH|?JLtZ=X|CPowhR!`BBCGKzI95P`UFfiOqA0SFi@dQOFp9(XuXszkEk9Od(wig z5w2_Dqv({}D`j_jsFVmYgh~*S`G%8C$;GbX&s@SPp4^D9sP$ck;vuy1AbvmIRe1ze z_k>}g^}vN!qepR0<1Tn{b(fei_bK| zJ7a^xH_v@2t1>!anD!AGn{(LKC;_q6cFkP=hF9@pOC8>P?1i#l(vI6drrf?#IbGR^ z`N7e6_qkd!{+@s)K{%W!B(8O<#KS~f4bk_d<0{PH6-I+i&{7*u(x(*2?|(6LxRKTq z@xIli?Y08(`p9e$>L5uRqgiJtknAt;G7Rjzq^UDHyrtC?Yy~vQkz3l@Na5T>d&G@= z!tA$rp7JThi7JA^=;uICFrU5wL0tUZhLnk?oW6sJa~h$^SZxO^{Hi9R5Tt-o(~%$u z=DBiZb`Pk7jIoa==j8~WURQAsQ76nODeI!0i!X&YJ~Y0DK%f61xj+ToVM?7b`uciP z6FQL~)z&FSg*m2%*33%L3y+^+l%H{sRy&01%sBnl>KzBaS)NG?fvb=8C5R5v7tS;_ zqE@boe6!>QN`SG4-j;1xK%*Pn6+EDmVIE&#N&~zTGVQmql37#sH19k{VAKNbY+dE9 zC6$O+eZ&xCtqu>J>F}U=!d$5EDM{g{Oa>^OipKnTif*L5ymsM;V}F`S-H~O?TIY;2 zC?&*v&92QT8qD3@%QDh>U?27W6QM($mAI(upK7%87MoFIcdRf*?bnl=T1Aa(DCkD= z{$eHlN0=`hdqU{7*Mhj`yY51lT@i#Sx($O0g5Po^uqpTK_F>aZ&Bp%ow@$$qub)eP~rx zn9eVWebx9{R1kF6qgR2UrBwJsR#h2|I_+@P5vqBw=3cRBJ1NT%n_A~1Ra()c64qoV zOa1hCl?Xt-N%&N_%R?bugA-oL4Cgk^F+rV4ct=ZNViLXHn@W4rl7(p|3&Koox|mM? zRuZ%m?j@gMXKH=}!mc&Fc!;v1K()Qp@OY*SRn(t>T6Q&G$iHB6SN(j@c z>9ARmpRA?Ntf2(0UlPWM3sd*x$H&r-66(i??Kpz{u)Vxt@$TZ6Tqi#c%5xG9C4_tM zM~tFyRM>rfHSddow(K&HT%}Gfa2KAt8xh9*TKTmIoapAZ)}$WwcRW{Sm<4CkZXL#`jegxXV$ zRlzK*P%;JHV7Jmt!x^eV$%-xyE&3uRWHC=mg<^MA3SoS69#Pq(R~5OI|7RVrLWA)@ zdknIyK5O`;wWN7ZngjldjNgS3Og_sWDDBWzY$g(4QF#k-$*R>cVr zh}GFymXgjau1JKu1@#}->$bp6tBek!P~5gw{V)C|3|zlV1lI)|Aq)Z@B?)Um0W)A^7@CD9(k3W(c=dB<&w3_MI{)sZzs(5 z)=c6osMgR}SklX45G^`@f#qvFc9F+CCE`MQNBFVA(Rd6jOnQSVQpv)+fqCk&dpXtO z&bZQ{pDQ16m{xBr$Ew_FX& zaniglB+Qt)nNo$$UD>g3>eQuT#v)7-T3hP5)3R?ml&zolno`v(CKR6|SYV z`RCt5BH%hdku|#UHrrYczR78Awy_gEPTNFWZtlAaJRHi_EH20D3Ols`@gs-@gQt^( z*oMfE3d$dTGp~m>^J^L&w4lrowJrxHoI|D}416Dz3Zz))3|5f}yPLV+b1tR0-~dP_ zCy!E1ul~_<#p=gl$0=dXYnMJVvRAJob;}LX;r{WVg@Sq|ZY7+ZoO*3wWLhU&;(lD{ zfcKWnXo-l+eS7}2gRF9F6^&E!W7ePV%lt{K=;m?Pkq>gw6lsZR%}ZP>9tu(|m}B&N zFKTW6u2d5=AIB7I1>P?DKL-`le?Vu_l4eEiONOZL+E_k_T1go-=~+ALev&l-`vOd& zC|^xh&}+X0`Lx*e$aIoe`1mwfX$UG33di;5-G1Zc2`;P7X~-kp&e9_S2x_ED!>hJF z3%;%znexB1sZMUs1@Ond-%F!z{q?BnPv~xdjYI5T&7cm+Z{UF(CK=dV;;UJA7lgsp zxI^{dviTO^$b0P5_D4vrU)=zL0v7LxWvt>Sud(SM{DtJSw@ zd4wKrb?}Lm7vkG;*d4fun^mNoCMVIjL8{>)E&GvL_%1M+C`2loyhNu`6A7TaaDu-g zJXd;v>%?8KD3?%8Ad6XPNX?}Q2yKvUZHX_Xf{+4M=}{^q6;%PTArzt3=i?>$scj;R z`*1;ily`8JH7GMX(W-a>YkGw3Nc&QN64gJeHOgGH<@cRR;HFWvz|a6~#ev0s7WR~P z^_+uq4=h@$4{w8xic?RYsxZ(D;Rj@>B!!zUb$z#KX=DgDmlK6dryS{!fL8CgoxS21Q`R$=# z@cfO)(yq2Is9(>LM%nXk(+|%*5){KzOJ@DDvk|zG5^xIeXNyKrfFTw?|FiNMMz7EN z443b@8tPU z2EvX=lP5_?L4SP(*h4lJvZ(;4WnZ}c$qv9>Gs#IIH>*E9T4F}zm`M3Sx|3$sK0 zF10FCoe07S1V&i0fIaPiR}{&u8!t`G)b4HeS3V)i4)b!u8G6uQaKA( zjTZrohhR{8JL7rjEYw&*?~u?lDL#SqaJH&9AzmcM?X2*Sc1X`~Ep=Mcd0Sx+^*#cqvoB34a%*am{r zMMLrCg$6FTiPz$8E%6m{%m^7?!rF2LHpz}cE6&MeSTJC#)M?W$5qt7~Y_vLlJ>-5X zZ%QJ0p&gpK%ZRQr)}RloPR*75-YXrN%^1!M$L#u5bi$ z#T!E)xFlQoBz$7Vg{Q7bh>jpe1F79c_tNH&(8K)}5jzp>rg#4gQYFMk$b;>3J?p#o zdg%D@0skoXEOei_*r%q2r!!?FEB7dd7s(*^O@HZ!iylqKba%E*WDsFRN=U-GGezgN z!p<7lzI!|LeWEM9c}N~&Ja}c~kKP`Ob{SfSo5L?M1gt@kw|ut|(cp-b&gYde+!{h& z_$(Ii4rpGl$6(N6>~-*ac5qy4?~;L9*!1+pF5^NXoX75u)~!B@VvlSPr+e+I+lUbL z9(uh&y)QHqMV_zwBDRu5tM7lNz(&+o2Xi#opT?#&iCAY4d%m;wO;=r92@g#Blo6u6 zDlyl$m}A-fhBFnA-Ax+%JDYHvY@nP_cN`83e zbU0?rS#?9hw`W8r<`)kGF6&-?3|RF5Kq5 zK^H6}O?=_Okx3+zgWBg9a>>A0-5I}?U|eGr;czQ~_WJAw2GtTvYSt>HXgSb;3ixuRRQDc5asffZKcN z2yykZ)1#HGe60RH8P9Yal@%X|$tv|d6_yV-EcG6p!a`~~z!YvmuC;yN%U~PL4kMk6 z6HMx!#B%<0*D>W$9P^6~ub5Kuo^pAP8w~+%DzGXK=-RzLlJ=`q_LP~633scTIriB4 z5r2hVq!^d_M%e2jtCUXl&XbD{;yW8h8fDl!Z)9T>+3Zi#`aMh%v7zc!(bm>2m-x_8 z@q4O9LDsiX1|7*5B7Vh>BgkKwMr&Lq+BUX|v3}9m(QK25H&fBml7~z9ibl;e9BEsOq^W;2%)3@C1Tus5CVo!$3&TAda zxFoF#N8slS6H0rnGCc}Ojm3Vm_~$}3VdccQ>w1(-WdNGV?dInsV(u-`nP}Am?ia2Z z{TJ*tRoe`x8iXs+SzthIf71vayd$iA`G1sTZV!}9snKi8Rm;1G-w@QfzaG3;8E zf!8FL4qSx6eU!&Q1ICeZPo8Y#^vxS|CYjKOoOe--X9WE|bTVm|R@dM@bWxyjSk-Y$ zxH@I!qVuieRrYODDSg%lFS3YVC!lZPMMXV~tHWt|hF?&Am);~OH*&wn@@?`^#+VUl zF$~JUrJ@R;kG|&)a$^|g$4KLYcXY@ z303k;<%9b2;dxg&-A6>t?A)(9Wq;oi$+Z+a`YU?bg#tr;vbl4=%-QJBGNWT!8J|^| zS+c(sc)?l{Gb9t52G%E|OEr_?)Vk^Cs%T1LaV5CwivoeIp6$fwO-2gY7~+t1Y^U*lgFQ;%V~ziRv6K2ASWM*Ek~W`VZb;4bV&I!~?5W z8G)fu24vLz&rriPKew9=CGRL5xBZ5ZdbuAliCtVe!+msW3yL<_&BX=Bc)X}ZnttP+ zC=z5g1VMqHYfc=@*1QGrCkGw6Qy{3bgiP;!u}j*%TeiQ6c7xV?vX1#}ZpPHwlL!_S zSCaFf(pp&kSGa-|^mq76y61c;9E%lz(DIb%=m2z)q}{>Ayu@6-3g`r}^)Xsa5sAse zE*#P;m($odw(+j(y+;&w#~g41XL6qP+JW(V8=qG8$4OYzIMxCpBYHJ8CRVSksL`a^rnw1X=E6ETN`6bM(gv}}WjR;AA@P3Q-&B^$agd^r z@vZ3^5@cOeQYb^?=i;n9o)}+4PzyX-cvUd&X>{?!5N!S56y#OggzCv#3`YVI#m!`2 zS?e^>;AT=p0tiD1_t~o~hxa>&De6*^_bnHLvfB`Ag5~VnTVzrYg^Jfp?qWfoTEZK3 zmwEVQkw*aW#mgfXA&)CeNXac=tBQv}CvIvOJ+zS~C~biZzglc3SQ4xm=1p1^4tCs^ z=LW)OkqqTzrhrrM94HG)W%=qHg0L9!4-B_hvG6!r16jE;J;JBIgv7btG`W+%isE14 zH7i(W+n+a4&_xySkMJt9m2HV8JLmGZrw>QzBeVqHBKTNV@yAf%04*O zZt{#FpKbr;zwL{%FIyW`$OV{&GIU_$nY)OKLLi5AWmrpXQb_7zn)p?F(O8u68OdsU z768AGtGQjWl1#8d&^E@QXCgBIa{XNhv)V>GGxYt24k&~^LL@!0|0WkZwGk^`q!kqp z5H0p{FFjjIAJ2J()OnimUhQ`~-Uzy=Bp0@ntrNTcd+&zHRfT(@{a6)~`1te6hE0-*sY3*1TRFU&xj{0deXF0xdrXQNBZwOx+aB)4f7N< z(+K^W4|(4+1jT&vD3kpw9hZcZfwS$k|_RFzAKpaMkBVgi{5vhX#-&3f85H^}L6K;fRTo#p>@d0ZZtGYF5Z>v;S5Of>ogVm0~I+U_ylRZO6jgmW_2yFpfg?B15Y-u(KD zelTO7z*A%Or!BT$6)EP>?geHN-XE9v8~#3fzs_a+MRvH2H-y)Vz$Bz5>f%s51r?U- z-=c2XKCn1bbjaY;2KOT>6D973RN;MERK^rhvx%f9v%98tBp-=%qnc|tEH}lQgx*XZ zAFWV+89{0Oo<&aswqpIROuRFhjWo-XQ7#K)^KkrC_9ncGGB&}%rSB~&q-j_DB;sfB z(sPgZzU%`Hf`e$3^81k&;y@%iA{b^lkWqli0b^^(SYn83UD_amPr5&~yjm3?$f&=(#-8*JDQ zis4<Q)cWjIYwT9110+Z^#tZvg%%Y-KEa1@|ipAnb;edEj zCHq?>iAPkl6mRJLOgc82FR*>$!dyu+DFCejS6O67@4igHC^+{pW25wPCwI5f+Pb{_ zoL}j@x6eB~&tdPFW1vg@r%gj03(dg^NQ`Bl9ILcO-F4jP&7?(K`Zwo)` z>P(RpTAwT6xw(dde>lq~gqjUkbL9VeJNwIrAfNCr<1xWr%^&pae^aa`Z=VSt_Xi8K z?C|xu24(SPV8ntdxW(HzYVO9AJV9fGCUTI9^3YQ|9a|orjfzU1U^%`y{Dre>deazGhPb|{=Ee}J1rcqw0T7lzK;+61r=$7&(*Lj%{ z53w)#)X+QwrUf%C50rg{2#yT0HAbvf^s(aiapA_FTu&x)C2I?|_J6EhYOI087giQ6 zz^zfdmQ#wHORNq+gcIt`eqT!TX(lj4EB*yr@Ep&4t8pCi?bL;1Yf>`T4IIhM=t7HP z`$xtR`@f?n_Gj!Xj=L7AXVE0ycRG>e)hjGQI#el#StyYmt*%umXXAAnAf{VfDb0H6 zX4h+HZ%{J%q5kRT;*K@bQds?4XlklQxKAude#$oU(UFTaK zgra^UIBuQO@?k|C=S<-nNL`sD0|WvFEGG2g?6jVE{KXV(D_*=?Bg6kyHA%w|PBUQ~ zN~@u-b`^3c^val>&S;de5%<-W>HxXw_*?J~n95_Vx=HE@$3`)PgU*+;8`YuElF#7# z3EZPG>UaXEay&%?lg}a?Wlp642OR1kJR6_wxxGIA)erA_`(KF2$u+xI4=ZexKHumY zc9DvFq8tqyMf7-6d%0bd(iP=+wD0(0mi9P%IwB!_@GVov|HfI5n4T5 zEL^U(F3=eF{=3rF)`)!RI{G2Uo6MD0xbZci2HP^bwAoSiz$+(`wkBpg-dzGxS=md) zYd*o99xM_@ZY+@~)1o0b8TdKeh!$0Lx#nPkhZnrU@Bf>>afgAV=747xL4bXjYxMW7 z^Q7lw`-L$bsdd!@g)8R55v_Muw070lVi-1AA&W zt2+w&m`hN~+yp-Rx^~)GWPTpw4!qgdt3+&dA+Gx;MPCv|D4KcGU_cG_zg86kHH86HmdbWso3E=@mzuT{C)|; zD@`CBYnRhx>TVw!CL-TwwJE1`FI*AT!m#UMbk9e4$E0JCc%M3vz`j`2IR(Jbyu;`& zrY9&79n@43hoBQLM^zlCuW_$HD-f*JeRpu^#}#tSIre1lb5Y+Gv=g*~&B9hMyddpR z&1CwmePraI(_0XtiGg*s{~z)MFi zxOn0(CE0YtJ{>y^D}$`MI&5X7+b_zH9kWjc=^ZmFn_?d`-^s8e1c@*mIdYGee(&j93P_bYn+Wc=Sm{)&)D$)3pKY zo2`!RtXpj5MQ(}A5NSxMB~?WhxT$dH9S#!-GB>O0WQ0U#8ByE&^Rp5ErF$+)*WZai zgTW}*dsi2Z>C2#`?ROv?@we%gwQPMu$?7jawKTIa%`6z#dI1J2G9E)3#7=tn;cp2V zBv~ZSZIy-0Zm~$Q$3?>!LW-3+Z7%Hrk3Yd*+Y>NS6qj3T$}*+t$>&YNd6inEZjRaj zc5>dhybs|8Rzp>~T17WyJmhkeY4lSWZ-j8@iUTirju%MQ)-Q>Nd?2eY4A*n9N*Gq+ z3+rTw?>OMTu-S&4{`{uEg;?y*)*Rs3uKk2h9D{LKapG||j3po%OhNNi>q#ofC~UMO zEJ^x;LVC07pMfj$y>D(>uQh)qSHf$H<^}C4bwIM=e_zI?kQi4Q zAE{}yDj=OpP(jQb8Ej<1+|I@fr$%Pg$Qf82eNpKW<4C+UCdWd^A2e_;=Bk$dqeOln z?>z;;nEnl_r6nu;733eYe@ugUzVS`h)K&mf!{swmpj>!NLi3~J|TM$KQotRwQYOF?X`D0)I@5F)X|^b2?q=y7X=~{7DbJQjP{yvSeU^Zc~(FSD%p-5W#x;hj_NxZO6{w)r05ik3C4Um1ZW3KKAxb2gm3KAnE@A z(LgT0N_UdHi#nyTrLjtILc0~haE>W`IL?d;M)S=ii33lP5xo;ea>50bBs}2=1Mn5b ztDe%kCFzMIkU9<#H0!$Z=2hL|lB2+sDc~d~QzqHW9?(m!m;~PBV3{9ZWt}&l!o)#l zW|#z5CHB0&ujXfF?XKk0DY{~h+v#YPXjhwlPRNo%2~gPZ^`g5bZchqj`@zh+oKpn^ zG}-EY+g}#U7%@4|OY)+=5zsjDbT2l3@J6mC)2LlQ5jufLcL(IOUFa8p$ zRse_paR+MFZAN0Wg>L6DoO-1Xqi5sXXt7u+$wh@uazbOxjyF5rIgZQ%Y4WwH0rNyd z#*2_+Tq>Om4fb=2ufilytz(UUy$@26$~e~Ao7g$b-01!|3QU6n4wy`XXfuC=jKHO` zQ9vs=O}^eltjSYeFJkI8iT=E9n6V~n{g#@9ldbGFc)vh|=Za~8h*pGF?V@ijVa-!Q z50r5D8Pp+EA3}or)+9O)qhES1SRSy19J z5T4>49#GLQ!!TH;IY$>?AEiS`pCp!G91`w(GO^vdzqsU;$MWf(iZqQHDUopou*FgD za(9`)XdEfQKpYEK$FSLbz7?YIk zayifBn)+Mg(Mm%m&nb^&vd7$Kx6+V=PwxdmI^Pp(2rKt^?a4$yLgQHDwWIgJycq)X z-ng2)1b5a^;8GNr94=q#Zg<{MU=C2gdb`+Fo0WK2b(m3|nAzR}5$Vw_xX`@<2Kq(j ztVx@bt&Rm6##JtAye3=%3j4ouEyCpT>&|QPtmrCD$)S|FIpqT_ zcP_%h>u$!u?>@#|nz!N5Q+?R=!S67VQ8QX@vC&&Ovdj=v*P4SX7FymLEZC68jR1-4 zo)jcfwnrKhY;VCfPPG_+fAmY&r#k-hoJ)=ZmI4l#xREgLC}5eoW_SA$c#utpBpp^d zVbvCP#V8{`NiQUY&}3>21+_ZNWIVC80EK;3tZIcSAzs&HERbQY<$S1CNcmAe7H_>B zwT(mA^%q}4dZ-;EshiMvOA{Fu-IDpwiqHi>JPC=qz?;Z*P5?!dy_Kk#9~b=*6Jz_r z1QM~$TyF1xZ}brIwOe?n+Ba1|!TaSR%<%Nc574$+p~+Pz$7D!^%Ou$yNwXHl$e9F& zj}D^smLS6UUaY>W6${sIL$vu_7&zID`cp9J#lyCS1=Nl|2k?v449RLx~F?oZr$k81#JABfUOOP zy~hFw*_H4vla~O5k!0i)^C64Qy{C~_QHxVOalG}@ccOmfdi?lL_MvJCjhxAZB$3fG zCaSCvi_0}v^^&cNmB{c%5y^sve5{=kjx#i@q|`(nIWINEaDI`svC_Z!rUW zSXY-Or+~2wBIlN8^~o2GsKg?1WIoZ535-A7NO_e2K$ zokuBc@FN}PxHIIPjWo97Q13!2*~NPD-QaOBRBPS`jW2l?rc(Xp9WomN8t^VqZO2~F z7{c;V-sh5|z*H#UYv1;{>C9kLDZ}0CC{R%fBoo~%&oXVz{E}!$y`#|Y-lVB>#^&T4 z1NWGN_c~Y!iVSCSd{z<{&+S)WMs_DwtztojHD1+Zu5AZKF>o}Cjqh862R{2=GogQF z)5lPM(>pmy)6;=<%4G$P1bkRSP5LH{mt8nf#225@3N?9#DxGJodWT`ljlBt1wQ7Iy ziT6t7H24zh%zc6C?<9kjyXsW)DZa$Hc_;gve!eN%6GdY~=zHw|L(Sih8-M!!+-9f> zqkRJy8;IkFpL+z|uji47`zbA9uXC(Lo21HPtG%wiiUbG{I7rHIab;wWm1gi+8*52M zu4%g4YikXGeuX#4WiyP!RBzrZHNi+u*f6~##ujBtW^jAIv=FHh5&{qdbUy$-B$o5h3KdJW&YX8*D`4V8*ehUDj)$3 ziMPqiX$aWZp2a}t7!pG0ank0<}=7#h|$AQWR=82MAigA)^TF=b6W zcll$sM*u`Bohqty;xEECvX`KOyToxwROd6ZUg>S#LGnuK;Y4su*?8x%X{u zw0SP{%e7KR2TZP&p1y|KsW9%@4C5{s3?jfF^^5?8k{C5*0ENx@?35{&VBIX~ihzRD zH;Tf_4HpSu^x+@P#QM*Vz>Jl0m;hGVN!Zx1Sl40(0UcW*x&jRWm8Y`_QAd;%!~Ek>HCI6oZ!0{FVejv2ix zde>}TbE^5SvR9n~o(%b_SHxAoQJ_>5Fk873M+iJv6^jf?^;^KhPN14F#|>e1pN-5r zzTbys=9*ls%EmHyP0WhEYR^f8&HU(HBcP*+*%-nK2=ITTrV6!7!)R`AMgQp#I(D4G zrjNcAn?JN3y$7DOh7f&mJEo zFj*SG5`^)`6!V#1x94ftoEo6va^I_1uxg0GanItm-}xM>SKNrhFA&50RS;i{y7{K%d}jmf0Bw^zZtOz%LDAXXq=Y9760-aDr; zBejm=pIqzXXKs#wt6OU)F{wEHwUW?mb!|`fru9M#Wk`~a7%7LzTV;3>5djIsG|}$P znnd-Hgv9E`$-EZZ1dEPt5OwdGM2bnz=iRAd2I9Zo>S4vJD2t$)|A7Z ze>s5MXcKPxl`RN{#q9SW+g*=9Yz@WVbw~~~7+a&ff2ek%ipMgvsnCb_vW{{W&R5?6 z-|%*-bzWtI8HqF6%4=3qqBdA(P1JDsh5+(={Ks9}LSpv_Mi)=f`xs3^=wvkl?{YD}p z@FCElPaqgFdw!YmdTw6R{3c*S;7#DJW0xESra}P+Or}Du8Qp6|nyKUf4atViF~k{V zIcFahEkZOL)?<9aQbBV_hb#G$ltsAqk3?pI5HbM3H(jtr|a``SNxIe8X6G=ey9fss@Mt;s2m< z+e2u*ZXM3Hz{$)Z(pta{t6*#FI+VCT0_AUzgfWY$5B6JkDN z%vumU&l~4@+*k6>A^d zh}Ii!#?q}DkQ^Ao4}a+kSakDE+|4zFzK#rnRka94=ut$#f1spJ$3bkf< zuU_bJU{VnO&M?op`nYIOE?^>6nempPj$D!jD?t&Z1TGYwgfQtZ#{bjDn zkW@&(FmVbWILR6biFEBcYbI_%7(O+Mj+eV|Z2Nwk z+%b&7qj|L5w+=l=I&uBG??a4{WMBE#-ynQ?7eetkF-08J6l63qc-zp)5rP~Byo%7k zl6BAJSbute*!rp=Y7@V;W3V<6OrPei!5uusW4QnuX5n$yLc{rlNv56zT`7jTm(;}U zn=?5B^B$R3-|SmtM&Oqk3<(T$RpHD5y8UXm#0h;k*V}wKU?Sk1VeAqw1igL-2|@xv z8TDGVj^1C-*jq-fdM74=bJ<{xt99l&wOsuxNdX5;DoAYyOvZ^_PGBOVJx!jHP*^Y_ zqf$m?)vnGmQZy4NnK7A+&g-`YFV?`-p0g4X)i_I$woP`4JU;mw4_^`rl*u z%_~sVoX6QG{vLJ9dC$wc5n-7zo=Xo?_s>^j88&| zou7LFX+IzX&L&&>vmIp4P()dJotS-eF4>a#c7bgl*$;p22=_VKfU}3lU;{i?*xP8n zwD!`#L_jrbJaDY%T7c@9CelD2ZQQCso;g(I$Ro!ljUT=*8hCuCJV|2VqWf|bm@)+% zFqtyRrvHGLmy?)S`iU0hl9)(@oI4lgRg)(7$SH`TrQ)%YkO}>0__Cdh1uC>C9#`kc zjzE$V&AD1BDvE5upwD6(p4YSb5Em5GJKk?|@)CcDCxm4fo}AB`I#6puBiUR!i+vAw zBAKB_NW&sDtXzaJHwGLzy#OPrr0s@wq;~GbJzx42thl!g&;Rw8(AwrhG(vaQ zUa4*}QvT)tpTd4GT?VEW0ybE*S2irrS=F9XTbNpXPL$bq9D|YZKAHm!DXNN> z1}1u(2H&(NQLsP`YC(K@j|~~MrLIXuqp@zry8*#^Z#-~r0e(@Ha5p&$OhN%qmOaTO zcgj&LF@W0%+VwH{M)5YPPl_%^TBe zH{jTdG%#cs`9>PI`e|es4Dp%NVS3K|UW1SOfI5k!r?7ge4sk2N}x# zBqaf~UkOpFqUf3ftgUi0h5_f&pkUUDPSf>@)3!aJ*KZoH4yMTZxq#)4A@*0n2?PI zS!_WJtwtFzV$?J`&)0;<*2vWYo3b^$z~i$ikGcEMu$sVu|FTJWNq7u0fKXkGHY$vv z!E@DH@4(2YfB^y3f~CY41SC>H$@55U%@L%S52Yuh1@ba#B*4lsRtZDT`_hN$GNiWg zqfq?1N1(()g6`yhwWH7|9?jmw zI!ao?pmLd^Hwe;JA)O~!8==~Z@jK>36G}{6PfaPwrnYO)d!>DHWN(#D(OZk-j&F~7 zFG8WZbAUweT88`V=s9m8P~xh&?B+QI959*Z9eiyzPwE#2hvKy91xT|oMy%nhCR{O2 z#aIx>u__uf&+=HC^~t70b|)UN(LFq4T#CyKOeX0tZ-13>P@?93e3F_)s z5_1WVibm6^=fpNnJZ4_X(=>m!(O_ab{e?Is93MuqC|6;|HKBS(Nzz3D54~r3RPjAy zU!Ut+zB|SqWj43ZG{gOwr4(?$WR`Y}tJd@>kfmxv>KFkIBaSA+u}obPrN9X1T3+)& zi-3w<5lW&WwTGUQ%dr57eoHj1Z@n(l;!-NIEXk7y6*yg?4A#*AkMW=8y4q1tSDk;q z=t2f$GLzcEYoop`y-ZH#G=FD?_>;6z&^f7wxSKL`EO3*i8YH)e*QE-htFfPMMDz^G zM;LvU2Dru>$Vi|Bh?L=MVP33<>OXdv^<7NWmg2audY4U>Ru@YJtV&NRMI}W|%^Mw^ z&CmcIUEsKr1R|Xh_?wVOe;dwTw7**Pq30U=$`6~lek8VI7>HXffT8Y z1YqRVGsYc{6OyKo*+<$4QfRMY_T!;+$CKEM=X7m?Yl;jCUq7_J?A#QX% z^3ml4D70g_Bv_iHoN}Gw81N-oM*`xTjZVzT#4Jfj@Keu4xV=cakN znBJZE{H2Zs+(V&yB$GY#aAOP?MS3Oh%uvl2rsPX1oJ?kz?@hIRci2{4w*l5Bw(C+Q zxbuzzm!^ONCYPp-yE3OKz{%05WN7Hh2~&(g)3%j_h)LKm^E&6nDUng1t{Y}UV?O~F zyOS^(u1m#Hz(_#6TiFK+=Ejir`A7>;wU(gdG`)e2($MyK0)Z7YhuK7cvY7l?Ojn;u zCvkK3FJrtQ+e2WrlE%HXi81ZC44Y?t<&tjP(hb01y?HJet~O&!V8oL=833a8=|1M0 zO3;P7Fdi*<@zjS>gRmNuz>E!@G62Wk$Gmw#`_&kuZ572&m|Gy!GLrD_QSR-;%^#Q2 zdnlt~cx~oMB~bE1;F#k3<3%@C82W665nTyXBGE;>L#5fag~HmpKuOQKT#yF46AS)(fj5}33ZBRBYewQxY+Er-nYwZRk9kC$BPSB;A z?yoj&%3r=1mG==jNmFC*Lgvrab-sEH@(p(bvD>INp{w)o)9}UL;VnqneQ zRWP|MB|37OHr_{8#dlFg1Q;3@;TQwfDSWR1B?Z9C<6ww(7y>rtJjaf}3hhV&RN*K^ zt6LF{()LC`g()1Nii_=5L{%f9E1b7+c+4&4j{(v_dOQ`ROEnDmHkJuu_Zc7Fy`&%a zH6?L2!}3c;Le-CLWxeDAtm8;*A;Ab^;TTY*Q>2mUY76=Wnq-^hlB2*(Dd2#~OeyG| zpC$!LHiKSNzKx|IVW1t}5=)z?ul&~P)qc48EHFaP-o(3KldwyrqJZIC%uGz0j7*F- zK*PwIjmX#DMUsf)_>}4sdsB8yCkb!%FXDYhqM}CSN|_ee+OKl^ngU{FykS z+;%`}n*+TeG}I2_AAf2$KL6nV!MoO;L9#yx0hXC3t8$VGN8P6PNp@MOq*YG|wCf#` zSDT{1YU4hEej^#0dEqVxM*%oh&J_niDH$m9DrI(8th=4miny(Uw}=4JyFXs1myL-E;2%z`rY6SDL|z99t|riOF;+ z_=a~-y|df&SH6~pr>1Aj9cBKbaX?9qq$L@~a|zIMC?siy>UW73mE%SLB01Vq7%LOU zjK)`?Z5&53c$J~3+-xPelcZ&Aq?6CfW6X{-tMXAE)&|OqGmX(g@d5;~swPy?kofhu z02FY2;~0MT&O^9=^=>Sz=|PmvR!gdx-!Q9Bt=P;{VZ6)EhVP2C8zG4ZjI5ni9w|nF zl~Hdl7e|_)PM&L$3|&5&?96NCa=11q;DE_x`dO2V-JI$YlhkFNz4(%58(OQIQ0dA~ zzssEz$3yW2W?#~ZvRp}O8s)D^)G`XKXf4x_9f@Xr!x#dpgN*j%Ibp@swqM5kTOdb_ z!~&Oc)%Y6CS7Lt|%hujXaPl(2$s5E8F?z+UB|$3jeu(ZI3sUiAY4{qxj;|=aaR@{; zUOX_W9dtwC8cIWGhhplb@uRULFu}3Gu@kFVgbdw&6QkYA)I6U`4k9Y42|W7kw<+J87#NeZG^E+$cx!^py_r`{P)Y7z?761XrrvT-B6 z7}VQm-Csw6sZqcibyIulnz-ApU{WcHWo2|`CZw4+T$7!*TAF!om&-vDUjR|+g_SBb z&0nrY-Zf2a)~2I8wX)I?cfqfzWmN-pTB6J5e<<&&ySY=dFIvUgFrCXor#4kSa* z836==4=qqMX5=0#)z8H7D=;C0Q2SnMF(3~XMM@1Cj~v~rkJLDkSTj-v;Lzxrc_+sD z$>+KE3~f-t(MAJofn4WzNq#)cPz*R}-d<1b+-CMXOS_m6>_qr20nqhMt9Q zbPrvQ>EU6FP|G=5R5rOhW3QNKxj}M$Nd#D=0Eo{=psJr_#%Y2G9ulxLQgtyx>4;Tp zgu=D7y%;2*=;D3MtFZ7ez?Sodo>dZ~m(i;vZ`Rf%h79AFaG7GBQ3!WGL3F(@i;j^L z22(sA$>ERR`X=7Kb~lzcbkMMsJ5h7bB)?C<+D7`#=PiKF=P9jFy-jJQ`J?wh!QZ69 zi5OLAkiaCtcPL`^#*vE0GmKS<7J%8nhNd#6Hs$QU(zMg%GW#gtfXQY0#J*Dc>t3$Q zkZIOkGBzgcGXpd@feV~T+-%OXE3LN;`4QD>-mw}n@mS(&JI!0knI>JW7_YS?P*Tm^ zj}IPF7<%B=Emi1?r^z4UaFN=B-T0HqXEI~jW^0gx=EAx%cg zlBPkZq$7%cnx<=TAWSL{bxdlPVpns^LYGE(9DpugTp`@OD*e@4VLjRuPmOI+((*{Y)!zB*$0&Y%j2E z0oE<1mxfRa&uJ56kCKzqhw(MYtc`SvF18`kU#bx+t>P1g`}3QOJ0EL@FV#uoi30>G zwB!tv;g;1~hn3fUK-#d-*p~>9+Wr@yRRmXo4%v?goP=YI=c2gkcyw61I8zlv;42#u zf!{O@LuGdoqARdr(nc+)xF;z}P3n0c_n09dl2L0aeg=N3a`tAHp|1W6{$%Su+`Hx_ ztZhC+bqxVQj&!Eb#YdHm3A}p&fpt#wA4yVT!oE`NOO>SJl3L17+Q|RzD>n^s?S&(QSgr89 zA@5D1=MyF;^$#ahO~~hx#PbD^a_-zUa!?l|(-|1V{rB9Dciy!GAOFZ5_~%Dnz@Pq4 zdaT5WK>lRTGKn^)Zp$CbqKZaRPB#{Lnlsr&1Dh6kbKnWn}LMs4iY&aw}VXPCx z)-vCx;2S$Zl@2!uJjeJZ=2(Jb)m3}gWa-YL%?DLG)=e=)1Te&SH5M#DsWsn(xaPhb zr9TQOACA=(K&b){Rxgk ze*#~)>jm7m`Z#S!UIb&N@foQV$=C}jo5@;hsOisOfEuTr-2~SSc_d1POjutUy9z*w z7E<3RrPGS`DiscQycVD&jciOHyPu9|D9;?7m(j4kU2+tdDh0e*daBR4`yB;lH3h^> zEOXHYYGO3jW5KFxBq0&=l&y)}IITgfadqSsP55>~)%UfXvPm)M1?YR_DTJz?#H}~| z8P@F^*oTYhE2A^VB1P1DZY{t6J zN~Iwn5UO2+EcdO+CtoLrUO~wT4Y=7)>a!~+ukWsWr@e#s=j3PGN=8cQ06*3fk0IG9 zEs-ilDO_Dgsm~~qi9S0tDEZNSRw^gPExRU;B{A)SWI=I2u1Lrs)0dHGivjiuMkr~c0GIm9Q%|WM$2|arj;7iVrfKevqky4+114@D z%sUDQU<3kOjhmKn@}wGvLU=BY8BUw4;Q+eKXMv9V*xzWa2h{8(}1rXqSRq9gLT#12ZsB% zWXL$BjyR83GC$8Q!*-8W)`~L|GU^@)G13_tnue(=NK&1{@b%%^cCkQ8Ca$!h9rGrG z^!l=>hH^~EkQ9DON)&pXhOFl$p#g&2v3QpmPXa5Fh{)ho081(=*;U90Hb~V?Vx)_) zO3tFHc8QU^NWD{4(@OOfQ2EYL*S*^Ka?K18a9+W&Hs|pIokruD`Th@eLQo zt*wRV;b}usG>BDq1cM$$1rkz-C)VNRBaPU&FpGhb*q5-$EJ(mANV}V8jG^aQ5z$9L zo{Ah}5UU!7=*ldY;qj!mP9Lw1<5%2~qd>6~aKNNkO1b;y6a}ngMEl^-jg#VcKUEEO zb?XJe(!^;2$;Hf!ld@L7Tm{=nc1|_Dw3QW)2~p~lc>wZ+}n8yAK!dCzV|{B|LZ7?Rw)%gAdB`oKRr5fM#Z3gT;vw(t-=K$RMI+I;6p2SF%^lMC)kegy6(6*O?K|$yRL;Z{vag= z9V4h(|C@|0LNAn&5#%VvNFDtjppyuKYd?Wp%LX)ce+El#U5i^E+KgY@@rQWg@ZI?S z!9{rNAf*x67}{$hXv+6fNs*?9iKJPgG3if+m!W+Itw70Bt^J11L-q-=HMBK(5}9F2 z6>6?$KMG*Ven@H^n`BS)|FicV0CpVLmFRgnPRud%4ukE!d+uv(TmShD}S(LRa7)Pu+X&Ev;Eoq?JX$Ko+qpD$Y5nUBBJM_Fw3P6CN-?iCL`Zh^@x1Y17(=MQfx#r+y*7b6J5J)R)&D{c ziM_K5#TWsElCd$$hmCM}BgqtVl+Z1fNt3(6@TYhzkCqm#;wunjFG>2|(fLKX$szr0S%~jP}S|N;olCDj@enWUAtWs$}FxyRr@%z;foa;e_Ej> z!7z`=&9(T#pM4(p-&%z_&hm3KW{mvs3&@@MD}=ZI3WG)ig3n{h!0;=TCP}B-ntyT1~tQ6`eLPv5` zDD)5)`^y}KC)Z0L&_GhG4Kvn^Ui~PJhmNs*%Kb+{i4L|!{14Tsq-f)RR~_CGbE}Z( zS@J|_6<|hFhV!bZK3QMHBQbUJmQZdI!&joqs7MQf?vlz#wTaV_9b*7d9O=vB4XY00 z_is6iYnwlVRvJdq1Z(*s38MmB9_BdV?Wbn4Nf;XPV`x}O(@NxOFv%`TKy($9saM_SpPC^UjzK+;4S$4D-C$^#2P$5NNr3@!TLZEH9;>1a&+~Gsw{$O zdI&{kWA&zcSpP7&Bf|s|1-dtw2ZiC^j&c;BG)0PE;ot@U&>F%%HfiOlEJfUuE>(Z) zUeFhT6HyMH%&NPK#-!K zmy4DpwbNeGM1M=ADA&eG;yZ`$!r$$y!?yY&hAi!5qTOeHRj&e&`0@RMV9M^%7V(ko|GyOJfP8?!%?Mn#l`UJvjb|HKG_aHBd zH?|(Bv!6%*A)sdS{fKS-aSVLt2^9JcAvN4XAVDBVGf7Q&gs*3?sR8)eT^>Apau@#n z)E4~h$t;c>45PBqi}o-r73nS#Eg9)ju@1Rp8$1O@N9Q^SyUX?oJgA{xtqdW4fwFG5}!X{EE(RyAoG;50_Ea}uQOJO&LM_3 z7x{gFuQbow*u{$sVqHZLKM_BQwspg}Yts*LUGotHeTrHM(n>-XA(#}6vXi?;uQc5> zLqk5}vWY|rXllw3nAr2nI+A141xTg_B?L3RB#!n~;dh_ifVxWNkKuK&mQgv({(~G` z0&_~kFMt@KUMia2zTm9>U?PX9^AiW58l?j z701r6$3Gmb#J7(!RG8pkbsd#(sFo?+8)*4>m33Up=o#`!M$U2e9yh7p#s3iTHB5r4 zI3QZHh8s{|zL669I7kJn)b9Z;p2wUdCM&BfXyS|TrHGdSdXkD!USpnSj)Xu7XZ)Ta zrpISuxrPMpXgZJe)e)?U4WceWOF=M$cqGMWqa$c%w$@r24TN`|NaKNc(w|gYjPMS z>Gd*)PF&%nTPXw9s8C^|67p>LZUDUwuz+5i92EmzjO9_le{Nf0PT>AokrHr$$rRl_ z_L%9D9@eE))aJ1(n6H-077^xYt7NvGr<0Z#M(GTSI%g@!oF#chJ8?Ih7^=FL>C~Cw zl)RJZU4M&Q_xDkE;%`Vm;kqyB5YG2&!M6Zv$eRdtl2YU7n?j8>x~5JpQQ(AInaZFOhS zUYWq^$~Yncf+_EDx>vGXJ$H~qQ{ogFOq2vo)(vx?@sEH=hSxxQ2k${{Fo2j(9`Si0wv;&6zM)Al&AOvN+bbYrwd4oAlyH5DghUmOd(GD=;?2HB${I1#rBg1kog8C z4k$T9Yd3oN;vhcf#knl!U5v>J-o|EO5uQzVbT3;XbVLw>FdL!8M*RG))Dsm&tPXM2 zR5Q@4byUJ;5Y}h6NCL_QqGDVg6tALCe16LDR0>~DJ%nA?-i!3f|HG8=)cK7!qu8<; z;Zt9RZ{UzpmylPV(l$$HiTHr^2pK^fsQCZ2bS4NvKx;yTmMP^mpgPH(osj&fCHbzht?mMxaIB*!ueg`qDVB@`%mZH0VP3#k}vPS4ZrmGMr^GmarP>K65A#Uz!WxG!r)819_#iL;nn_bT|8sg zUp~lvb0sh<33%4L<|FQ1HHio{yA#Q4z7_l=&AIZ;96Yqb>3o$S$0~u64{+VJAKjqz z+ICEv)AMUU)CgaG*CJv09x4~Q4Lr1um$G!>#7TPW?}wMxl46RXpqx!3$wlNU;`qin zFCOJ<4fSA-_1H*>t1;)C)2p7R%BTWK7>&eROxSHY*+Vf~UQU3Pg$yHC+C2mK^`Gm- zuBwx$58R6WnjO1E17&O zlz=~&LuE9JXy62@!^hCT+rNsqMWj-vs6I7I?@pl|fE#$NY^+x7<=w%T4l;Z48q6=w z?w7plTD5CGT{a`bUWN~o%u6@Rc$dv32Tfyx&MHfZ14`ca)$6gP!iS{Brg4_PX;gK| zE+Vfw>)KrMqb^4O)rWF@@qE_hsYj2pGQ}oTE6@TK2q>CKvT}vNnQMgBsMG>PS8{nwx%>3r6iA%j6EkdWPr8bWT z-2@Dc6?n@t0;MW2i08UIcy(h}bv72Id60 zJ$VxEzF`#&WK9V9Pw{mQF}hePc}vLBp?YLIv+v3YLe~z#vgn*vak5bY5iG`CH3~kg z<;HAhn)m4xox^}Ho>vLcdV7N$FN!3!lJmm6BD&=+242&t*V#u_CKE3Dte6=hH4t3{MOj8@2`Qy`GG7&=HpnX(A&(#!L2e#UiDn#q37NmAIy^ zh)*9`jR*GKgq^pB^*v7h(-0P&-SJsUJ zc_V4Mg7mCfV53@SaUtl?S~8Y+%T04!5fG5W_Kbf(QXfnwFB0K1Pz#g2I`-zlk$y=9%zD zm{=>xh>;;Kz(%Fzt*m7LC$$MRpj97JT*M0dNlM+XP+Uf^Xt6x2*`^fDml(JO%8n)=VBlr*iVTL!}XGFqA zL7LXi%b#K9BI)Z3(rr`VwUFq`^BZdo&wg?Hr`sU8ZphxIWiO2r17ZCA^AF+=o@v2W zad_EB%J@1T#hH*@9(cHbWIp<1rJX~gyjxreEFB4G_QoVy9=RBl1ypIxll^=rot1ej ziU}&!#2|_TUGNaRXkP#miArOKUlPIlDQ*`A`+r?xhWrbg@f z5h$5yk=sUQy#Pr$US9@J9=iem;a?xYAM9zz79~()<+J6KTuIxErO~)44xVA~5uM@tOjVLi{ zqm-ipi&deN7BMn_Iv)qw5<^Yhf%cNlH70xJ;1Fz6n6F9gPf!tLrc-eV#61be8qB&e z70gfa#YYQNq#Ll~*M0FT2OgydMcMN}rTo|qpN!Jqni#dcYD&P**N zMOvV^>J7OvT;7mC?pS{VKJ&tz_=CM|#MEAFB@budsz%>TN01l7c-<#1H)}d_bN894 z1YBS;Q^~od#?qm^9Iz90U&HolV_OMXeZ zvJKx((I(CrDV;xVaU>wq#i(YL5-O3Z!~7)7j+#z(Gw%k$9ajL>D!v7-G%LW8O&74? z4nO|+uSu3|JBaIV_2Hh>_fhORhFoA6L8fm{dFoN0?Z%d#FA{{vI9o)HYpc`@zii2| za;WiiI=x;o5@AE3G|qPiapp`2jkH7rnD;{@($VHQZC(WLrrm7EOxk8FMhuUR_%n1y zCD1w8hVLHTieKHmj!Oqcur6jpsjpU^lFNy2415Trj#m?8?l1$dU8Va%z;d}d-QQga zOjiQq@3`q685;PMhED;vJes7yK73zYCBYlsN1UgoNhgI}&3Pn2Pcw5AkDfK>+0 z0@I<3kKk)u&-_{$Y~&gvQoL0hY?+YL7N?OHe=q+${!OtPN}1v8I{vq%_gOPvt{cQ8 zWkmuH`M7LIC{vaxAsLH9hSm09NR|_>g~NsOEGf1c>tEA_ul&^r!o6*%+k7*w$?e8> zH~kLc$s^c){L>hyxDI{QccXgfIRv?uN{}eSL!QV)Mb;57T^X%hRky$2Z(KXg%^5_= zM;RaPJJGR=eVZIkVh9`86J1eq*_7yxdZniUdr$Ae&+lnPshg_=*Dw)XoB)pZWU!mEFDMU_~B|EuaIJ$BT#On zvu?a5_liB0ZJi6tnW98)dhzR8u1}J-AMI~T_OI9(T9(?H=b=Z6U&G^(!wYaYHS^ho&6P1~1R(d~s2NyT&_dObOl zww^&{c>dgGeE-!QTxq!$FP@BI9lenOx`s@8`9*b|<_dTb;~!*a+fwTdx4lb60&aq} zrP5PNpq())?fpzE@>^ShU%HvO9Y_Wrxf|1!2AIUHx8TF?AIe}Yv&m(b&G>8uA@Cs}A{UNRv@J>(Se}3-%LpBRkPGM$ zpoAjz2Auc^W{N}%VWQCUz1=up5yoF`dkVMQT8XD_e-B=_`~O9K-;>COD%b}}f)R2m zn6{lAgg_>Z93yu~7UG|=bL!l3lPHU7HtQicQRGVz;&dz}a3ZM&gll<^_49V^Ph5jN zXKL}KS61O0=b36hegx?4sj4ZB)vmghpY=%R5U4*equ1q^FIdM&`NAEDY((jI^(!fp52>S zzBHSPwmG}$5WSR+baWdxC^4J12MK0qSuvmcnUqbShi)NRUyx+jY;6P1^pw!{S4CXk z2Ar3nVc8Vx)=}nH>(;RsCVoLMQfqRXC?$C)Tx%kCLR>UYzsap*0)PoP@`*Fdwp)Rt zM^m`#{vuxUj%)Bp>K_p&h^!pg$LJhdV_0p0p~XI~7JPj5htPKBOSEJiAz;xjPN$3s zRn!!vcpn~SdUp~*oo*f5V{v|x>&DNBq25#w&zyKI9(%PGpM9D9CQlH4h8H&z7%L&6 zjC78nZoaQ%I>5xb_Uyee-|chHw{j)mx^h---_QA#OSNP;&~Buqqk@T8FI6*BpHl0T z$U~Fj@n)tuWd#%b=x8nHU8hrpMjh%ittJXn)%MZC-@Wpgv(QRX7+}s5CTRl~XY&vt zl@dvkX`|#gq!H{s#|7eQ*f=HgvWG06zmyAQRU*;VCe)(T2o|(9N^1Cw;g(2_BEu>x zY#>z0-+jb%y#TyGL%+syBFl=73lfcPUhl!P&v5a&zdelHhj(Dxo7dp&z5j;u^*4~{ z=|kiBJ<9$L)6o#{002M$NklI-rkw?ri1t8Tyhqf4Z%ZZnsF1O!er z-ljP4iidF}FS$liJh+#Yyl*|w6t}p-F@((Q^3@`zaBk197rZzn*Y{G2E}txyqpzr? z`-W@paHCfVHDt1(MMZFiHUTCWtHdW>02hE;3W~+K?#sUml!#*zpsPlq!ohGgubELe z1S)=pVC#1iLQFYm7G0uwKc5{YC?P2}TxqNfOuE7_#o2Nq~vd@}&j@b=N7cNo9zjDw@MTo@z&sfFySb zFm|qR{@=&a%ujG`30x0`TU-e&fCOBN$pQ%7J>?>i0B3_mx@eRW#T#V;wxJ%Tx=%7H zf>sd`95^_8xjM1Qo1$cGBP7>~SR06$-xkSL%tc zab z{POLM=(sh88|fiiQ$2**frH2r$S9+!A=UD)psmu$^F(uUZ3|V2R!&amJ=oR0AMfnA z7N0rMh*exm$8eh#s!`KDuM0}1D=Bxss~`avm|O*+x>YU}3D8tc#Y#=e1`~uZ!ly*# zt1PleaV9U<2CXRGiaHbsNC;J^GZ(%6edfX^5Ys=oYbX>0P9%dIEhFJrqv4r&xqxD7 z_^h#-c)28ipCC<~lzfg>B_(C^GVQ$bo={pD5tIoqyaXX@v!~#XhwzEr4CnrfVch+` z`*Cm60Iua)Jl8evXieu4*CR0L281TCRx9cAO?!gc*d z0$y5H3dtUFCkkA;sA&|e1cNo)Pdt?@SNJ7K)*Ht)H4I6B*2Ri?4rUS4czd;#yjb~8SGunp_!+DU6^ zgvDOUKvjL{x^)(NXDzSxxxi$3^{0C^S1JKoKx8GL*0gRK=I1C9q?E`j@l`j%r_8Z^ zB!7~Bfl!QjQ+R^B_P^2-E%yCsF`~soa?n!uBDVU1yk=giEFy`44&rl)fvp&?+htbGI`ErE-9vg_G z*uRnCTqG88zuRj1aZB|++|=yBriN~`R6NB6{A91XFr;{~OH}O>Hz{8Xp^Ds^cWgh7 zPrpE`3FGQT`K4$VabbQN6V|PE5{5FGXtB3=5ew68+7(K`rCeX3z}(%hz63O2oBSt= z+zVi(9K) zaVIIFM!b>$R~Igl(CVe-C=zcm))BdHf{{9ck2(a!HxY=Vbwn!gFeI< zX|%Wt!3ceDUT?^}FS{FlaSMONx6ZYgtoU1C@d&^fs)d%7oUO=dWr`MjtB|gwolZ?t z97D~dl=dV}nDF2ttpHkI2fhlF>NfDC1SWK06bpPrN=$uES5CNcHIu4w=AI@GrRwS^ zu_;=7)qj#@M0reP*(fj>U54%%aZHp@O^T^hq{WE8350~1$7D1S8%2>$bTJWIy9pzf z3rNIS6@eI&7lraRQ9ZPnl$erTKuDxn6@i63fA@GoVw5mcynq`T>+wuD!8Kz}VVI}S z(w!3tG1(i*qXLClEo>*SIL%_gj0YjEy`u^k8O}9;Ea}$IWstSq$NV-K=1fuA__-{) zQ}VPLiAQtS`os9MS87e8J*ted=GW!+ z$78~U6>%f?i?MD9C@^P<_#|ePVFC>iT}6g9925dOC16u#SaDATI1CVx>ErNP%{MerE1EbA5MA@i8)A00;`bq;GH=W*Za1BlZK)14@wKbOXxYk}K0 z^rOCl3kfpfWjc3<`kAR`OgY^b!ojl@c=nXCu__4~ug{;ymIe=QTc5=0x+MFWwO)k| z79lv9FW~3D_yK&as{*Tobn{$=^VloB{NKwbSi5{3?jEiLu7(6$i^ zd04-NcI5Yv7^>eSU_S}Rx!z|`NqllRwHY70r4~PT>l6HC1jFexPWFcJ-0>>>(myxh z!}pC~N4p}YbRFZbVeD%3Nlg60G3ZCep<*qlaXG>u50fI((w?HY*S zLtm`MPv0EHy<2-|J-M)+x%j4dt_m8G-hab>eC><3!5?BSnB{OY&4qB>I$Q~ilYk3M z#tFmyxOgNm8{IFRA|)`>Qr6j*96s6hOLkoB8>p>~<$?;msC*hL=c6tTW(gw`$7giEAuKnXo>$GRi8j@Lu4XE&uq?%0+(8cAL4NCl- zD>XA*SlgB1)yJfCBFh>+3Q5Aj5Q)Qd#b{AKxxUhdpHeXU=(qxNn%D&Z>DuA`0zoPQ zCn8xEGv~>0tw6YX2iG8?Tcsj{_gwQAh;iXWa8+JeMd$_Lv0U`7Du;VECQuP!w2#(3 znxHtAlGFV`{MWBE;6wNJXB=aUv;iX1L9AKLU)xkzY%k6GhbsP!z~9QM8S=TOLV%8Mcpr0uN4yn7>D!Prj=O}Y7?Z;{w zTk=IlOtC(J1Z$kJ#8rU^5n*NVn5gIoKS#H3AE?5Q?Hs{t)+LbQO2q;f)cgwooR zn-UB$WSi|%zLM)&_F7^nbDdmVq)#tLP!-U~(9rV0wS#!m_8|pQ@p^c!Da;^K{B&^> zX8hfzsgI(gx)(pYtruqoN(>#Ik2BA>$?k!!1m;}=E-;yQk-H~!B{2UIa5y9)v5H5c z?*bMETo^#245f0tgbetQ6~#lVi8v+N$M8?YGvU4&x^qN~Rdi0yWQVm2{4s!+$B_Ig z@WXclHkgP_$0%u=qH}bNb@?#eys?`{F!~>`;P{os9%avn(=xG*)KGZBY`v z3(KhC=)%qO^etsLF0Wbp47N6BaE2Tj27=9D(bbM!v5Ax-fE*(_kNq}>Cv~fGB`{|a zaDmC3iPEiTu}DA)(k&NCKwOSI!Gx?KBCSes$@*c^%X^rml^hclxo)BrZHC}VO=hen zVWycEuS7t@5LQ`_cp(BS;*H2HBSNdGlkOXFR!mM5b7zs?+Y0Z+Kt?t4kMegYhl!u*>7@{jrG(ZPv*@2e z4Fo9suJG{Av=6WE=rknNVu@L1S&O(L;$@gN+P7){9aF|u1*@)+vq=lzNWcXqi=rFcModovn)zKm#L-C}HoZDm+Fn*c$>Jd^g(1RH zLCIC)STtnah*z&M?j40*7v%b(W*47Cvuz7L(#G;3S?97t#&x4Qm6JpZ_o+S|D}}03 za$G$GEEc3VXh1ntf(UZGqVAj@Z*1s6MfhWeH`|`5+DixSiS}Bl+r%xfib)cX%I0df%sXN%83Yaj75bb`_L^uNWptH$N6FFT zqR}zpkfa%zBcklH<%?s%I|RGSl;4e4H+W+a|iHC)%dw zFQVdReqBVJ;WlM1Bp^jA#Vgac9#*$V0cPMH}W?HMK7z!cl1`lg1N=s`-l-VWy4GGq(wHJ_3sf8Y&d~JHC#~ ze^w5ZWY&uTX4wb5LRor^+L+z-`&|SpT+MiOUBVTX<3cN;a}D+xC^EaQv#6r>HOF4505565V%T6^h_h>n ztgvM!^eif}?zL%j+!W_fA5H`;r#NKlpEMcJb3$vSy(8>jSxT+jPlsylxF4t?Z`DVgR1t8n1AZiF`4@f{KX%M0^li=u#j65g_r? z+M(_GEZ0Gi@>9+clSZDr5fNo06|Fo>ma7QgC1@6oYx>QA56NAj!p7BLI>Kz1GA!?H zk178Z0oTug@e{E7i8anltRvWW)(2lhi7QQ0dFWt{Ry`w1pFeu$3+kHm z3y+oSraD?gi>NL`Y+@Dhnd*K=*#GuYL!1}_hX(GVz722v&$ z0}iKsY&iQ;%EbZ`XS;nbXq9LxCF}OI@9LzyxIc8`7|RO#Xek$6x5(L4B_#oC37P7_;ec)w zzjxr1}jQ3Da^SP@*+*inTSksv11jqauyKhbtwmp zAvxP)EqIZoOBO7vc=UuUpE9#+xj+V2azkArxLF-su5D)5a7k0RL7=2)T&Pafe2&WB zbuS34=y!Ade3+E|*@6)rtZSKz>nWdmhHow~nW5m^!{$o@b~Yi-f>mz(t6BM!z=Mw* zXaDvgo3C~**7I1?y(}P+SR;W(tCfwJQA>`RQ7e&Jma{f;LiD0S6hM*u#2N8&U7RqF z4Ki7pS#_BEYVcA-Pio{G!Aiez1F4S$H1wMQiutbd&~>Eq>s)dd>1B)Ur$KU9*7hF9 zO}kFvGk1O(-AzA<5ZBg;M3}~&V`L;yG8`0yifFB7x_ph{S2%V$nPA_tvj^91Dd2|( zJ=lN5i-*_evArc}Tss0h3sF2=3rGO$b({YkUb>f3{48K$WqEOn#y3(zkmW>RjeTjJ z3mvO);j#TR8#lZX_N6wD@287vcfT(K39$dY4ByunsmX}3knw@hvt8ii!G!DcM(_to zROf!0plA%xb8ms~!pmT3+w&G)er^k{x&-91(F{-nt)q>YPCY+tNS9J}6BRp9PyE|N z`!C;ZlAp}p%+kt~@`VCd6XqZkiM4XT@*7~GYY%V{zFbDcR$H_vUt+k^SVxpZO;!{s zI9X1VKuvWx3D)e)i@l>rpRz)fVzpqz)~7ZItVoF(HxTzzdinw*dQJ|H;8!;sL?biH zJ{{{Y5jj!fqI)+M@MmA*5LNBP-lHCT^qoVfjxmkCQQS^Tr~j_-+}7$mKK0J?_~$*f zj06JSbZsvxX-S!Eh2rVgKJz^V9c}3FaXir4gKu^=qKWPtZM(QgzLKGm<=GXQ2`Z^@ zwB6qA0)GPW1eGRSTQxY(fn1EGOb7Q1>HD(yL}u z3KuRFYswKh)@@VDi`+^Ruo=wtM}46Tt`6^^Xh4i{A8;orOU>GyFy%Z+w@GG$t@Y7* z!bq97JUob+^lrTG>yI%V8_B5wKW6Dv*3=uzxCPCoYu-U zc4lY0wpbvl{&19w15k90%FwI+wf!~zxUt&YPs>CCG%c2Biqj-Q_d+p8AQIwo5dXky1ttsM2W|_dl7I$N`a?769&#P(2u`AAZ_8ev>@DJ@ zR0wa|c^>UGyWuNVqu{SW!E=_tsG6$>pTj$D>c;E0dr(OiM}+!xww4Vk=d1xD9F*Ba zLa7A@CA#a>Cvs73YB-Gbt2SXISwoHuEmX#3DJzHoq|G}cc?+bgUj+EnPnILVJ~p#s zaZT(!ZvmH9hZdiRCVqlZuUA}?$(|klAERoQ|1{o?pq=d zKlvqc?+BFWzS2FVZ2~F_S>$APiz|V}D*-+{8cYMhN;9Bp059Z~CqpF5DGDDlJ!o!` z(xdsFp}SIGQdpWXTcWEHZBgmbN+J@gW>tn8LU)Zd2P^rR7WE4;oVdR%Pwmfr`A>tF zlC9}D{qr+YM|ozX^c2mbeiO-7eWn~Iaz7-7PMOeT^-Em+lPck%ZT+a{VX49Z_dUiI z#_{T^w-C%wm2#h^3Yws0DVZb7hE>)AB#Q%-NERMXfq*H7+E@=hyt5Z4&l8OK1nz7p zvJV{#h}|N9DeIJ$77)47y2;d+y6qyh_kalRB|YDA z_33_4_&&EQw@^OI@srQ3ep7^wZU=Ke3E1-ALKk=BUg#ch&wp77XrK)Q$ZL@QlL|DG z;f$N1d6z5Js&cu#mjCaPp9LIJS{#A+Kbsyh+k&BL6MdtMtqv(p8KKqRRjv4US4V|5D0}yVfMJ zqvZwT-+p%6M{scC#}K4-Y;EV~Ogp7a<$e+0!UBrr+g~MQ=u!jL(`teS5^|Xt)-%)LvphHrv5O$a|fR-VeGz zR1C?LyfP18fiPVgsz)5fY|4_UCk_6}aK)4L~jB`{qH*a2B4>`0^q z!BFkx4-dIVbjI^_6!E#?ZMc>tj*3D-5C3Zh&5BGg}Nx&{6!8|3jgn0R!dkI7|=`i<) zCK)CYNB0B6@Po8+@+H@gdCJVgw(w7z%P}$CaguU}!l8hm8V%`(ha)({a<6?9FzZ%tN2QmQ=DnnUQ%)qA*V^h>`MdWFf5qCva56#k?e$cQtqH(YexNrS_BtpAL zcFp0&SN@QuX968%rA5-zw9@_*2_Uq7&aTz30tf2IF`)@ww=e}ESxc^(;ab2bWg}vv z7Wh+N*pN?wM9U{pe^?$$sU%*Ch>)_pxWHsN-&g8iMcMF4uF@m*V0 z$s@#Pu+|YBuRf6*D3jK5n)sz8T`uL2qZ zE7pCJGy|0p6KCEcv?`syLVB$mz>+2f3e5ng8M#r2R>8@&V00(-r`$TJk+X)3X$HrO zWG@x#7r|s77wh|lcjB?v{Uf~D6WDO_^ElS}dh}ObM{ok{-|(L~$EDa$Uk+b+ zmm?l3(VBFe{YKD7D~kF}K+Q+@nFWI4_t2_kULrLsJljcXXI+@;t2{xX`c2?Q-%X#4 z1BuH)UhXwqC;@e^c}}%iU-+2HT4J77em5cg1SiIN!e>_#4UG+&gcz63BDg|yRWwVUbpaL~d7iR#9$jM~~iSVO*X<<1jugQuCU>dYnXwe{~Su}y1EFxwh zH_9+Me3lFA4I9}hS|${+Dd#0)xoZSsoHcXwyK(7Au@R7%$u_~D$K=A0Tm=>b1SI{e z_q0h_Z~Dp(gO>7vaqFo5 zl~&}IQGS)TZaK>&D+H3XqF5<#W7z^IpfU$-Js=)u)kAUB`aN42l-vZTn8svIV=rdZ^%C`9E>Cl^IGxQ&=d z0(!s#feQ0r*;yZfhN3|D(3yvg2g$$Ar;Ohx!r0u8Gj3zfLGFEKn!8YCzMBor9NDni;=Ljgq_8+8Hpb6dQ$SM>q59 zC4YEeOsC~OZ@#mDTRQ(7*^_^cf=Z6udM@-`ePw})B&oRVZgKyP zN?@5`H31D3>&j!F6NCtqn8^j7aXr(z7yO|xpWi{_&XFaB(+{)Hs9YB4!$hO`0yV`w ztt){`C13%F2f5r3IT1<d;FmY90nW@tpdki>lNf_OprJ(q~?r^UP}Ek>%19 zx)1bwCN;!Khw_?m@-lNFTzoOJ{_a{#W-UUuf~zk9CGR3()6DYi1FXR~pHK3!Q*;T# zR?8NEOWVRd2xeA_y+m#o!zCe*P^1l*??vtyfjP?=(ONa-2q*fK)3l~I%1Yc4fe-Od za!gsDQ+C!^lNoo-`Zg53h_c$C6^63}BvOEm_3BdDU$`Z}fiFuh!^5#x&>TOAv(bCd zG~5N>U^jx~3}uNQ&(>^2ZDJ6WBd?mY^gQCiN>J} zn$Uh(Mb-9E;apJZynb zn^qZivbze<$lNP}t0HwJ_YQ+r$&mzUpWnWP|n5k-AzQ1`rlyg3U)io(Ru%;hR9*a?-l~lIzESoN`_g<&e>LC11;=dJKDiOxDvw4(iFitQT)z zbqM+E--tbRTkznae?=tqIEk-Z7bl%S{oo6RV^W|l9pr+7mB}nEDU2#vB!lIA-#f|# zD^k(%_{pb!5j*#N47<1f5st3@O@vZ1 zJ97a)!v_MY5@+F8%Jt>kWs?c4yxmw$D6{KZ4&kl!LpYNrc$6Fs01>fO^GdCuV|gc* z^vWm;%1tlJ(UC<_?lyO*FN@WETLKdBRkz>$(IwDMw}~rG0@gZWtsNRvrJ?MMP<#{R zZ_qaVZf5#autvM`??lDSBsoBjnlLBYX-RLhp}s+8O&xFX@RMKS4>EZgEhokmLq#T{ zTd5qi;b&@j$ws6&zggtVDm+n+!9<7Rcu@#V9x_$LSY=lEbACe$oFTs{g|q z;NdFKn_vARdKzy;U7{Q9=bpf;Z9CbU0fr2pAs8WV$fTyfvhz&8MN4TqNc+ zUpv%-nh^VsVv!|Gz(jpy5@qqT+<3BV38)y*LOBZ9tB)P`&r-T_x2-Ez0_72^$-1JD%H`?Kk-uH}2$Wu0Gi8Tw z5Q^3cz9B2?2f>H9BvNY1&MGpjEF*y+b6W`5aUXG0wBTQk%zXj9TzZgy%l>F1*eeGM zCZXCh39S9ZHMn-*8wk+ya<=(ejA$V-t`8GUyo7M=X7WtvjxZJyT6m~nb^euJ6!5O{ z+BN5BcDfb;M%OSCg}RD`L|Fb4Akk6{9*@mYBA1?CdOmct7VvX`y^eB=D}lu>0oT5_ z*n4P2x6djjnWgh2YmOCbIU!P^%c9o|%GPu}l_I0tqRg2Xt7rLql9j}ymS!lhQCPGj zDY7Ot)H%91pR6CIP-)516ef=&m+3bg6oDEG1SmrS7rBDOO`(e8cB>ri!B_S-=w*m- z#)miW8p4K}PSg%Qg;HP_p4#{@ntPr=eg8L*3D;uPna`61`@OP3OC-Xn`tr)!u(bD< ztHy`w=n?$PmQy&A^drnsTe!`FZ1ttCkjV%6oD@k;wqfFalO@(q;I6)Q<+-%)f|b^K z7nrQH9$#|T%TQs?_yYclaV_Ua%)wR`5}C4P2?++P`TNwjvvmtC{hTHyJdxmGtjMKB zola)G5}_GBfxri?AhINxEUpZxj>MYj=1CX@rUXnf@yped%aXZoTr{+-&_bex_ezGB zFnfb)R1F&`;{KaPa0_0<>FS?EEOiiX*#CK)X?`z`b$k$^bRVrH9_BK+10&UMhqoYW z&Fmu_#^kefKhZYGY_5L9X$*OAV-GM~L`)%01}qAU*bK0`Lb-8NtV>4`Q@k(ue3+ZV z{Nny`C9pIkF!e~jG_JvI<#LyR2Hf(Xs{vVNYs-0{>wz;u4aC-+qjf>1nrYMQz2vPX z%ITu$8JjFi;3E{NHVQ~&S7mRNTSp1elxjX0uC@S#s|HJ#DRsPcfhpgK&2tfnH}fYC zEi2swF$5*X-6bVAl`JoR(Vy{PGa5s8Yes2O?{ zy><5yoRo0Bb~{eA-Uh$YDR`?ce}WaWhqzHdd+k2lv~dJyXn_hSJ1qNKeP#G2WuUH* zdb*Q5dhTUu)I~ONc)raH6naWqH*S=-NL+ONgHUMHycm zWu_XTN6F#>q21eaRD{hfWbhUFGYZtRJsHgv(@CW~hOmx5CDzBg|>i zzznc{TF$g+ACn8}9$|mlJ4Js9Q3%?tb-uTS|=1DM8#4W_vYd z>OL&*v|`@8lutVE!;S4P;2mw(;j<@VSR3~em{=>C^3hljtB5s4v?&9uo)fpY5?Fc? zaDmCv>$4TxVt}+vr0f`hEFnJzqwoE(Eocfq~{*~7#&>@1V ziI$P(<}1y7A_6Dk4G3@qLKTKnA``yJ{z+h_Mcf2PjB;XjSAi94Rbf~)0Zlr2-T)bg zn=l4S=Jpz6GRV;I@$lP^qifUqP}%z;g1jz&VSsKMhJpLiv*89*B=#bhFCdho zTW(5=7r_j>dlB~7+zM}!+SI2pVvTFtPT;dgH_1q5Zhz8a3C^`FgA)#Kt!q|NMqI3!vbv_P_|KWG!A(UD^Si+)RSy%{A_Q?j*Cq=v^ShYPI}TAVLAo*;bDs60o) z2+>-yKjp_;cl08%sREr`_a+_Pj`I~U+|c_REhU=C6PS3C2o0YB1D(O7DtuATnkG%$ zF1dz6Jd|sat;ixJDqwcc`cuFUHY3NJHAR6|bDQg)<__v-f>A?yT|gu>I_mvTxQv7JU_Q~+KFn@DC^WHP1s=RH4zi;$oZ7r8| zDF5WX&7TBZU^0KQcF!~`3D{^HMU;rZDMcFyR!(moHeuSZS&exB#R!~mILLKyIQkdc%hyy5FRwA2IgSih zJ1v1xLY@x(cBvYWLM{=p1pGz(;$1_yeihwWm+n@scR9b=`@Ey-6dqjLjmM7Fqcy}? zJ!LUUT9#FcmPPx0;e?c92USJ<_`pLQdOGK=Q~Y!f>U07{L0W4HM6uc?(B}6mVu{v8 z?xODnT0_(_Kgm1VM`K7PBbO*KOW21Kr-$(LzHTP9o44k>Cn`(81t#vTutX%FA%r6a zXM`TZ#l6=GSsIOlPmLcUm9lQpj^T@P#fK2+e57cRTClKuJ=s$=pfs zBw%6qGgS6oj*?*?v$xVhqDUL99vY4{5QxySLY|3SIs$G=t|ktN$gPI#YEN?2FIAV4 zEP1gdkj6`=U&5aKHK?yIk*vw~a(kMV97j*dI+FE95t4%7XR{2<1T9q02dNxJh||7w zIEdZ9l*YP-9F>o`BPKXvxo%>wGqry>kik1P58x|%S`q6=qMz&Oh*Kgfn^MvHxMahq zkQYD{ek%Rry*b>nwHAN)vG)>8s|V+y@E*!!<BW zw^bps#)EiM7~#qQqiZS=jz>9YCOL>&Te2B)d0ig*Y|=y3 ztcSC{fe_MtJ|w#f7(Sgt*Q*7F;+{dnhHgaaya+O7Xpy%5f?4@(?8>j^rvl9%rSvf_ zCd{n7WpnvloX@~cCaFziIBd-yj+~38&siN>TPRluqp{*3{?m2a@kh^fU`t&QgH);p zxG%7^L>FovQQoq}^0PYj%+8q@Cap-IKQAQAlFHD(OBb0md6X!a96j?lLTB~GEahbPc!!tFu6F&)f(4E zdC@;JGID?l1SJX*}aNDXU20Oa=qzvZ$`9!0OjGHBr?*YX|~_sY-gz6P<)rPOC27B!MhkX=GWU|~Lr1Aw$%P%526RXl9M zsnr;{Zu0c~4P;~ui6BF_%?MpMvbd!)1bdt;G|;Ux$Y&@?7Y_(x`uI$7H$*3!*UZO7 zt=WRMQ)Jo2$ayUqYB#|+=4Z?tU|o8iWZR|qBSuD=z=M6My93f)qZ`>alkB(>6dMc zFXTllv+-7vQMrF1 zqw`+&G}GEaJdk5U9(?$9Sv>tr68kzms2Zb`OskH`c9LqNJ_fiXQbjmtaLL@lp2^O< z__ljJSQk7$LVrZi_kU%*A)vmu-=NMvxqmP*5&T0uNeA1ULjr z0!-lNrE4@A3G(+Kttm-vV+0eQ0p0(FLcxHUU_goLLWXA|zKLv=^8>3cdwDBr2N#&E zsP0~F7dtixi+5r}Z0VXYGc*n-Byh;Qtt^=Qz|9j4G2^)=iYJ_CpXtfXSR}|DIZvgW zVr0rS57(o4dn-C_T8qZbjpUTbMZ;(y4Tc0FMb6-|Vstv^RFn0OzfZepP!$Vwly{@+ z@gQ0oMXVu?>W(@zZR@}d@4gBB$9r-72S?HQ!bu$dY#%aAGS_~K4L26YWwu30)ffeg zbyZ2c+*pIynE_PN)5c33Edm!Qo@yd6r053*Lrhpq*QvYxCG2QpxU}lL0x?Zk=@Q+j zS$;Mjz_!LAytXZgPyH~0=6D5rQ)*N~TA3wA(yHzW0ek@vhX^ZhGB85-3`wx{)g-&p zA|i`nhVC!9a`eo}ts_9A?*cVGa!w+g&^R8@(fe;@igwaY^2k@m*lbh}SHa4XE%>}LkH>9^1fSOfw}yxIMH0vYl??)cCq zv|qmptvlBsPHBGSZs6&WSqw7`&H37BSE4B#47()NRREG52FybOnw#i(&}Gn%%x zAlZKdu6y_t&b)XEdp>a(Bf}$T+ojcf1(-NAI}J2PPydhMp%Rc)c?R(Ao1*y2HwJLF z%l64=Exg=S?L~tfjcca-gRCuQQd~%|YxF`!4;lMV!XLkH2y5z?xt85I)t+O=EZ0vA zYvrZwT78&|;8qlw^Jc~!w@gGN4vBazPSlM7CD6BT2#hD=;|Fd z03@5&I#XV9Qwj#O2*_l}Pl?kN)WRhdhLc5dPnL<0mdItez+{PZl-tayBp|jueH0vd zyv%%R+&LVeaJjQx;qT+mV=$2bhvtKfmZ%6}<2_rj>ZWzr z{JL#8vHKXFe{?^RJ!!<-808UR6hijOT|5S)(Ie(zUXs@~eGC8kpQ~~7JiqIL70ONI z(v$Gwe?J?c+1yX}19NZ?VDNM}((S=pccyXg1Dy9yARu9-)wMa?ypF3Q&k#6Wp#&H8 z?y{N)oZY$RI5t+_ODhyZWDPQ0)Xl!?jnmT=B^XJhnb(KUao=D9>1@srTLnJiQIf^^ zJwb;aDmBE=_$9J6G%WB zOv4Jzu>=;*XSr{@K_yQsnoO{TQj*|AfW_Hof~vSlDZPUso7=>YLnq$NxJvvH560Z^-bnJnxx{Zy_w?Dz%8ptHc0@$teA~kTT#2N z5t|55Uitcdy!hDzI7e4h^R3K_!W1t>L+qGd!%PmAt^!?1t(0RMZrj+2o3?Zy&~iJn z$N$On6m5XuLPzyHJslzxo2}FFbxSP zUx%zDcGNI#kx@&Bep3cVuSqsmC@^9rb)GIBT0TMp!v}xr4cM`^3Y~+T zF-oPnRDtTkk zVN)xct8ml%Zo^yt^g-PA>)UYt2PE#2M5|mY(=Ay!Sp zH=^*Wf2cUOwkGlMH>7a%gv}VfZDS5^-_--sVps&7F8xP{M_z?Y^%IL#ipfz<({1n4 z-cNC8%w0pU_TYw2X0@Z`Y5HBYv@d;v))&Pw;|-wD;1nZoWF5@V>d{nJ$w>(#a=2X< z67fz%hSlc?6R-)WR540N+cbsbO{+#_{ctMJ~>+>4GI*O@#a1y{LNgOJaWQN&P39lUQbb&R zXxh@uyj=}gd)s%&(lKE1!v?R`N{3 zW};Wj6F>IYSA@Md^z<;^_Tdd!-Cl#1_Ek(?aKKi|>iwLGW`=U;-(SFMe|sl(yk#dt zbgPWzL>7|iM$sq`(K)MH&EfIVhA-5XvP|;K>7e{89ps-p^zml8clP1CA9)UucKX}v z7^OfW;OIqM)K(={tL1t&2YPB~v8sm0KfqAwD7q`RqJHpsv$qCkSv~ldxP%{=Fs-GM zQ4|C#!x{Sg6#`yT;g*Ybh-X9$<``R6FY{F zZSKQ=d!`mUY8WOv`v9w1NlbmL?o}wRion3tEKcoG`m%gDAV4meb3KF1XA&`%lLV8d z$x4!^RU}UYJH&`M@ktaGtZ-pPuYVU| zJ{?86GJ0Q3q49=F-1iT+VEtX23=qlFOsq0pP@>^yh>P#ZPM{LukQ1Z3hvWC6Dvkh& zKz6^2r@*#Gm`;4>J8nk9#%BEBFQ3NYuXLmB_9#~Z($(4IYl6jmt+c#<4FnGuuexlO(TepfhgIMQSXA0=HY zuPz*6I_mu|r0~l>zXLb!TxaC7c|$9H^&_|8Pd@oQtfDV^%R5%#_Mf{8HLL0vwZm{< znv1Vw7O24~#cABFv07{i(|W`5T(S=b(j$m)(7aUrnq3y|4;w7oj!3*J9lO?`W=$P- zZ+IF{{>jT|znKwE#3tg>TwE0P3vxl8KD-XE*~Ec08$fjAB@{_M_A)!E=AQC2WOCG| z1??=r$n&tibZ`?+)ony`?~@4ShPYOc{ay0Y2(Nc-QofWVap8e8VI1hJ#G}sz@sT$U z;Ce1-$1&dQr$bgkp~~Ymd$Fqa8@R81JNEQfqA9>krVHrKktH}~UbKJZ3k{SXMzOD+<*b)3>rTWJ8To?Sn0q#BZ4Ey=xvW^z%CONF+oE(K4Ku&WFhP=lRFu4yuC6}(>F@p?TJNkdE}gkP|5B*W z1tv?Om)tg9MgmeHA-Ql0Qofu;z5m^J zb4FvANf$Bh1Mj*MfATwfaNYmdjB6jc8G(4HOca%d)cK}YKo5J%N`}LkP0IOI7^nt_$~}e4Qf5#_Qv_^FQ8;+O>6f{G&fW#p-}r>A1+n+QuUR zGs9yT^^^M=@UDmIxR_stZm^da5}d)gwg(U&IfF=HukqQN18WPkd@yvABT=+4g2r4o zqW+W-v1Yz9%M1S$seCheFAjDE@$%USPWMD`j^y0i*W~c%d(M-iLb5JHX6=2ja3Y)U zGkZL=4iutTTb;zM%|rOkxeBxsY#YAMd^cv?`E)-Eq;7Au;C9?kbNaC|>cI~PcY|~x zs*Ha$9S(Ox|U*e z7Y%Sto`8=D4*B$Zc%B@su>G{^6bsgMH1jjhu)&MyFc+9CqRw!eG7|~V4M4>&i-=P^ zX3;)<)1ytZKdmGBZ1b3q92adwePskMc9M_qatWXR$|Jb`n$`TBu7UD^bq%wne($CK zh|dKF>|z;Z^Xrui78?381V`=6k8)kSmfxggZ4DvL*j`)NNXrRV31%3xb!M1d7Tq7s zAjLiLF<$rDx82A*E!Ft^Bi}^DW)AQKcUmOSDf+mI1kCUIs`1Is9YnR~an53!u&pV9 zmh-QmI(rl&l}vQiul}MI-~xo5{UIFhi*WGng6G5;9E-N%Tp@;J=Q;GG!{`|??!VaP4gtZ%B`b_ot3uIup@KcWk$7bZho0)pv7=(#(rD4Pr56V zU*&3+u&Lju_BlU|fW?(qM#ne6A^ggHV_vj|Bp9Bo36WlAQ9*LK`Uy^QH1mZBRMG@5 z0^tf{Hmv5c$7+1lE8?>YOjblUFWrmOuog6#OZkBhYL-v#nBP?v5GFCpClF+YSEJ|* z7_nf*0VSGwYwDfje#ZaI)mfka_7S}4k?Zjr|K|<3>(;Gh0bMwV-o1T2no@)KM*mqH zDW$juFvE|>Psz$hP5#E2D}5A|Wch~-IN}U#-0@XDrNFkWp1h95@r%Gok+VMy)T?e@ zk9U9Nt@zqUzR6XRNg%B9kAf3jj}oc*b3VLqwgVr!rI9NP4`L`;fzyl+est?k!S~9) zqix_ge&cO+Dj47D>H3Yk<&~&6GF^IvBBSsoy}f?kpNn%GT_oervKTL z(RT$4tz1x|Yv-(-^0=k_MZ6|{2VPBuP)T=}mF~jcKU3ucnv0eh_lvHy4EX1w&bg?= zwu_S@RwiPtLY8HnQTVd6hL6BYjC>claAa9gsB%0OVJ2pRb|I7thoiY@){?2i1tv?T zt5#k^rNhGnB`+T*OY$^F-U<&k?g$&@t`%0LjASV>Q8yHiO)0N@1X@WnMPelV%Z5ur z9tW)|+V2dPrnzlnEgt>-x8UYoJ5X09%xPR3hZ&5pHE?W+RAE&phJ(pL>`wIII2ZpC zIGN6vA=SXNZs^KqF_>pW02f*tf83c|nkmMNEoVt|t}}ZqaiDFzt^+^v2lwMkzx-8X z8CDr0aL|H)y1*>?4QoRMJlR==hlg*#Pwje){E-IAw+8pbhES;MM1++d~fg^!AVZ>DB^kIOg4!x_je+! z!AcjhlnUjd6_6V@wcw#oJiyhIL&z{8ol^9xsAu|VV&7by$FF>I9lo4?C;Z71=q$G3 zK*cr`BWv+*H~uDp3{+ z`JZeYSH@A598ppittFk=1kPnsTs>F>HJxGVVt@_X^+URZju4D&4_6umvX>T^46OwL z4qBJ1W9l<3%&&%b!W=FQ1SfC(o|%YtzM$czfKwJLH4?{k?(@4_FfpbaoK&8b&r#7T;FnzNH0M$ zWti8!INUkQPT14DAU)R#KQ=Qgei7G1;mTP?_K8QLq;ATilFczeBY&4gB}V|`@Z2uA zji-6e<+9t@NWNUI$i0pwF9BIY&JE}Aw%4x4``&f80VPU;BpC3vH}Arq{o1W~<>^6G zUXp{`QAnJjOO+M>pP8oRH|9-@0@MysEI$Rt}ww#nqcOsksdrb+(R=vvq>&d zP~u#r15TOYh#3K<9k%vtnL}QvS%9=a)0U=$meK+b-lOe%v=mBP`Y3%Z{gu)Z zAcX)SKms9&v)Ck#x5WD@%eEwI-$t{KX7s-AUX3j~lC52`HOC&!ox7anf6hJUJOA~G zj#Jo0Fd~<1#WuU)dt|iBobnX+#Y+y3K1yb3#^ELVM<66lHgw6d3f%McThXs?MD6QK+US%ExNTk+J?bWAb?ltsDK&Lh1U%#~MX9Hk zO5y@21SV2|&!%%V&1nI8qjk_$USb}B6s0~n<59Z2hpgGgVg9B?Il~YpeR}#RV2Eej z3|ZdQYsgUAg#HR~r`l8}VMx+%p^uJt?!^pLWz^3%Mn^8?Vy#?Xjhp{rJzn_KRunNhabJXNX+G0Uk=p_)!*)mQ_?=(98}I4y z8BH#OEn0^r z-!0f$xfoTuoQV1RaU$p<7_s1ZH(iO-aobV98(m>~0VTHvwD{Tbu`RIXHjzy(-i#@N z8>T6j_-VZtKwj1!*o)>P5chIto=7XfPE)UH4iY$#SXplHr8^R#pOP3sw1!ZOi-Q_zBH}nyTdgw!7Hsca)JJTsmftf`CS=`Joa^maX*@q9l?BbXJZAZouhA5YFFC%G(PLo{el5pucKlKZ+5RB*^aZnQdew;cKz>@oy;g*kH zgEx|Xl=rmZT3?X?fHa|Or?};nUi7tuzwuOH97>BY}}y&l)B31H*07h&yL4r?*>eWFhG>0BfuS?HpO zW=q>#*yi^Y+lmmWz|Un~QO-hawz~ zqxOnUy8dn(=p+e*F5i^F1uoX-L1oSfe5|Yw-#BE!QZJo*&4Qtu+AkQ2qlV~idiq(j zkLsd*kwh}Y>r`ls8=Mk-D}Il}IszXOSt}lotZ^jn(Qmn@cgowKrmX2{!i*?24NPW4 zH>4|ZJ_TesS6pPlZI3;Pzj^FkC@9FKeSZkgKK~Ye`t#FRy26DZ%~Lf2m`UJ7!r?@S{yI- zwjz%rmy|=aR4L>AR&~uPkIdwFwB>0HlFH*$uSEiL@^d05Y*#&cE!vK@qw`n~>>l-j zSsQVz^eOlDBg@x^gQq-r{RLLIcs@hq@;h8g_KLpQPx9p1LO_w50e58?h-a{fx+CbP zWzM=JVl=(BMk2;8G{FT~RuA;{#^2nT@?KH5Hg3a5=5bMQ)^o8C)p_AEPTFHDc1|v3 zj!TPAkmckf6=Xgy-JA(-xG{+NXVe)FuE_ZN#C-p$uQebB(ZWC7>Fk_DrXA}gIIMHx z1ZR;`qVE88r{u#V$CqV9IMz7<6ggURItHBe7&M;l)=W?Nn#t2CO@SFl0WDx&E^;n{ zgg^XUu3lxFaN4>3sIp=iL5W{tf_G?04W9G{nnW&%T+$oShNbaNPo?eKoOtlN*C2mh zA^K>Z=cK5nk*27}=^j4MTY#hCUWy;+SA)+=d^1r=)nyB4;X~FgzXcyAZtl^tBx{%q zLnM_7<~DJ)zc>{P)-`v0o_x8lzaHPe=V!=U!!}D#bx@Ksux@ftvZ;GgcqNTUxUJ-7 zl6tIMMoZ+$HAa?p4AE*TOpxuJq@t^og+t_e^%HC+=!G|fh;q&q+V~RQkRpqif0^7k4n(ZDGAeJP0FrWGzX5#3WpYJ0?iWPNSCB15MjG{p8^YVBGbL%I*Jwnx~)ek z=bnl0vJ6eu}cjFeu@-LYPD z?wx5`s^2!w&@Hcqar0lTLiP3Y`QmG0G@*fFRn$g$v6jGOADw?`-=&3qB9cnb6dmM* zoJ}rFm6eK}EWygUsf39DC#ig7*4)t_qt6i2O}UI}C#Dr6qeKdvNEvqHm)GLwfBFXI zUh8EO&Y_t|l6o>Q^9rlf0D%zu}U z;vUUk#>rXtCk;$yU6)^44HkQon~8JM#oyKQXJIZ%T&$x0ILdGJVe##YVPzyImYoo1X?ixjzv((Yf z`V1f;;4+0o|WICP1yQ&W1r^l)Z z5E29{XX!Z+ZB21GBq}odM*We=V*yZ>#qVT4o$e8M#05%tO#noj0f7;%xY|IN`Ha19 z1UAE5z!%TWgm+1WAtsrK_KhbGfeC}z!EvW!4L^-ReNvldJC;Odg_9B_ovpuSK*_@2+djtw$Pd*w3u~LoU@HeuMzI} zF@5$7u19sKsPgr}EN5)vpE2Sky=x>I9`ghPjBPbOr=K z;9`4gfumT5ToaY2ow}Lm^_1d_qdmam%j1~iJ5FmH3AQe0Yn&7{&2Nn^EowJZ)f1)8 zGb|irSUr&`C590vz=-5c4wV5QS&qd@P6>aDqa__L3pppIPa)QUeJ7`f7K-82p9Y8- zXWjG~M<~*X(D%{hl4dg~Q6J6}zSre4p3?1@K@>Mx6z^_(%pxJbZtNP>M91D zGdU%LtEy?+$({FJ>X`(%SPH0&H)$yrU*vX25mjVdbjrqEBqw2UOgN1r%wJUBoY0TT zW4JIQf7Sm=dmh%=dqz=xM)y$_3VOXW|oR~q+i-S zYZ238B>hft1 z2#hGw;_r!Q-$?5%I;?(Z9rizU!VssKJnpkk3v`@|0R>(JKxj@$Q*8kcWAS1}+JP%x zYle2@ysmU+*UjsCPCN1huOlmo9GVFm#yFEg>9cK^X-5)HCXx<#obC9+$_{+ympNEa zoPfViy)h9*`PQ3cjYUI}xQ1m@CC`pkoReI|8 zSiejiof z$Slc#f5HeLj+QC9F--D}d<_8-&vVop{Xmh9J<|vcq5id6-DW@1*Nwz(68Gq>FjK1k z2Y#l%W<3Scz+~1Bftf-8EmUOpXGm8-J1H$D5{8T;(L#ZnG&FTvap$eoaL^G~d_duK z9J!9@QPn()yGk!Xfs%Aq4kr9);O;S-3-4qlvDPIXdb`h$xm2|Ea93+*c|{bFDB?^u zYcfu%j;iYYL`pW&z2^D_@Xgt40F?|XA`6RX5!1oh9kaP;sSPG%Q3Fcra!XEiT1fLFRvFLnIA_BUGsYgOw3UrqL2M! z0!of6aj6Wkjj7P8{!_nb#7UP#_ZjCn4ikxx@tc3xXDRW|Z0eD8TSr3yy}Id?roi-3 zfQxs3w3UG5HKK(U&X-(-EM(VP^QlXaL#2;ASTf5gpJrTIY4ICMkOV2lZac-I*V21l z;;C??4+Sf{XkL^<*Q-I{W#PZ*9?z~$j;-|_G zAkjw)kf{ne5jl@B4_3%Zky^#-xtO!=0AfJ`M6&daTIy>c2~=odQ!)XHse_Op@DPVY z-dmG07iLOvObopfE|6xT$Ux(RzppNDVIER^HEA$#!>szEM-#(ae`UdGdMnK5IH22q zd4U}#6%4%1v8B;`#u|zzrFJcFXqo^MAXC>V$v}(hD_zxC)HT&0NyTOX8?ztBS)ugf zmmLLCZ}er?uylhi0Sa)EN`_A&+4ZUkG%O0Xr^Km~{x|v)^iLk6o_E0t!7K;Ud^}_^sc+^m_*iGm-zlA-v!$f&(|0H+~Ed(iy0phG-Ac|tDr1)rQlh4UEo4YVA zW_(ui0dBVK^i-60)g0t|T=YCj@1Wdl%QNa+ryFmDx(!EagG!j}7SHPZ)h!w1lFYw; z5%&M6*6j0hK#97|6xkTck>sA3q5z3N$HCK+_AX4fArku`IV2+x_{-;d>$9Iun-$=Qa3PC7k~|RqAl(Q`ghq zvz-FzdHJFvV4`*x7cFQKZ5(^{#n%w(B(Ghfwj86+MV@+K+^5DpXGCozD|Gte~eCm7+q5(gv_6kCwta)Rm$f3&QL(Oet&jD%T3jH6l9 z9ikVV9Ai;&9dJ_7x6>(2ftgQ%6z_EA*K}HHNLeV^!Ru?WV(H4qt zUgN67)n!Ofa^7QSesY&=u=Xjsp~JHNWHH>O^WpGtr`)*{U57T&H25bB&c}uAbRU8@ z6yw8Hi?Pm?iyj8$3utv?>g0lUtFM&c#DQIOsFo$&hFQLVogwig9w^GNJ=2aA?_G@c z-Cu7sEK}=q|R<&A}WajH_ol+-*ie-;L@N#nqx9-KWT^8hiJIfSWJh#Z3bU3rf%wA zIGni*zfFG{hQjD{Ral^e?D@E#imsbETZ%QWg+na)(I-Q%grhgX=$zi-62yC{AW5!= zZ|)lO)+NzcAH$RHS%kV(Hv@6guI4R55P`tLG z&W*nH7k)xz3{}oh=PsPn#9o!tEgQqSSFEixoX-(!PzjbCvjMzSHJkxnd{)+t{Z^3l}*5salF}mSWk1cOipbEf?N^l#<`~mzTzAzxAxKs zCy88gN0w3HwbcdtzEwh^`L8phoMC2MTi$fHuG31m$;sL*kW zi=!k_Q4Ot$a_KBAwG*KR`v1NrTtY4BZH1-Pf6 z3jf-47y&MlKJLb?+*NlGn51eoT0pABe;xyqRnmMpF^K!3@goj}zV)|^F>@+F1>Px0 zDpO!6Rfjxss0wTXPG(XY1r3{MM%~aKnL3kc(#LAIBD*|~rE^>mC@LmSADwPVTw{~S zk?~h7(Pmg8bp$4jypP~RZsQEpB3P%|Yw9vv$xIBfjTbFA)|`Qx|GESykKxNVM6sqS zj0L4V$R*inu{;2L_~@)krUSbCto^7gUj%<7j~~e3Z(MF(lb;g`ocMR- zleSv3h};k_ol6Ob2;ihXgL6rpo2qYC<4jT=1w>M1o4@-}N5I%d5sBJ8Wr;;mEd)LF zr_tLQqCYfpNC=!1un)0TSb_&Bs@ct(6eEX-g5$?T6hqZf=TaZ`AM+?L=tnL&bU)ne z#1mUw=m>kM44Z+P;s~v?%msAH+By>ol2f&QFYLB3i^Ja8n1LpNiwbW$XBzW$Mh-CSy0;N7L;RAM)qtAb27{U?QOxEvTIM)v1{PHfLbG2uh6<$cjVMk6v;{qTD7gou34K>*2evIPrTw$-#+s8#3)uoSoV> zPj_kDVv&C=a%{YYmqWvgYla2Xz6&ahjGaQPM46i}=B&n^O zzrcg}cNa0flKj|=t$yN?IK6i2oKerL(nZy_K^YAyZ)~}zpPC8^jCTH=iaMpsxNIoE z2|>Cgu`U9W!#t+PR%6dT`UYxhu{h+yIs?vZawp1)jUD!eza>C<-~HTud*R@s({?zH z>#nH8byel?Qnr4C;+toBPGbSV$wHS8S+v}cMTY|0or;AF#n?YAo{vZKDzQ7#iv#?4 z2~@Nkrqkq5KnsV1p>MsVKE%T0U@%-jhJPCsA0+A%heYBy@plF-D5I>3z=xjr^2qQ_w zJA#TaCxQu!!;aRs4&d}FTX5{xucCS1Vbs008C(D5H^wzBWzKRtdb2WdG(=GiEphJ1 zEysPnGNb6bd3=9v1rkO*OzRZsVFajOvYMvEzfIi}aZdzF#!=HTH*wZHNa9Mj#9QQ} zGHwUZEiMU_TMgi<6=vdKp>k^pQH)as{K4~Xoa%5e%I@sbJCKD^EaAkWf6jDJ0b!j)h`?8y1`h^8T(kY%ck_;rq>elH|_W zDbnquwr`}})Mt{1==2LdxHD$naT=J+ygs>%YNXjGO7l)CFj}bWs2ObxNn8>oYo-%9 z^Q%y@;TrVR)}w9L9!@?!__^3Hsyb|(%(ySaoI(%o%q@e9lhtz_^?1?WjAB~fcnCU{ zxN~rKPAP84EXsyl9k}{xk6uFzCy^@|BVEl|{WH z>iB>bO&lx}Uzl=bSs65u?Qgc>>9=xCu8BFMCZ)WYn5mkZ5g=&UL8ar5F2w!cx&t*ED+m?{Sman{AnA4H ztB6=z25$S}b-4FmHjwjTL(B0P&zrg>QY&iP7Df5mLOlHBow(|;<>=naat7t2?8FJ_ z*xrYCeS8%j_|6?Dy&@m=JE=S^)J26{-QRp93Wv+VayH`jFI|V=F}3 zNCT4@)g6~vWjF)LNnL-ug)4&=9$CE?d#p4p zQub3B_NCs|iIcsLww=S=FHn~H40Fq+ciI>i{RAfDm=rURBmIv|7AQ#(Cqi~wWl2B& z_H_?V63Ei3c(@IC>XFOuJA)h!z9K8j4jG0{dR<1Fq)ggRpFzQnUW#3o;yvHE0aaJ{ z$Som(33kG5{dnN}*Wjv0uR=T=L)0HNI3MS~`iey0DzU=f+>6qc<#^w}-iFl=mhu^S z`sp{gC>y`D47dFGM!0fZ2vA8nYd)3eS;pWbAmm$SLvKp}Zr*p>|G5dbe{m^89+>r! zSZCdbD{$X8Zbiw`Quv$b>AA*k_;(zfPx^RfQ6O!VGi-nG1?0k(w#pfnKo_~d355D3 zpgC%P;7uMY67VQ@#9F}OeQ*`z!A{3$G9t+IWFdRr99;E3K8|+Eq(i|9=Bvqy0EUxR zE@jL$_b$i>^~N&T-8A)PxPb+?R$$J$xv1ZM z3cvc?7PKDdLS`P{3n8NOlIGVvUq1zS!}JDRfdx0ugPo2O9n7m}SuU2`wS+*T2d{i} z3wC_%1bhpJ02Itt7CgJZ)rg?K9YsqEvGB%)$STf+i`u@tZj3%32oHvHm` zb|brrjh-Njr1P4T{y9MuNCT5$38}%$SR8#pu6fhJSQ_PwF=RWGhiNWsk*q_zjfu#1 zM47N4F7BYYb$?(Nd8bl*edpvfWVHuQT3z(gdg3UmZ@&>Ye(}?2KXM!k@4E$MS1d!% zsncjV#^7zSEEYpp!Lsb!BpuIj;NkbL!pBM$((1;69f1z&nmExGr$aRYkT!Bp9?B`h z!-aEMN)k^|%ySJbcW(BT;21?Y(mAQ3h$lp+Ya^|#?wcrJB9aM&boYeN-Wfn|Fp3~| zo`I0Qqcr)nGQ7YzOfm;(t6`vePa7~{L&XU@vFUaAYa3yuUXU?92kJK&UuthYhvCnU z+Hl*#7%KB(v(J2(9dEVxaiDcRc6R6S9npx_Xc%GQt7fOgj3dW_!#_HWU~d;HuPjG) zX*O(BPIs~&cmHTNUi->heCHEXv?Z5AdL}~@A>Sui^K3Y|xgD*Cno+nY4+RSf>F36R z?6Pbed!-gH{nZ<2+v7*U6`53q9XcOHx5%0AKVyzOx4qAW>a7~R@|9QkwLZ7L}msN<03_nGPNJG%t6%|^PzT185HUoA967= z#`zg?mKmyc0zwHYz8Y2*qUPw5e3n$-coTdj#W?We=Wz5VPosy<)a>p|?E3dj2()=A zE9R^;FzY3#Ot+Yxv`)n$I7w?9`Lt-F{=zq@Lvos+p#SNI=V)T@@`p9v~~8PqdSN=+b@u!kJ=U{_oTVC z2cZz-0tvxf!bvn&U_MuV4z0@$*mRJl%2cS7NBq%`%t^26Bo&~s zlu6}Z;P!cKhy*Q0MCMTy#%_{AtE^w#b1c~Pj}2(t(`4v!h?~+~-;Kl1o}@@8M(a5$ zlE+Bh;kuS%T~mao%!UI$Y(xFF(}ohQEz^czdkD3!oWSwl_>r?9(@9 zB492^w09hH(o0B#1k|AD_6+|n6OF35;J3ZQ}KQ7HAGg11MDKB9_zaF@(rIh;%WKM4CAsAUs z|0aL)6DMw3>cwit)=5%lg1+u2x8NC)yM_xbda*cPZxbz8hF$H4udtPRCe5#gQS(4P z?)ln!6fP`8sEZ1xlndoo>)b8a@n5^JV<+zX4^d_T$;d_n>$!V+)6dzSMz9lmwa zsUpYN`roR#A{L(d#QZ+i(R7{?jewVjhz{r(8v|L7`|E-o^H z^t~SJ!nHI5_EUV*!_OgdNKR3t^WlPdSVzUz-vrvZz`L-Pc}L0LjdRx4r)>bR3~I&& z4>jjbgeWILc8P#VAHRnK!_qzJqq(C%Nham-V@;&8F~?7rC`WxPy@ST>Z=?Oqqp;=B zBk&M<2xD#Eg`N}Vq?@dgM9hW_i~L5wzYE=ed0mtKR6q4gTClHUF&YA1WKx`C+M->& zrIKpL1N~U@2i5cwaxJn7v#|Z!yQssFgwvCW9pBl3l4T{Bzo7;>OYCSn)Jd)hUuHgc zP7+pOM^vmU!cG748Um1V9NTn+?|T=;LSAhB<}QL(I$PVY0F~GI=o+A%j@;;kPXl*Q zGNudW?V@wVyZ-4KEWBkgI*ztt_YV%BbX749{pu(}1oAc4FCh1|6x|Ko^VMF*)Ef+ij7OuNDs8lR<}g!-2H1wq5Jw{Oo>wZ;KN52RThq--^>`cRF$H$FG8i;AY#m-^6!6 z_?p4#$e;+e<5)MI{@6=6{K66Hi_FE9A6Y?H^?cvWA!0UJ$EF^P9Qb_M2KS&75X zAHsh>@B(@o{Tw#}pM7}Z;n%SJzqTW*C>z&(;wp;oxS`S5N`sTp44o;xH_g+UQgTmc zVG}X)Y9<(^KxptpGJ@tCq6t=2ZsvdXxxd7fAAAsxeEg%>|HLym^4#-?hI1%~c5`u# zb8o*(vL}Pj8-_D><%)*@J0sl+Y{=-3F@8{^Pa6d z7KfyTzok=hEiHNWhr4lnz;b6Is5XLT8*8%KZnS>{4~()GMv&vw-(sQOlhxzHo)>$t z`tIA%eXJg_0KJkPKL%%hDQrFuVvSv}<*~o%T+g75z}Pq!6pb1K)44P7dRM^jP3olNY6kNM≧cyL3ADuVDC5UQB;*_fCcrl zBa0g#X97R}&>Oh+iyaKqNuY5)fMR_FS`#5EQE#n7>)}?s{JBHOSw=;8mnr5H_$aP+ z;N{QmXCA%qRd~>~KZM*>)HfN<`P+qvkiIr06ga=Dr=-wyA+v}Aqn+(JDOt!Z2^~2K zFUb_~Q~a|1Ei8ZZe)=-#hAnFzLLH2+LdQyj6B!NMe%o-oxf7dvTJfu%ChR9?q?w`{ zO}t{?AEmqbxh^kiGR@dIs+wHWAEI9qaa81hZ4aO6pni#s=SN%h!SxyKF^e~pb*Ug*`F#l8hAWp(?cU>aqS${d!0v6CKX{n)4l<4qd)#Ja9!Yh}d_xKUiJbWuf0@a99 z8CVUrj6-}im@4#|PCbi$Jn^ypbZO~V>)Y-Q@DPVZgAu9FBpy>kyDfT?J#P^)7HLkH(!1D1M`>Z|j2xvR z>wKpNcTmqnX2Z?#sNvx?$n)=}h^K)OhcgL4`Y6IVO2=ziJU&4nBVm;kU#Bt|yF_Xw zW^$qKWj!SW9}1ut(axAgU0=PQ8cF-i9i^0@3|2TTX&%^NX15q`3!|w5oMI!rALf4jt+^x{YDn9 zsO&;P7IiPEt3AbCKVouC)QuL3bK(g%s&iU#U49?_r85JIX+}L#gB8)I>;{j)apnd?Pi|Nq-3rjdo~M-J;d185 zdoK4rK9Aa$+|RV~>x}I(y@95Kw^7f!lh!7)PBxo|3#Sw>hnjKDExbS0OqcW{SAMm5 z-8kLahRhpEUp#>qNiKosVar1sDRcT zbkZia$Hj-FN;ZuiZP{bPwTppyg;Wsdn0hDGQ%_tz-$@3z^f(eY+F6Ac8}n(g zMqo0E=E>~V034*ZBa4dXZ?@yuuNn+zXDQu{i<;(=&{BZqzQ9P^<*EC zD@qQG`t!^^!*z`EVII0q_lv6&?uz2j)2HZCK%SyaIo+9ihRbIZ?N8r2xfDo?aweC) z>D)O{j5H;=UA5BMWaB43hTY%zo}pKeS(J~>pZp3n^hl%M$4TSIvb4j zY$Nqc6vR)i)uv6kHXbUQ2qYfxucFF+Do4`^lhUI zb)SV}Xe3EFcG7&fsuK9jv)O15*yyT$6a#i7h0M@aEv8EbChw#aXZU7Ee)XxmuJPd_(43ZywEU5{W>D?0ZbK_59J@BaKBW667OLAd)Q+4)hroIdO7S_^d-pIud* zhY#Ph7Im=z>Z!nbgj^CiS=&Qyh^%v@^!fq1s9(uoe!JQyCnTT%X$jCF1CewvNhsa59(qY37))LZ^2( zqJC>VoVf&I-1Vwu)KWmdEf1dTK6LEghppfLA?9CqEyXtx`nU*DK{kZ0o%=EGu8rtv zp!d;}&4@OiruSGcBh1o6?&&(%y}4Z6Mz-&gUyl>@RxBt1u9y=t7VJwN_zmvtA&yh+ z^D%<&>ql3kHEv~?h9Pbo=_w9K_20PDMCHSZOh|pk-Jo;^W1+yv-rTX|oPH)v0r4Gd z6kd~n@ShL_b;m7e-+2fXH?BwV>g8}1=A!(r8?fuE|Bh&v-=I^?!}r2Y@s@AN zTrBy}!?3wIex%`Fey%FQW5qCJF?K!Lq$+={s+d@1s=P^FNx-K3GZmZisA-Z zrb&c(**E$U@3Yxc{pp;8XAhNUaZ59dsKTSzN(sy4WWimShvql-fN{pK{0|?12l+Vg z)GrLz^d-v{2KjxI#lCFX2Nj-68+XXHr><6DJ{w{*XU(RslThOkta#u($SlZ#CpU+Vy$TROkOAWOyR3-Lz38Bg zK+f=cGKf<$l%5U?IVVXqVICiUeXk9_+FNK=!>G$Sw_SEW=t+cJC18CSqkqR~Vr#xi zmt8y44Z8FwkOn4~UY}i3HP&K3Fw>8y-iFyBosz^N5p*-2&9e91iTSr~Kr=efR$GU? z-+PMY6f!9;L!?DJVl!Lm9eC)B;_$%^{H&{)Zs&tkesvmRoC5kQnNKs}?Y(UbT4qB& zL5i0;Ds$=eHH+U-@_glrevZ>U?V#j>M7qot4lRm-c5*yS-9&PVUt zlo>k?A3*rfQRG%uVI zg;avIan~$QulMKAF(L}LGO(Y_gs;yoK{drTiXMDlK^0chVkbmyNgp9e9l=ZyEqSu( z{dM%3CaGFX^!H5b{|Razn-!y|E=Kp}QdS*2swC2p*G|33c02nbW=F(Qjng~YQMO_c zx_PC95vEuF&O_Mw#eYNF{v)W`un~FHRao-TN3i%q55ndl$fPL8w{R}?O*}llu+53Q z!DeStv~yx?`1`CQ_GdCdqadA4ngT;oKwR!iB57Ko zEe2WoWcBl-eK-~B4G_Z34%xcKMjG57p_mJtj47pW(4ZHOlc0nNn&PK2iA9{vLaAjj z=AF2r8&7@Bhoh4GX2@p6v|3RgTrja8GOTiw#-7lQ^+FHux)fh+T!`-b&cD(;IE)|s z!JnY|?i;c0@!!U_PyY>eZr%^uwHx7D^*B813(?PLyMt+?op-b=f!!xtQ9P%T9Fle< zn6J1ghh-|v#qJe~a1!K@D0ttK?I(<5FCPpOw8+Vtn;aDRH5t56Ua*ambWx;A5-`a; zSkyMYFGRmds>h^cOFApkb^Ebagc!F(mKY{yjSb}ZO0qA^B+iM%Yy41T7o}LIt0`{0 zPp4nsf#na~hy0sX^rpxTCxmUu-JHG8(N(H7A|Y z6qrs5ToS-Upu)$ArHZphJ143-e!QGy=JKO=NMI$0$C_N!prgFrz)vyH+(Ao^0B4oy zm|IvhrQkp#>oT&Ccf9#b(uB;gD%&??*IWo1qaMA)m0#KO|{fcB-Wd0s@hBp@s zD+7TwzP*=@lA@sRD|l(0qTTGF=9BoqpZy*_S8*IK9`s|=s|Bdw4gNussFMUg%(xt% zh&XWzxgkGpYs8gQf~}{&5`hxwk|>}bhl;_r?w}(xB}E=q=>jWlbmXRf8>yRaBZCS4 z!)hAJuO)>(iW4{vn7`_0qi8}4^`)NF0`);}OAmo0>Fy=JO5!{d3Bgyen_tcAwyR!x>8T@hoPo86Jl}-bc zNUUR{IdL-1d_~M*663%z_2cgKapcc)B0~Qcfd;?nkSnzt8s^zc@w7&Lg8odh`HHSC z4l&d(1M%gUbJnHUo^+ir9}3KPU?PTYFF)((FzaTQ2hWEBxRSG4Ie!;&*L8$4X-#zR zcDf8j#yj}k$wl)xw>mzqFiU80A*HUVp3b#?xG7H99uDG$&UReM`U+HyF3~3g15VAn zG?I3R=u~24ROSrdJ?Qtp&efi?o{`@9yb*9K8bRUJtI@sZ2DTaLZ+wjH+lIp}TZC4l019GOqS@lt1spC>qHf8=PRydrvDU(2j zvv`0YMN8CFJI#{5gWQ_az;m3vb&1aqLzxYeQJim>P5P+RztkLn!VQ?HT6O||`q~ndNKQ78qU>)soiqLVz)8ev z4?DxrMZ7HlC)sN92%A$=iOvJHG%+_L%bEq5e^tDcKqU|U}h{*O`z~17e)hixiAw1S4~D_u!-D8}RMAEG%{> z=+9@I8P5b7rR#9XQb4ccj81wz788Ih;biuDB7t9ebI{72RTmdB>ZhTcItdOs9;mT9 zaEIH2rTot3OcUXxew=Q|y<*`HOz(P^r|x6!Zd3Eej3)A=R!)lzqsV$!AS3%4Do#js z(Nt_yv04jYj4fX>Q$OA}(uXFnG) z*kApZ@QEe_>WVSu zD?G%r zB0-nKi{t%1e5lk;u(}6fO9`$i+JjBEpP&-$Npi~;Aw-k%BQLy)thr^#D#*ven{PI< zIKF8MGAoKG-Z=|)6pE!RD~Y!c+VQ6!h~a-c+ylFVO4uo+JBTETcIz(8B&KaI&aqqTWds@73f+7eaez2zu#;$RguxqF{`= zCyeg}XZQf#W9`5XS3W{-onCls=7efG)Jh`U;zu66vt@D|$jy8f#eK!pCy^qofXHtc zZCD;*H6u@ii}$m2oE=BvG)YuB+!ChqgM01_RrpMTi>ja83y|lTVU^y8#zyK1_OX;; z$t{r*tljQ`)d(8tq_~ASQk%SzV4{#3@9~7x+Fe*%R*!Gqo`ZX7u@mtXA}iDdS8*wI zLz)Rr@{nIM7q!27#o*2f;nGbG!D&C}DhJySWa3MYHsE)ED;o}XE`6c&82T?~>O1H_ z;KbpzBbVUBU*C&Z4|Q11Wxx!&dlC;xDlwD#!i+3LPL6!OgxeHtnPm=1^b6M|gKS56 zzg=E(2v>NnK@+)?9@0p?l1^y~Oa}#Ke5@S3XY$rrCFN0aLMq7x>ET4yL7fx%s#xvJ zq)Tx|hNW&v3$H79+cJm4;FM%>5;6b^^B8Udoh+iO-cqDI&3b;D0IH02rQZ82PUgZR zts9%O5XIhJe0Y5wzWh)Cw=QqTqT;A9!a1gjB;T1NYYnS(Lb9$vW5G(ef&uCqw4jh6 zMD_@CH z#hDsR3E>KHOM>+Fy6wNVA;$MY=HZ}a$lw)Kx+Hdc7C&BI_ppz|6E!XC=%NM>EUd-q z@`Lo&E74wr(g!O_Gn;W+O&I(3M(|Qo2Ig4$khgj%7w!@q`sW`ayDA5n$tc3H44gU{ zMQ4{Cg^M!rns=wYnpEH2Xbys3%z==G99QA2Br718S6qxZG6TR=6 zmiAJy^)sF<%%#%n&mtidk|n<-62^TlH(I&tUQEF9CY@+;og&91LGPXnp-mA`lzdJp z!%8u7@FY1M#7>t`%l@juxL62dvcLLIppY{B5}1SBwRu z_YXQxAxbcrTC=;k&G#}yL6kxG_CD2sIahnQxN0SpD1kAG2uQ3}Cu0J+$+aNs%t)%0 z`R*Db24@0gzFI@vLY|s&hxCrZeVJI8AHn~AA6*F?uf$>R0vzA;8lqH!KhfJj)(AgX zg2g2#@csvvlmkxtf30wG$TBU~K?^g;w3tyD0U=&XrWPBk>a zmWVY%@St*J(tQoR?4IW4Vb|LyVelKs=}6K_$^;}Xa!0)EA5l>?)aTq3=BXvbE0sB2 zrXC0ZYAT6y5@sN}O8Y^4rsyEP`(_EAt`BmdEyvtXeH!cYPUHIi?_t}DFXR5oBUqZ) zga+GV$PB%JCH}8ti5i!=kqz3;I;a-~jNVxXqr3q^NHNw`kZCBP_MW0MwlL=%&8WjX zW;{EQe^`%b58d@s*F?!%A&*XUL@BY(s5vJJ;+MdNd8hH!x0b_}DTjjt4K;F2|4CC| z>L@Uyfr(xVdaTAOifY!8<@+~e{qEyLRzsKJy0()FuP#o0k7bGVD$5QoK>RMH<~`RY zign0T)^;$RW()4huG9X`1$NlOI%)H|48_YKUe z{|34lckPAZk7G|PA6`z#Nv?vK`2;2}H(>Q66#Hbm40rQd#wJ72Rd8r?ymL6_|Am`0 z%~ws1hZ!)uyF-BAxekHNqL*Qu+!YJouEEP4wW4D(4*&Eigt7?{gl*8H&sMcIVv-) zV=^?wC2lKX$3D}Yf&1rlq11btii`xCgLc5@K1t7??DGWY-c470iG8{N**$My|AOCx zi{_&~OCzjvR^kV{a!~4$0yLLYx-btkMp3lXhrF761JFq4VKS4Z@=NVA6g@;GS+fPm zO%d>yqN~6~H7%I}6r0M6ZZ9_o0wfg-gm_y{nPH9{;r(2v8H>nG04SXwQzVi@7w-|i zpU3;C1G23a4L>+Z;FwEKwprw6(E%Ux8xD}9>Sv`Kubr`6Zm+Dv4RiPK^w7_!i&Y03 zIW+}YtanL}BASD62cE@N_h*q4Yl1uPy(sAVC)`A_Ubfqgm$E+wcVaGlf$#F2weVR9 zxqJv&a|fV_wmT|sh$LbWBuVeYAs4EwXFNDXdM7NKBsWZlojw9Pfi+fBK&g-VCkc9T z9SulQtK^`-{{u`8Cz( z|I*)$EzvNZ2n7ufuMZKZ+{;fr`N}#@^6hkTLKG;`h9NL8IchI9r5~2d|9sU2QPOEJ z1tiXK(TWBwIjC)4Msb{WYo>T+@O@{Gxo_u+!+8=*>z{?UXA=^R)i_qR5dn73S68^pr zmxPa;Npwj=_5eNimbXxJq|?K*C5+3=yQe*g_Ilv0jB?Z!d zVn2er>t!5sl%dx`>mX*?>R1n3;uy!;VT3GM2xSxyYsmV?$n{Kp&N<*E>757^c*{J< zroR(8kz?~sf}V5p1pZK!*fH@y+;Hj7CL5ESK)J6nn}H1J=R_Saf+XHpqMyE6GUK?f zrWF5<=)Gv-B>4~(*rXp2qVC8VdVT$GD!={_6=9Fl66evb zcKk1ja{k2Yqw=8z3&=^Sq6AxWf+Edox91{DS-YqUOf3^m+9Y>Q5-p`f2a{E{DXN;d zCD+`&PB=N*se?Qe7tVRiYm(FAN&a&k_VF z#zOqg+Xv2203$Th(t)Dvb`HGxxA0y;qX?pxfj7_y9!?a_wh)}mytnTnP|op zwA^V&t@m*pabArpf>}%bAfR=P~5<<(q=?fG7_y!y50%P5=TVhoEX80Gn-r! zO?3P(4vqj)cWn=SuIR>L33cXzfTh4lRTjggQX_o;-=DdjK1fqwswgm{fr+LriExfl zTqCU#Ey4_~W$dzk7U-eXO#&~GlVYK7ldCAYY3F3;;r)_}YtoeZn&RX{^rB7`MF+}Z zkw)34X&I!Mv|BUQ1Sjn_9o(H-Ir&cnXGS^&(J&`6vgyNu761U{o6ZrM164Z4q z!>ZD4%u`VGLJJnHZU{CrG-XoRlm`fr(XxI1ow&L-gz5!*@zE<6;@b!Fu!w=-`lxFZ zX^G(E){`h%UPAZcWL7Kt5lqtKZr~-9fq+OheTUqcQ;OH=wm->0h9?L(-i`+NK3NQq zayS~Ke3#|h*u z{4O4@xEFOt+OXGqFYXHdm|PLgG11qgwagpmBeU>6)F%qDBO@2Pvp$CphPR-y?N8an zYPLfjf-n9ar}R#I)jq>wM{#yWBT~6|y9V7s;u0$wToVD4R1ycrPBE2t4n^N}{?zG< zd`Au4oox3Z$1hmHqWC|$miSu6NafHt7d|Z)T zl&y6)`*Gx@29&LJav{C=2;(*cK8hrEQ%M?`VSHh29qwQLE4q(wqdpSB>4<;{E6U0m z;gRZT-B%rgx8o(`cYPiUb6-L*wj8f#f03IV2d;5aKO}MrwzgklapDwKC7aOUxC`Dy z2Q!gAn*v~{ImZ zb4c$}DjhclFA9$2Xf2+|LV4~ne6%uxzuV=*(wqc2)e~FgbZ%)1T#N!U8knf^n!<88 z$)!@EF=;Zc15aqr)yX@`d09$px;M438c*=vvrndSn40fYT?DA4IyZUs5U)g~@uRPC zBw?FzW-+SLRbC-YhhJ|jN2A||IoUmiS?L97rCGQ?3TM)ZP4jQYwu&`)WJ49vIh4fnzBgaw^wZGYM z3TvHNMkMDgyw@L%qi%C8cKy5^b8nzyHU&ExCQ16DkDrRJyZuT0!SZ^%f9WndRNHCv zo1ChR7?h|C_O&IW4HkMg+K67a#BY0HnR;TK%kSya_ZC&qy!r2?}UrI!9AJ4 zF~}i~z92apvd*D%$ulpp>OjtkyNCfzDef^WhJ>w#Ndkl7ju6YB+{Iao0CL7J5W}=3 zefrX-z>K%b5qb3vr#`7EO(0&!eD{oR0f3=CinTKxAJs+Ws-CLj1|SoJ-AD^}*XxcD$4aKd@Pg-8BO;`Tgb`JczqmUG+yRtYZg%svbWnz%f9eTsa%`3)+9Do2b-DtK3}no&sq5+QGzF%D z0+aB1OhvWRWsIK!7nq&KpZSP$(85Sr6m~RLqLt=XHpNL9_G#{xc`=a@Mz`-~ z75n}MEqSX6N=o7PEXVQEyHJz66^}1%z`l^3%6!yOAWHNsv0(3y4{%}BmUY+yX3YhW zKy-GK-UU=#6&Fc*TwMenEi|{M?^QGqki1C|O*Tb^3VtYsSXtYMqarSg%;6>K>ESbb z31E82b=eyWVji8VN!0Tam1L{v#r5c`wKP{@C!}@~gQ%=J*x9LG0;9b>Hq7&c@Q>HM zjsLjkpK-&y*WhN|BJ}E-kr7~xboba&zn4vFo&tQ9uGsHv z`x5=6l;PiPE0G!PV8n7SHrvWkAM7y*)nO|^Bzq+XXc>QZvl645It{%O#@SI2K*O|G zJC_Me>#|@Cuk$(;SPt+o(vljc7{{^0{z8*I<`~caGEPUrk1>{l57GO8^L`02UQNQ8AHD#SuPAj-S zELn(R0)$Nz%RIn{&GPWtPL4@CwJz6D)FTc{8#yJnP|S0RoEC|9j)em#@?_$LHxJ{> zAKQfL`P6kdZ&j4YMt+LwVs?s#V)*Q;Q~2z4KgEsJJE(Li^Ho{bQMo zZhCq>iDbAI`xpKNc4pm%c*o0_-|_%L1%FFjiZHyMxhU)lz}@*(ERXL;PJb;GcpET} zx+d9-wbLJYo#Uo}RY~z~M_4Nnm+iOEd$H4ZUYs3W8*fq`tdf<{wWaiW4gwh{+n&i- zP8W%(W8O*-!-(B4AGsELdhGQU=XewOEScitL-kpUrU#4u>-E-ik)Nbl

    $JcxZ z^C-3n$GLFxM%sw+V~$V#wsB#V8SFWW?kM^%qd!dlHwDP-=tgO$1^0wE;rhnMaM1Qi ze7)d7*jd!d_75U2_!UE8wkh{f^p!kj7>o7yHK9N7YnDbg!$zG%$^O8o83o4nc1>CJ z(gjT<1q4bGWY33sn>g_@Dr=0&c;uXzoP!}|vWYDG0(k`}U6J@ZiQ=enU z=~w;O_sjuWhR~#O+cE&qnqWjhI2vglHt;3H#TaZQaGu9B*f&BJSk)k*5Y zXunm1PUb<;-tN!Bd+f__%>Pf6S3QIq8vYu|(!23l->-3Oc^j;2ci~6L8ob?2B_=ZO zPPjIp&hs$+owTqWicKRgAIfiRNi=lR--)j}hk%P9SqwsHq3^z?7AnC2`f zIZbdv&z;&Wd&?Mxo|CM$TACI4>{N?#OoCEhK?-+%58hqcLuJR~JS||t)#!prek$n9 ziaHMMIZF|Dt%NgDhop;^29QsZRDz48(-rRh$ zwr8J6N4t?;f(bG;=%g@+vQrCQE6Fvnq><$@D2?ZHwTMkNp&j7TUo`5r&?EVl3_t*x=6$ z;vd!@#wV`+Ph3%Yl6pm4P!pNVH^ln{oz6=d(i{rzA~zRs3U?GNPmLH;Vgm@kGRP^U zH8YZ{{+z&!AeRw{lYyOB6Zj8~l~r)X+OeeLViv+m`GnGLwY7T z{Ai4_f@4fFDVG0{zSw{Z`7yVPwcxYtK7+Glr#_3p*cn_k9kcl=GeHMxvg8?>IjKm) z^q_4i#5cAtLm>nDrG1%UizWQrT{@kB-D;@pqVV<0Uq%gPRRf$ z;g-?mErp$W7$zv0>`Glc{|L=Mz3v3Qvu}~%+T8T~aH=uv{GlpLym|%wZ4lwC-?hG41XjE(&ovwI=tj>K_M6P66%^fl3!`)rrgN; z#q$X@hwnxFr!!7(W&PR>gZ^paDTk@X><ygD05=>#wMlUo)0$5TJ{kn?NIc1W?6h>dy1WwsT1pMYfO*Gb z=@V%ROb`Xq9Fqwm+u+5AI$CN~A}UOvb1jo5kH`jN+Sjf)bfMo-=#t`6)CP zKA&c8OF#C9T)4iZixFg7k)YeojEk_6WvLO&EWq-X29&gY2N72(Ji#|Guk|(R43wZU z+Jfc<3-I#K6DXl)KI*&@j93tAiy{&TV9u3uNF$m=%5ArjzAocW20?)Qjm#mZOwoY@ z7Mm*g~+TP1B!U_L89EmT%5WFAIvMgGpA zf0-isGx5jbc<>*8j=ukU3gs&@aWY5;ZIp&QG^Yb!yJ-{Nv*dXcd%6urUdeufk)b#w z>W5U~+9@3jJ3)+xi>XXr4N=9gC?@sb#lFc=wC2Iu-it)hBPh-7<#^bSTK6iH#eRbJ z%p2h+7iAadIW=<`aw|TLjE;XYB^!z+8I`pTMTka>ry*DOi&en9u6FQozIrtECD0~F zi3b(?hUdnxZ8W$h6q`AHrd#}Xa7|Py$G3E>^d0lR(<`msDT%9Wwk~qB%J72&WhmpO zATZy5KK5^g*Oa=jlnWlVwI&C z(a0c)Mv}F5OI5v9byan(xi2qY&hK~b`!ch#GP9Phtjg;8Zg5x zw!E?w00`N^Atzt?@ClCIeli~a>)#%IBWzRF8CDJP3J<8iIPF=)Y>RQf%cEFLJU;%b zEbDyl&iyQ9c+x3Ki{-EP?AsULKXOm}-{+o+!#KD;%oPY9IeR=lz;2%R4Id=d!T7U< zi+S7dDK6Li=EuGg@sFR6{qG%%&p+RZcOShJKl;Y6#&!N&( zfoACim^1*CH?@5_Cn81<#mr}a0dW8OJA9U5Jn{SlhoZ66qJ(yETx%dN zuq9R#CM8~cQ{}xnJEiR)q=;@TG^vN?=K&Es4)5LVY0!p0WZ9gI7PLF<;R{rGVzIc`i={=(! zk*(Eavl(f*n0;pOp%|N;k1=)|T^N0H?7Q@Q-1Fk!jxRp?^YQJ+e=^?pkN&s#*6ds2%_2;@Nl&AZ2QBJU(;jT)dOR()#>X#C#4{#HErE_Ux+oQhxgbKi{be&`S4$mn=1wDC0o_<8NXhFihcW0hDD{n`@M zkY0TH8)X6V@Bo<%YilMz9U`g?v0FYnDL-uX)gQxy#^zHb!F^eBc1Itoy zzDc9!K1^>fK$mle1N>8>iRI!4aS>zG^40Wa|z;2a6BkiWuD0*N#~bAe@@(|OLxAdas3lx%7#A#Du|C9T=|{ z>H->zogEw`!t^~&955B$_#Fq$eDCR^9#l8y;tqkEgV{>OszxJr?d^>V9IW3c@1hW=9;tf^YIU# z__6qvuOEs>h8IF#usYNn*lG^c7L%>!@a@VUVbmHZmXjvVu2%&mw~Nj-tS_i!S3i`@e{M{X*Sr(&Ts8clXh^f8m2<4lv$@h9RybYehWBuS>0T7?T^Lj|yBe*Sg6}S_=*l_PY7KAGZtaRs&3-T*UwQ)kGKP-D{B?tY ztiyxB+$L-nSa%!j4d%U^42KTgMn*VfZqJ|mSX_Gi7vce|ZaqUE$MJQ7^si$ zxyPyV9wq|2xvuFLKw&>wP~YWC{%(@eHXE%=Fn8o2pc{XXi4z?A&k|%>PvFDVf_$63 zFq?iWe%~l~-pmkZo6JwpMwZ)+>-jP5&Oq!Po{z`(w&PbOJ?@@7t!^D^4s0<8YGAU( z48FbDqgV!aLjq>^n{|rQ7FTG78QB|$7UCn{I1qpHO>d1|`#+0vKs~NK+hP?R82Fj- zj>q`O>oGrb&IbMygLBWuy{BJ{Q}{H@Gr_;K^9SPKU*aU#pPS(Ku9(JRqF1F`Uzr>y zk5*-QSAD%yX(MW_U4x^W7XU_+^PY;d~ux4%`wBtTNu+lJe?8 zZZQW0Bv&l+++rH8x43zw?$SVf^7uWuiqO1)9gJT?Vf#H_BRq22S=g@ zxq4#kXdHXfd*i3x_*6W@(!#-7%3g1?TvPmNLhW{K{6?Aei~71+_L_276N#i=OeeG{ zq551C?`8i~)3ZE<;q3g=FXG$5L353<_?0~$jZ6Ff4vwU6hy&AKi{ZI1M+=Km9QbK8 zHmAZ4JQ|ICd~cti&+@lS=gTH&IfKgYWG8+n{kvJVh0F|_u)<}TMV8y<#{nDe_1y@_ z@La3O;2vyYj)_(A+`{d-D0_Au$H#dtW^h{Gq{uE&^+(Ntm(PJ3n7n)*);Zi{4!Dke z?@&8_`KkM3a_#`ijU6PLibO3RO{^rxqksNH?BrzI*@1^}+I%=(cl=|V3>)#&KkRSG=>jq?IROW;6_M+=Yop)Nz7K81@qQw!d7MfR-Yi?xYHl} z)rbBl-oI~wTco|TakI?ocQprY4hL#ra&!1t=W>%d(9fABU!NR~Pe1!``e7~=yZI)i zuyq)4B0$k!V0ikgIK(~~r}uta9J}YQA;72N_<{d1cHuCMya6C8h zd~OVmBY%NCv_BDz1K$TQVN3fQ3twk`0jH+#jK-rsOT~{u_(h7k$wZo+V1&<+!^7Qs zb^vTzPu44nxbDtz+i;%yBBC(;20^RJ;9l;OUS&1WudB@x*3q%~cyI@PILr@fV6p)N z>&G{h1Fk66q2@qxU_JSEyI7NmnR639|J)nndmjEYWME^@?U|cVq~)X+f0GV}u5ktuTDg3k!_F6BJX<_HGPu%Pfj3=``Mx6I#3;6HV1aH&JNx1ea@ z804NEcPUNice2hoBuN}}wWm9rhtg(u(dMGQvYL31-TdUr4jr)!mkY;cKQ#w#0tYtltv8{7I*YC2fLGJcbL;EP4Cy3WZ16WT+w0Ctaeg1WYd-$sNc^{F zUd^2@@sapPY>EBp z&i8SodQZ$~^S&$qROCT!XFw9e=ak3_<>lwy1l8}vHu+CCopFuR&1X4YH;%8&-{)0W zy?tW!^Xh#yWnR_}tI{sV;Y&I?^OsoGn+KF^K;@MgR8L5d))Fr5>tRPuF5@g`B8^qVE&NpQNbbB^ldS$(bKs_Ppav#4ou#)n!?N4vW=iU-NUvK|J-43j>|Km6o*jrUoqTJ& z;o#HARc4~yy&+d4!`(Rp^JkdAehe!LdZJscKF;a*($SxZ-471O`~Tr*;&=By7Ck$6 z#^P$F%b6B=d?qKIrV{5KnFJ8p#ywV(6Lgn@@3^!s6}kXNP0+)i`T3j6@^DNj>8j5J z3cT;rq~I(FGEjLAI9my}FLAsUSB+h1AoETDW95jGpn^|M|IO!|_M6YGM^%Rw zJ`tA585v2J&nv*Ae}!fWvy%t@UIr>Wz_EB_;9R_C?=Qux z2f43k$<4(#cz%iGG0X4aHe8i((nL9a(OGlJD+Hu-tAfYn_pqdM;bU>;OE`e`PsE5@ zcBpm?yftFqdpV}~U%&%+#BxWnu=mxx6O_CJaQOhAF92Tt5IT30Xninu!{k$UtsMHD zjNUg2fSZb`(_Ii)@kJIv@7%@tSUlLt9Zj40>JUBsOgu5RvclNCJXh{rkDQKy-{#7S z?7KHib)B&0z%_AzQB{XKp97U7y7OzYx&~OG<#cD*H9DCe6_%EV67UNx?`w9MCihXQj{gJnPB!2hle-gXK4#c_1 z3vB;HigQD5f&!Iy3m_yA@ctl`v*M~<;XV8&KW+m~WiA~kwcu`>m^xqRHjSXfPlF(YBkE<UOoqCNLpS<46~u4btr5 z9cSXjn|A+pd}z-va&`%a)-Ab{<_19tc}cdraV`r!w|XpL%(EuP*R2gK>2Q->3{CBh z&a;!TbnxqVg8m2gQ~#@o=l%*jypMMJM8rA3$Jk%yK$je$|R;o^5=`A{07jIS0N z?BqZgUmUqFI)DTHX7^n9V(ggwdLD`L+Nb`0yz@){OWc3|2O|#ei564B2DcO^kWd;6 z00vx7r#ze>(S!w*a6uwd_yP!WDvII~$OvFm@*@BMfrWrUB`?rO>x9-1@)anUmq5cb z9OGW{CcXf`JOn;kMpXX(nUC}sd}lr?I{wdc1Pg=EYPtUPl9zWRX)Q6WWhS737M}~U z1U*?6bV<+fu5Mb&t1LBIfvn@Q0-``^*7}k(@QzPZVAmGV7r!<6J=`rL+lcovcBiOF zwFI;{nq8|v)|DJHKt>O@46o2tz1X5pieGA;i|MC+Gp3(E8I6;_K|5m^+3`QY<7a7O z%I$f$v=vJ|G5ZV&Ao8W32dMl371>7w_`StJONjUE?0jGhOA~#*+vTcaXD@iZ&vIi{ zkJtT_?>z%ps;rx1j{0=k&&4}mbt=xxG{I`ume0DG*AHtBTmuK{+l8-zNp+Ol#{sSQ z=UL+DN6EkY-Y-O7PaE-y;NK)*a0ZYxV)yu0qkrLSbb5BgzVn}ngJ(XUy9y>y#J~6W z&&RJW{<-+_u|3h|vbqJ1>o35B0#hNYO!%H4LnTb$M%`580)tt$i)w{1_^IW>=Ykn6 z4K1uIT1=cl``dR~R?JU;!>zsfYzK-<@Zi#l78JpX0Hm_a2u{*+L%sqH0fnD}Am0fz z%+vhcHes2j^&J=PW%hW;KaRdW%F`8Jg^YNu|4^jXlEJ-{pR}zc%xYp0n7_2XPsK{i zoB569BLLaLxR+$IWq9cs>JK<+-3zFDFIJH+(wxIK{*Y+H2SZu_lQEL}*TrBiKthV+~~=x&|PVvD<^7Fl_G zApX_AI2!jnuo$!Tg?{UCuzq%vIIw!;+@vh)Y-$eNJPv3zc};&i{`ohL#))wbv&z}p zwRBtNhiI`LEC9WWmtqcG{Qv$}ABj_Y-xovk7vjMePsM-!9LgEpH|Gpj%SQTJ@I3`I zN?Y(@$WN4tOCT<(XpImAm_|*hg07yqtm3lHdgMiMp2CmIFnO_QWTSy($Ib zW^#3<5*#tkGQekF`;+mldwwKvDT$rq--v$b}pr1Q}V8 z>kG|Z?qwO_gwsl^u1Vg5mhAPc^lZ2_FSqO`cDg23gqhixcz})#ahyy^}>Vw^3i_1E8i4hKB6QFnwOk&N+`RL5U zw5v#c)AtQ=zZScGMSXQp9AB{YvbYmu2~HsR;iJ_B(VYwq!p0s5bEvsn|ss;c8Tp!9>9lg1iR0(osCwN5C zQj+C4b7EyKvOpzD15ptH-yg+w5Cz|^{R?_%XICTAvpfPg{sZQB!k?64l^X(ED20sc z+tuM95HmY4$$PI73;1rBgW}Mfv`YraZq9CAHQDgibk=IFWlzM|h5dBy-r*iS&cDGm zCirtS;I8Q0wLj@b{4AAU?+beQ7;=5^vo?8geV3o%TM6-S_3S6_wPNTZ=1_W$_B35O z{Yu_j=nb^MULg751L|MM*oO5N-FzT7l#q70GTvUKx6fBM4cGEm~T^32|+bW=vw z29~J1l*4O4)L3Z^X<6N!@CI;S-CnV>s|Fga?OmQf+a?ARW>^RI56@)nbgJ9GZBli5 z7x~vx`rw}B$h)w&a|!}C2k}lr{DeKF7Ow5<)|JOL95cf8whZJmVqC`God|6h)~T$K z6Dr*Frmud%(ok_b9L{negcKH{AJF?69q3MGGrAFQTb`rR*A~jJ!632jGz!>_`?sME8wWYAr7;544Fwxe7)D^9igs6b4vWo zRx{8N$_f&}6%D%nS4A-)MusR7*HhukI1Rm|KV^CtG3Ye);z5B=W(T?UN%s423Fg+} z8I$~7UT2rMa@+~j@t;>q|5jmabvjWjV9Yb{U6x!tW0b?$MTE&Bs449MpX{JQdD7M_ zX$!OVvV}qYMT%Z!cNr0-tDziVBP@Ifd6zMA*syOxzc^tpbgla_Q(ru~c2%$tf?$imK7Wu^o%Vg?BS&MqWD*^+;Kx!XJRX7;W+^?`P6GC|W9z;oD))&$j* zotesGnTnFdC4ZR$j_0)2O4bokLyi!rww3Oawt18R{*<(34891WzuX(Y8M*sY!ufeK zYC}vlO(#7zUDkAgRApU|_zwVfuex6!_1Z5zV`tKHU5onCBv9!A4Dr@l{!|tpq=B>v zv2wP0wN$9twLqe;O(#$IrFpQvUm8d=!XfkoJ!Y zFnS&&wlDST`_voDKDDd8DvLn(rk>p7NCk+@Xwa3ROJQQ9Xlha@U7o>*;`H}FR`fF|kW|q~g7@e!7oT?v3nxx*tR=SCn zw4jba@BuwHa(N2;b$RNcsaD#$iyahTkJ91q<5N%Mv%h){`K9IVw*u4{XW;U8e6Xg#D2TytTOjAXH+u95e4&7#6vo4DiDxA)9D|d|iIUTTSi5rY+f5 zM5-5_6`)~H>y`7QomoCAMzUK>4kzqa&gwu$xUw?24J`=YhXL2FLo>|q%SQBLJSUwWpT@Bnn z=?O8}^1QebEQas>iRv;wAE|2CI_1Ve4urc&iO34CDO-D^rNiK*QvRc-Ur6^T0graV zVW9$>maJ_dKj$XmF!A@e5zSoD-Bk_>^dVu@d!d(cwu6EDa5!-y0}iCQO@<$tL9H6> zC5?JWMY$a^LTTpVB|wcVJV`~$iOE~>_0966@52=!>|qDgJcmF6NMeEsDw~|KzQ4a9 zT>J$h9eFbC1kQ{q*SA&hhsG=F&i6Yl`-o6&!x8cR1zn^~6&&LS{WuUzyJ85X=YPyr$pt(>jngos=5_~7m@dOx0%Tp3`2fy( zEF^(|cYJE9yI`2wA6GO?z+fi-C22J>)7P{1mpLLk`JnPi%P(s4cQ{)0rwCL;L2D1; z@3n9~Dzd+wBu(T0E+IB|aJOkI$PD=IFcb09VaQTM42U}t;azolF8fD%ZEvMbv+|X z?(;T!$F~?O+(OZ%mtjm>Ga%q9G=Uf1EIkM$cN};|>%iC?nauSvZ}yWLjVkavcPhu4Yj$9DUbm zSBV{57HWeqVxKJm>V?6zxp1?TUz~uBw$Y|L5xQ0Kab{0s1zX zm|b*)4q8c4@w$7G2BM1;7V`P{va&7kyPx~4B=)C&9d7xo8K`zrK) zO`JlIsb8^`--xzd%xN)1F$X4YfL}(<-$$EA&`Xb6MotC@?&5gj!~*1YX;EW(Dsb>n z=J~1E{5&4SETx7Kt!b%W1Rfv(3gDPA_bdT&#F_@gz6nSs#i35IR*|<3t;$0tGped( zFR6G!V9b7zma`r`x~(jm<9SOa!S{VgF*J$}5~!9LgU!P6Uddjo!n(b@YX^`2#x30& z_{X$~M{SRPP?noqzHXB4rg&}z?|E(oRmML-tnGifvR0Wq16neL(b(LhPQkk-^yvz7 zMaA<6;|DE772!2EdTMKR=f%60A{qq-&o*sWvo!~t()~u&w*8B4mu+TSz3yXZ%?Huu z4MYD?`iJX)$aXAOR%HBoq}(ThuQjeE)FifTSAAN~HaVwi`fBd8r`MON)~5yYFB|BR zTS=jhTYhOHs_u<~Q3h$LU=mq@#Ysn6VaIcyhywco&rCCh2v#^y^M@+YhP~Ofl3Q3! zc3!r9&RRl2^Myv-D({M*wr|AK;&YlXBLE|Kl8UPGykav?mZu%>7Z@N@`)#iyd-ksz z2e7iL7Cs=c_x#WK3e|US{-K+Tl>`M}rDH-% zM|xz8g=E&-)Klbk1(->9tci!>`cV(w@S&WU$1r>Sz6SE;x?RgouiJQz*=EA)HXBob zRg=N3+d~M_bjsK|XNT?87{qezaintcvhGwpArAG>=LCwiV0mbl;9xkoSkJlR3Z{6kshu@jPgt8SRBv&-L# z@U?`HC(_|}#`L<5DWA3ec<2W*Fj(Pq1XzrDC2o$N;_{0BHF;?ijGin*77Yz-jZydJ zY0;j%R0-Ru!Ti~J=F9C2o%CQ*nty|~svm|a&6rtic4w=2y3pK3*Kl_;uGV9l@bvNX z#Q7Moe;UnMR5&)Vq|NB-0$(G!^d73bz z^)6wnu8rbt2~bH})ng0Xjn6fY5M!@;|6*X32|@OZwzRval!vHkhd8zTRV+42m+RK* zjLKbo|MzH@no3Fwc1fKYYd^o56n%{IVWp>|Ey5dz;|#Dl z^#foV-5BweEjLx5Yr^n2)c~sV+>`!uU_&oIf$cZ>9f3XqgjYT~kd{OXf zzS11eLy+l=#&Z9bnTB#<6-?6%0u48aaJ{oH#w!y~ORRtVwpOVpqigiYwlu8McBvC# zArvWd$ix%^-s>BAio@dy>nut!3pQ>aLQkiLuRagkaNSgMC>|)Y5nP?w7~6(!y)-x4`0CPVe2pWHoy>bv7&S|JMligjOZ1xVh}`Jka<3Mnrzx>iL2t$z z^3Kz2Tlzk;hjSCCJG)GL{ur%S)35}N-To&oq$>XPoDV)Avo{s4zsX)bl|8aMsn_jc zis2*O4>q4yD2j@_RNKH&Wf}(KMx^iewfW;idG+HqE_$Nd%~P+}3Zl={G^8u!J;K-g z+G_=(&SUlqGQcWu(xJZMk6STjk9L+8f3aKblZB^A=nAe78D$aaSeScNSU}{5t*4#RNwVTtanCCT@&OZZQijH&4 zC%^Rl;MbQQti9*xWp|8vcU3*3ZlmpkcX=WtVWDOZ#;;v@ck3inFEGO7bIAK`1p`Wv zgX^AC!!UN2mwSqw-%X0AkE_)<6wk>^&X1eOYg{BT@7uj$mSuO7wkdxs;-kE;2VWrm z#&^b&Dd$xML0iw*Vf{^my$gerFXO2OAwjp3{d_;RMTCTiDLf7l*C;7^>S9LB^rUL~0P+Lo`e!U-X$S(iZTqCaCp{6Bu zDQ2Xv<1OuTnm28Wi_=Mxwx?3D=GSF$PL>!N(&0)9cn)<-yMl0Ru04k25O$YsbF zA>x$9Uv9{cIDU4GF4|^XMK#vyKPT!dLlK{%WkZmj znlj+Vj6ZjDMC3cBs%y?`T;4=|vURg;A79LJ)?jvLh+z)bBUvEca=atfto86W6fohn znjX5@%*IRjxKK7b&i*hB$@j3GD%uZN5a7b;5!A_C<5rC1*UOM(r+7~uy1OL4>?DNu z$mi9FNqUmy*=30RtPm+Ye4!m`eY{KZ7%bjlkQ}y!7ycghewl3foDHkL86@>hvvF6n&?ckF(FM9c)IdN+y6uHnvmdQUX)_YF$ZN)u zc)M;+brCZ`_K`;+TGESRI=oSs@`lN+!|c-SZ@aeN7V+;hc4`{wGwI_&mx_INEk1?{ zeJZplza4Ntt<_@_lt|AiY{VI-a|sOqVB=5A_uzuRI{PCJMKFk-yQ)vi=zleg);7LN zDo;b}%rHVkcHZ4F)Uka8j6x`;HV5fInYS6cKaTvxCjdGJ>5?WoWc!V%qB3&%T(OSCk_Nf;&t0 zwE1@Mso)QpPb>WxGH|>ZbMMVBv@8|Ia^rEz`D&;#I2@JtJ~TqyN)wJ7y<&xUf7mQK zY`a_D>?Z*A`^5Wj<8gk^P{iR&r<(qDae@56|-*8MI@;?*+9-7Kf zY5PnImKYS`G-2x&GXR za)gOhl8yjE(o2}gNyNL~BhwUknlXJm$AC?ezm^*noSZ+{u+!O~C)I&4`5JVVZ& zXrmfr%RFyUvIqQH9sSUUdZnQ@D8eDzs6-^=b<_s{zpN@c{oZQe?qT-&Bf=)|S>k{W zYWPmBQo+ju4yH}`L+v7r$-TCcO@dsA{qM81EZ~lz{PUjiaHFy4DqlM?5@}32w#r_O zRtUkT$SHaEOlR@t1i%L%eE%o7hcM}Xy`KDYqL!9J7~tU8)4LP+qg(qAna28OlV4M} z7hnyYf7!uOimT=p_7@*t;{@stJJ-8L3ZgM1*ze=xK@S1;A7Vx1J!yURnYI+qx)brK zSh2DGkwal_>L81A28;W`9&4CynPyN+1iT(Iw2goi3Av&MgxtaSkr~*k0#zMH_z3(> z`h>#CjMC@KR*?5|Jo`<@pc>zZ^g^}^s?P%ZxCf0icOdwNA}EM1wy_Q_%rRnPxk{|C zPYthscgL3kWXrt{Y$f1|FUeNKVKxVSzEh&?ke$EzLh`jClS52u)Rl$yd1`Xw2Q`4J zl#@#d=nqP8*7cU@{rn;nn;@$1@EFxkJ^P zNXvQvci231=%hYlPBJMj2u`dP8Cv`(C*3;~0Z{9Kj`GDfZf_{I$!e@izmYfZTJb+t zh8VmOZRStKz|{s&^1%U)^GuB4L~zIJ>{(W)TbOY;B7F+W%Hz-evrQ#LiM3yyxEbl%~MY?`}_q+XtL1FoJZAlQ3 z$e_WhKs@8(R~?jv?W`@~1n)teN~OW5T+DWUeKI5IMUFyo@w{i!ci*^qZK zi%{Y+p2d?JtRV)6B5ZBM3Pf}8AL-5aR;>5xQ!wfzO|O0LIf^Aecy6{utTMY4b45kS zp(+3ZuO`K&tbzlk(o`EtHdX7?R>tpSF#KJi$Wh)g3%3(RiO_3JjQlECvjh_B(Temn zHe3hPH1Hv8NTJnS$tn%z`rqEu`=?n+mo)L3@iOfF8+o!j6OTmK2gN5!ToiRiBb9Mm zoBq6SVKNuw5M<_`VBV{;8JojjWyA=`gjsZHeoI?kjJsm`Mlho7T1dcYxJHmcd^zAz zt-vRWq8oLiyVI3xEfm^;1qjz_&YFlcsPUX8fYbYB;;!;)DNsCukcld2(N7Bk_E3f7 z*U}kI-4tNf*iH%Ga6eL}1$JbAtJPg_t&Gbx`Q*KHnwqwxadi3ooVwQSDE`{HYGs^& zciga((f;M?9YIM~y=yRA0s^oR+{W_&#h7i@tY`vP>dKv=hH zPM~RYWT98d$~>h}SBkm``#BdpH4e8xZ+%D!srNoU>hEpnc0e3_@F-ju9d6gyGkt>F zc1Sg!9P~iHJ(E0O$wGPYvL$CcD*Ms zsT*j5A!<^vofdTwT9ZSuSpnI^w4y(z7F${sx)i%frbdU1m{Zwn;Xnh}g0kO*5Hq); zX(V}?Ga|SQ|LC8ih3D!b>EjF45%|K1HXFi9PI(?-|`V#p?^JW>^$|(zh zoA&4%-WIME?djPX7|K}~?Gf(C#G`HdyT)jIDH{-U!Du}i{rTqIQ{mn;5Jvu_%OT`` z5vj5`y%TBj?>4vhCD#b-rFE9=XgX_fuj*HA6K#y0w=7G%Ra}|Zm8tMc9qRXcNQ34} z!KbQtSVSr*REG#JmmT||k+RQQDCji&&B)RCjmfa3Q-TDdjZiEsT1ks)QfXEY?JUxC zdhqf4^0ggmR;K`&9+Ak~fsq^Xbp1hHR{_|*@ArfHhj5sM{2w`X5CQcDp*)@0pV~YI zWtkF>*QU2F{&>!2=Mhv7Pz_Iw=K|?j7H^=E*iT$-4phUgM3^|nObS$SE$ zIWjx5c!*6kD`H;3Yc7^iI&uo{Kx$cfup~pE->L+SKN50{{tebk>;>OCfv^=eG;$jQ zSG2$+7X|!_8OReL*qymCr}`_WMg=ilhlIch?N781dsYGs8R9yQg`VSFSm|}n(H{G& zuossiWBGB(H!y2JeobyJdS}1|4pg(fdl+($=C{%V9`xLWlR=XjE;;~+YW3IpYa(4U z+vgIjfuQm3-$jQ6)jkxtsm3zKbKr4@=YPA86I<(Uc`9wx^C^e}g)? zW!AA46sl1q<5h+&@g_k)Co9*udLMtZ$FD@3y)wq_jCPL!YhHA1K#$AxjDFY9bHgTW zoLr6OW5=nIZq^I{$wm1SH&}!6tL}4oEdUg$+XvMON!p*Q)0ALB4|5v8?*AtLK6jZt zL6$DQ!lVnfxM`+FORJ3pN2C|ouj9rg@gh&I$*6SBNt?&rPtMW#z=uTYIyFZt(l38b za!CeK>i>AUbIv3~^i!AKT1|_yG6aRAr8@U4AJdw@3(YVLTcqlVz5%S@vF(ali{d1u zlSFD^?JUJ_QP|_M1;v!B*9lz)dEnobBmXHdRHiRS2q5VGwV2|+>Q~mOXqe|Kftu?p zs#s7d%BC^Yt6=s64I$u9x3-_rKYTV6&!07{0UAm77GCsYbz=uoV)Wkdt(0y4SQ&Wc zarwFwk|;Z+ipd;4vN(5!%@UeKk~IyST3>c-xVhUyq>@*hbM1K31r_1=rUTD%YZ9B( zFwh2pszVrx0eNgJGBTO52-HXTzvCuiki5@c<7DRl&3r%-A|P^0GWU5*iQ2L%#EXx0tY1MZ1ld4D|3`o{HQVt*bo zXKS;=mBBc3(Nb(Y=jT9*?9X&)%r|Aj_JxS)A##sU9T0v>%8S4CIs>1Hh&hNn-6<*g zhw@GFPlK6%pR>DBTCwj(f1u}Ja6oIpa_v~9=V7%ak*tY(KM$9}{dCv4tMu-&6IEXb zoaP~X>_AYXLY7JH?dz zN3~~fnG6OyLUCK*`#2CMb+rb&8dez^GczQf)k{KD3LO}}3#^j)Dy6|5AaEI%kDF^> z*S#${M{}qoap29kAgg<*6=b>LdrY5M_Mk1TQc7tu_<0S!4D&*Lb?Nd+SLu_slDe3d zyi$}&P-aEZHX@=w(obmicadSW^9SLyw0mpRUR(|Q!jPq>+z)=@gik1+j&quh{AO9q z4px#{1k7`}6BJ`iWj@+kNF!Qzj0qyHo9L=N*8$+l&aA?FUP%IqqaKh!S+S|H!WiPjA9!B8L%_U^|;wY365s zdP&aMlA?3VNC`5oAACPO)SXE~(VrPn?oV-$T#Jgah}1fQTt;3SU>wcPjE&*XL`N@r!7_`mguEZr98 zNG2&vhDv2sf`F1tIm&fez<|uUz`j%>aNzoZ5kE@_A+)B01OD<^+2%{BY4r0^7(J{w z^=gxqvs?E;bfE+R-xrN9A<|(bN${zRPgevlm!x|}g)@}emR-#f$Rh7-GPfWr#HSaM znY5sYI{GyEY!+}L_G662T?1kFpgE;;%`)S@6^q2dw_Q1Q3`Qk`xTkLYm@_n~h@UYw z!7_+1T*7wzC-6eB<6#(i*sEw;w4;_{N#uSDT?V`N&jqI~L6y6^O~`$7OzDI=sh(Q* z+kPhR%ad%g4EJ+YHMu52W?fiO8P45Nk< z27M6a`y86x%kfj9P`NpO9(NBn>MOU;Q<@NOe~miyS-V)#ZM^~kFjH}53x;S`FuJpr5!hNVq)U{#c{ppKUz84T;DDY`VH&cxF{(zX7i>4rI5K1J!mFMTMfkk*%NhO5pGlx z1Y}0|FLV9`mlY#08hK7r?jLv+iKlw#^RBs8?&ZG{DSNq3#CR1EJc?_ylp|Zx#|#(| z_o~v9Bbi%r8JwA&?Hi&(K8b3&6&Ohc%n(Gh7WQB|WYSN~2J00&ey(`+hvq^uh{xMC z@ng=1@A3E1Cy#&9BwQ>z0cX(~x0SO&fK(INAOF^tU0Ue8IOQyfeKax!6L1E8c;5&P zbsQ~MiySs&&vvf73E@53`ICwI4<&nlTo`aroMNE{u^Bx4F{J6@d@iaEfq)PcP56R1 z1EzTM0rt#)_Rqd7_EkKIZTrQOBm|&k$dFy|)oxGW8TY#G*tQ88^n2@qe@*4UAJ|1m z_b1*Lm;_yLM)p1MT@&@?@=3S2lSnoXHGR07XpAHBkkP>>mx{S^ zj}Nz~jyRigVmXHN!6a!&WYT1Wtl(cgO|^esLN|e00L8RhbFVh~{WiCrjB=G(@Gu{Z8((4LF>n zv*@LW<)b1>pX-?rAEn#P07}rsm8o&Q*V}^&`?&Yc9ExU=KcUdOgZJfTEVT}UKx_qd zTf@vsV&T60aHjcbxwbbbUuHLk-wG2M!KY+tuUdZg*s#5!0|iAU#b`h0!!dHC zmzwK~v}28zklxPPwz5s+ujw}u%jBCH>BlMwd^uio z!#!2EUx;+O3jtDDS(*Uwd|nYy-HCjby`J7-npmT<#)XL1Qerte9)|y5cM;x7N?{@w zchwi?(!Vvtp}fDVEuX8GshlSZeM6R|#i_8ZyR#%&R#eS(Kj_E)55~1aEyT8hQcaP= z5@%nX+YmL|+T=aAy%Ufj%OK$i7GT^+i$HzNXzeyAQ{{Y`W80C&_(?D5c--${d;WOs z^V08bszx4KDN!wiT%ZD+su;}~zC-d7D%vc5{mYpayIxD)g44dIwgh$hjxj+Pt>;$$ zSpi0eM)#0lS`nk*sQ6dw!ct<1OweV$dK{38L*^yN0Zu8;K`TFocdSuPC*q71P&Q#$ zec)=U^y)Yav8!vR|Bz?x@Pl`#iN1hQZ=ZxF;`~Xkjnn#8Rk74;bw&A_>@gYY?y`d) z|I2G+ebFL1vk^>PMh^vt(JPb@X+uWFU(7qE#FYI|ADxJpm|kS#)8~-m$?D3tt0iu( zt7o=ahWq0gFb=PKm3fCFPoiz|%I6Az5Kq66q2Ky_x$e!L)R0121Vnt0nY-v)%#Jd7 zQQb0imlKNvy+5q9657-*w4_lnd%m7BZD zrs1$BRJcI@3S18+5QePi9=x3{7Ng83QC%}1FIn(UgI!SS><7?y2lb%Gp|Iri4h4_Op^tvcL-8uS(;RVt6&D$ z?2pG8gmBt1lWOkUZI(XB?4vWe|I^?gcYm)Ns3{ip&lM3IzPwp%}8k<_it+AWVY8q|B@GuyZhdyCYD!u$PVt+;Qca~&D}uV{c$Bv}>{P>Fcf zhvBay&4NpTwtd%gUwdGll~%6L_)MW|gl+STT_{_4og0%a`C4%AXFeGa(*lt)NPP0z zXA{Ao#JJt+EC@*BIGt$bbvcT(aQ&Q2+Vjhm%MO$VpfRn9Dve-^Mu%w6C;3T&)qGO- zXheQP*;E;ZpoUoBuk$tE@G5OCF0Qd$*72jOYnyJ6NEw|!wqHQEy-B>f>9{}cWf4LW zuo}g+OxQ&7vwK55%S7MftXLF<^FlqT=8|^;-ggSss-?LbIOcEl)+_=KW$1-o>CdW^ zot(_NR+@nyMHHUx2E9KidQ1^Y=xVUb7#Tc|EPgaZJTik{R zeSmwM#K4cpDmVpP+MURb5vb9+;uP^eA*zOzR4PefW1N_o>d1AV4OX`r5%zsmC=h^t zc>|?5dL^f}4!e>pySbosJri0MAU_-y<-nr;J8vd`9k@b6HD7%qr|v8FcYPY|6&cIY zq;|o6eJscO4zm98!uJ*0-d{yoA5)%ZcTYwbINg63px&@fqM?*4KdM8Iuo8!gT3b~`^wwxHx{Ei#FRQ5nR2i1c2n?=d2bsH0AlXK`_Z85o7ln4mdf(#$y~ z$WsT+-<_xI+h+e+$!Q;w?Dkxrl}5BUlNNf()kicqkFoxQN858yRA#40C{;&D>`?j5 z{F68!>a+ynO+0j2o12><1;+s+vz0c$|8$-EQp#7E3}0wgh6qRiDl_FZH2VZ5+%azUg#ovQd~=1HSxlQW`a z9~U$(-E~y?R*}rJ1Bi!j9)Wpd_0?0Tc}dggSR34sJ+nQkxu0@KRRJExgf%=FhiZyn zd-HHv&Bv%=ioE^TSfO?w~b%~L{)3yfA?n*~jGBfpH29{8NdW6dbv zpIF^m>mCAGLKg32I0z!|vGAIfFqmNL{4V7tyD`<5I~Uic;nQ(cO&n_v(xZOi5XG@h z&5qMp?|*vo(k9wqICOXZlHqshrbqWevx~%P42QcKZ7UHOh({HYMAS?P?!I_>1!{f) z0@fF2wJdIfmp?i55&7j@`RKJ+Zyy#{`rlG_%U;&r$TROVu)P$@QBb@{kI?{=O7^`H z--F2@FgwmN=GEGR2&79*CBW>zTY|+o?6=PA0l!V0mJ>^`-M(G8g3L{Cxk#{=Jf(6e zJq#QZm!`Ml68b!4nTpK26u7`@x9O(ihRxC|K9cA-DXuJpSmE^%* zW2=$YmCaU<`gL^+>slPc?uy23bDt{}ayj6m8qEn3qT+1b%Q6e8<{I6$&0#1Y(zzeD z(ie0*#dP{c_lrFXbbNNkd~3=lE#y;l%-n&F`U%9p@vu$CDdn`S|J^3Tb0dGpYQ2Cc z!pCUnr_6B||L?$LcvtX$Ew%kTdSBfBci;@E8|=TL>WWsLGE3F}vG|{f6U{gDxE*2L zoP72_7XR~jHuN7&pzm(2Jzw~LzLu;0OFoe;IdO0N=lXx9wjTH2D{8&?S6i<1f4&-x r`QD}_P{04Z+w%WELnqT8_*ao9%mR!{M-p?u+a)a_|EXNe(EtAdb1?86 literal 0 HcmV?d00001 From c66d19758a1c53be8008e1abcd5264f1f57b25d3 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:09:30 +0000 Subject: [PATCH 12/23] [CI] Auto-commit changed files from 'node scripts/yarn_deduplicate' --- .../observability_ai_assistant_app/tsconfig.json | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index aeab635d676d6..d4c909cfea6dc 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -19,8 +19,6 @@ "@kbn/typed-react-router-config", "@kbn/i18n", "@kbn/management-settings-ids", - "@kbn/security-plugin", - "@kbn/actions-plugin", "@kbn/triggers-actions-ui-plugin", "@kbn/shared-ux-utility", "@kbn/data-views-plugin", @@ -48,24 +46,10 @@ "@kbn/apm-synthtrace-client", "@kbn/alerting-plugin", "@kbn/apm-synthtrace", - "@kbn/apm-utils", - "@kbn/config-schema", - "@kbn/es-query", - "@kbn/rule-registry-plugin", - "@kbn/esql-validation-autocomplete", - "@kbn/esql-ast", - "@kbn/field-types", - "@kbn/stack-connectors-plugin", - "@kbn/features-plugin", - "@kbn/serverless", - "@kbn/task-manager-plugin", - "@kbn/cloud-plugin", - "@kbn/observability-plugin", "@kbn/esql-datagrid", "@kbn/alerting-comparators", "@kbn/core-lifecycle-browser", "@kbn/inference-plugin", - "@kbn/logs-data-access-plugin", "@kbn/ai-assistant", ], "exclude": [ From 4b6cb0dae1c56ea526d3061d5d7c03e35d98552a Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 1 Oct 2024 18:38:18 +0200 Subject: [PATCH 13/23] Move i18n label inside function --- .../src/buttons/ask_assistant_button.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx index 060e829b4907e..624c3df9a1e84 100644 --- a/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/buttons/ask_assistant_button.tsx @@ -39,10 +39,6 @@ export type AskAssistantButtonProps = ( onClick: () => void; }; -const AI_ASSISTANT_LABEL = i18n.translate('xpack.aiAssistant.aiAssistantLabel', { - defaultMessage: 'AI Assistant', -}); - export function AskAssistantButton({ fill, flush, @@ -54,6 +50,10 @@ export function AskAssistantButton({ defaultMessage: 'Ask Assistant', }); + const aiAssistantLabel = i18n.translate('xpack.aiAssistant.aiAssistantLabel', { + defaultMessage: 'AI Assistant', + }); + switch (variant) { case 'basic': return ( @@ -85,13 +85,13 @@ export function AskAssistantButton({ return ( Date: Tue, 1 Oct 2024 18:39:18 +0200 Subject: [PATCH 14/23] re-add server to app tsconfig --- .../observability_ai_assistant_app/tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index d4c909cfea6dc..e1cbef0b075d9 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -10,6 +10,7 @@ "scripts/**/*", "typings/**/*", "public/**/*.json", + "server/**/*" ], "kbn_references": [ "@kbn/es-types", From 87d2e91440741e1d47a17ddbeab5d0edece1edda Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:51:11 +0000 Subject: [PATCH 15/23] [CI] Auto-commit changed files from 'node scripts/yarn_deduplicate' --- .../observability_ai_assistant_app/tsconfig.json | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json index e1cbef0b075d9..f5b6d1db53885 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/tsconfig.json @@ -52,6 +52,22 @@ "@kbn/core-lifecycle-browser", "@kbn/inference-plugin", "@kbn/ai-assistant", + "@kbn/apm-utils", + "@kbn/config-schema", + "@kbn/es-query", + "@kbn/rule-registry-plugin", + "@kbn/esql-validation-autocomplete", + "@kbn/esql-ast", + "@kbn/field-types", + "@kbn/security-plugin", + "@kbn/observability-plugin", + "@kbn/actions-plugin", + "@kbn/stack-connectors-plugin", + "@kbn/features-plugin", + "@kbn/serverless", + "@kbn/task-manager-plugin", + "@kbn/cloud-plugin", + "@kbn/logs-data-access-plugin", ], "exclude": [ "target/**/*" From 8b490b0fb72db53498b2ae0af5acb818290cf142 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Wed, 2 Oct 2024 10:22:34 +0200 Subject: [PATCH 16/23] Fix linting error --- .../public/hooks/__storybook_mocks__/use_kibana.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts index f836c3dac6159..deaabffeeb50d 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/hooks/__storybook_mocks__/use_kibana.ts @@ -7,10 +7,13 @@ import React from 'react'; import { Subject } from 'rxjs'; -import { useChat } from './use_chat'; const ObservabilityAIAssistantMultipaneFlyoutContext = React.createContext(undefined); +function useChat() { + return { next: () => {}, messages: [], setMessages: () => {}, state: undefined, stop: () => {} }; +} + export function useKibana() { return { services: { From dbbdc21c58698c36ea3662a4e31cbb756700334f Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Wed, 2 Oct 2024 12:55:38 +0200 Subject: [PATCH 17/23] fix router --- .../test_suites/core_plugins/rendering.ts | 2 +- .../public/components/page_template.tsx | 25 +------- .../public/components/routes/config.tsx | 59 ------------------- .../conversation_view_with_props.tsx | 34 ++++------- .../public/components/routes/router.tsx | 24 ++++++-- .../public/hooks/use_ai_assistant_params.ts | 14 ----- .../public/hooks/use_ai_assistant_router.ts | 52 ---------------- .../plugins/search_assistant/public/index.ts | 9 +-- 8 files changed, 36 insertions(+), 183 deletions(-) delete mode 100644 x-pack/plugins/search_assistant/public/components/routes/config.tsx delete mode 100644 x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts delete mode 100644 x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts index 8c7f165597227..3d749ef7ecefd 100644 --- a/test/plugin_functional/test_suites/core_plugins/rendering.ts +++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts @@ -205,7 +205,6 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'vis_type_xy.readOnly (boolean?|never)', 'vis_type_vega.enableExternalUrls (boolean?)', 'xpack.actions.email.domain_allowlist (array)', - 'xpack.aiAssistant.ui.enabled (boolean?)', 'xpack.apm.serviceMapEnabled (boolean?)', 'xpack.apm.ui.enabled (boolean?)', 'xpack.apm.ui.maxTraceItems (number?)', @@ -318,6 +317,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) { 'xpack.rollup.ui.enabled (boolean?)', 'xpack.saved_object_tagging.cache_refresh_interval (duration?)', 'xpack.search.homepage.ui.enabled (boolean?)', + 'xpack.searchAssistant.ui.enabled (boolean?)', 'xpack.searchInferenceEndpoints.ui.enabled (boolean?)', 'xpack.searchPlayground.ui.enabled (boolean?)', 'xpack.security.loginAssistanceMessage (string?)', diff --git a/x-pack/plugins/search_assistant/public/components/page_template.tsx b/x-pack/plugins/search_assistant/public/components/page_template.tsx index 4e8d5e64f7407..e9fb3a45e9e2b 100644 --- a/x-pack/plugins/search_assistant/public/components/page_template.tsx +++ b/x-pack/plugins/search_assistant/public/components/page_template.tsx @@ -4,32 +4,9 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { css } from '@emotion/css'; import React from 'react'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; -const pageSectionContentClassName = css` - width: 100%; - display: flex; - flex-grow: 1; - padding-top: 0; - padding-bottom: 0; - max-block-size: calc(100vh - 96px); -`; - export function SearchAIAssistantPageTemplate({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); + return {children}; } diff --git a/x-pack/plugins/search_assistant/public/components/routes/config.tsx b/x-pack/plugins/search_assistant/public/components/routes/config.tsx deleted file mode 100644 index d3ad0ef97267a..0000000000000 --- a/x-pack/plugins/search_assistant/public/components/routes/config.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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 { createRouter, Outlet } from '@kbn/typed-react-router-config'; -import * as t from 'io-ts'; -import React from 'react'; -import { Redirect } from 'react-router-dom'; -import { ConversationViewWithProps } from './conversations/conversation_view_with_props'; -import { SearchAIAssistantPageTemplate } from '../page_template'; - -/** - * The array of route definitions to be used when the application - * creates the routes. - */ -const searchAIAssistantRoutes = { - '/': { - element: , - }, - '/conversations': { - element: ( - - - - ), - children: { - '/conversations/new': { - element: , - }, - '/conversations/{conversationId}': { - params: t.intersection([ - t.type({ - path: t.type({ - conversationId: t.string, - }), - }), - t.partial({ - state: t.partial({ - prevConversationKey: t.string, - }), - }), - ]), - element: , - }, - '/conversations': { - element: , - }, - }, - }, -}; - -export type SearchAIAssistantRoutes = typeof searchAIAssistantRoutes; - -export const searchAIAssistantRouter = createRouter(searchAIAssistantRoutes); - -export type SearchAIAssistantRouter = typeof searchAIAssistantRouter; diff --git a/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx b/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx index aa4b6eb723d57..545ff1ceb7370 100644 --- a/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx +++ b/x-pack/plugins/search_assistant/public/components/routes/conversations/conversation_view_with_props.tsx @@ -7,36 +7,28 @@ import React from 'react'; import { ConversationView } from '@kbn/ai-assistant'; -import { useSearchAIAssistantParams } from '../../../hooks/use_ai_assistant_params'; -import { useSearchAIAssistantRouter } from '../../../hooks/use_ai_assistant_router'; +import { useParams } from 'react-router-dom'; +import { useKibana } from '@kbn/kibana-react-plugin/public'; export function ConversationViewWithProps() { - const { path } = useSearchAIAssistantParams('/conversations/*'); - const conversationId = 'conversationId' in path ? path.conversationId : undefined; - const searchAIAssistantRouter = useSearchAIAssistantRouter(); + const { conversationId } = useParams<{ conversationId?: string }>(); + const { + services: { application, http }, + } = useKibana(); function navigateToConversation(nextConversationId?: string) { - if (nextConversationId) { - searchAIAssistantRouter.push('/conversations/{conversationId}', { - path: { - conversationId: nextConversationId, - }, - query: {}, - }); - } else { - searchAIAssistantRouter.push('/conversations/new', { path: {}, query: {} }); - } + application?.navigateToUrl( + http?.basePath.prepend(`/app/searchAssistant/conversations/${nextConversationId || ''}`) || '' + ); } return ( - searchAIAssistantRouter.link(`/conversations/{conversationId}`, { - path: { - conversationId: id, - }, - }) + http?.basePath.prepend(`/app/searchAssistant/conversations/${id || ''}`) || '' } /> ); diff --git a/x-pack/plugins/search_assistant/public/components/routes/router.tsx b/x-pack/plugins/search_assistant/public/components/routes/router.tsx index 5f61754991749..154bc2ab46a3e 100644 --- a/x-pack/plugins/search_assistant/public/components/routes/router.tsx +++ b/x-pack/plugins/search_assistant/public/components/routes/router.tsx @@ -7,14 +7,26 @@ import React from 'react'; import { History } from 'history'; -import { RouteRenderer, RouterProvider } from '@kbn/typed-react-router-config'; -import { useSearchAIAssistantRouter } from '../../hooks/use_ai_assistant_router'; +import { Route, Router, Routes } from '@kbn/shared-ux-router'; +import { Redirect } from 'react-router-dom'; +import { SearchAIAssistantPageTemplate } from '../page_template'; +import { ConversationViewWithProps } from './conversations/conversation_view_with_props'; export const SearchAssistantRouter: React.FC<{ history: History }> = ({ history }) => { - const router = useSearchAIAssistantRouter(); return ( - - - + + + + + + + + + + + + + + ); }; diff --git a/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts b/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts deleted file mode 100644 index 3e0efe5992135..0000000000000 --- a/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_params.ts +++ /dev/null @@ -1,14 +0,0 @@ -/* - * 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 PathsOf, type TypeOf, useParams } from '@kbn/typed-react-router-config'; -import type { SearchAIAssistantRoutes } from '../components/routes/config'; - -export function useSearchAIAssistantParams>( - path: TPath -): TypeOf { - return useParams(path)! as TypeOf; -} diff --git a/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts b/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts deleted file mode 100644 index d99cd7a4522f1..0000000000000 --- a/x-pack/plugins/search_assistant/public/hooks/use_ai_assistant_router.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 { PathsOf, TypeAsArgs, TypeOf } from '@kbn/typed-react-router-config'; -import { useMemo } from 'react'; -import { useKibana } from '@kbn/kibana-react-plugin/public'; -import { SearchAIAssistantRouter, SearchAIAssistantRoutes } from '../components/routes/config'; -import { searchAIAssistantRouter } from '../components/routes/config'; - -interface StatefulSearchAIAssistantRouter extends SearchAIAssistantRouter { - push>( - path: T, - ...params: TypeAsArgs> - ): void; - replace>( - path: T, - ...params: TypeAsArgs> - ): void; -} - -export function useSearchAIAssistantRouter(): StatefulSearchAIAssistantRouter { - const { - services: { http, application }, - } = useKibana(); - - const link = (...args: any[]) => { - // @ts-expect-error - return searchAIAssistantRouter.link(...args); - }; - - return useMemo( - () => ({ - ...searchAIAssistantRouter, - push: (...args) => { - const next = link(...args); - application?.navigateToApp('searchAssistant', { path: next, replace: false }); - }, - replace: (path, ...args) => { - const next = link(path, ...args); - application?.navigateToApp('searchAssistant', { path: next, replace: true }); - }, - link: (path, ...args) => { - return http?.basePath.prepend('/app/searchAssistant' + link(path, ...args)) || ''; - }, - }), - [application, http] - ); -} diff --git a/x-pack/plugins/search_assistant/public/index.ts b/x-pack/plugins/search_assistant/public/index.ts index 0d4aa25177e5c..cb84f8519fd96 100644 --- a/x-pack/plugins/search_assistant/public/index.ts +++ b/x-pack/plugins/search_assistant/public/index.ts @@ -13,14 +13,11 @@ import { SearchAssistantPluginStartDependencies, } from './types'; -export function plugin( - context: PluginInitializerContext -): PluginInitializer< +export const plugin: PluginInitializer< SearchAssistantPluginSetup, SearchAssistantPluginStart, {}, SearchAssistantPluginStartDependencies -> { - return new SearchAssistantPlugin(context); -} +> = (context: PluginInitializerContext) => new SearchAssistantPlugin(context); + export type { SearchAssistantPluginSetup, SearchAssistantPluginStart } from './types'; From 130a5e0c1b7b3fd414b2ce38c6af162d084fb7e0 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Wed, 2 Oct 2024 11:08:55 +0000 Subject: [PATCH 18/23] [CI] Auto-commit changed files from 'node scripts/lint_ts_projects --fix' --- x-pack/plugins/search_assistant/tsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/search_assistant/tsconfig.json b/x-pack/plugins/search_assistant/tsconfig.json index 9a43830cadc7f..d865d2bdbff83 100644 --- a/x-pack/plugins/search_assistant/tsconfig.json +++ b/x-pack/plugins/search_assistant/tsconfig.json @@ -21,8 +21,8 @@ "@kbn/observability-ai-assistant-plugin", "@kbn/config-schema", "@kbn/ai-assistant", - "@kbn/typed-react-router-config", - "@kbn/i18n" + "@kbn/i18n", + "@kbn/shared-ux-router" ], "exclude": [ "target/**/*", From aa2c644de4a634fdaeb462516c90b2b94c58cf50 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Wed, 2 Oct 2024 15:01:44 +0200 Subject: [PATCH 19/23] Remove commented out function --- .../kbn-ai-assistant/src/chat/chat_header.tsx | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx index 78d2a13156c48..c9f0588a1c90f 100644 --- a/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/chat/chat_header.tsx @@ -71,19 +71,6 @@ export function ChatHeader({ setNewTitle(title); }, [title]); - // const handleNavigateToConversations = () => { - // if (conversationId) { - // router.push('/conversations/{conversationId}', { - // path: { - // conversationId, - // }, - // query: {}, - // }); - // } else { - // router.push('/conversations/new', { path: {}, query: {} }); - // } - // }; - const handleToggleFlyoutPositionMode = () => { if (flyoutPositionMode) { onToggleFlyoutPositionMode?.( From 911c57e4768d24833c236d942d6c860f4fed41f9 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 8 Oct 2024 16:15:36 +0200 Subject: [PATCH 20/23] Remove context and revert useOnce --- .../context/ai_assistant_app_service_provider.tsx | 15 --------------- .../src/hooks/use_ai_assistant_app_service.ts | 12 ++++++------ .../src/hooks/use_conversation.ts | 2 +- .../kbn-ai-assistant/src/hooks/use_once.ts | 4 ---- x-pack/packages/kbn-ai-assistant/src/index.ts | 1 - .../public/hooks/use_once.ts | 3 --- .../public/utils/shared_providers.tsx | 8 ++------ .../search_assistant/public/application.tsx | 5 +---- 8 files changed, 10 insertions(+), 40 deletions(-) delete mode 100644 x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx diff --git a/x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx b/x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx deleted file mode 100644 index 7f4f629fbf719..0000000000000 --- a/x-pack/packages/kbn-ai-assistant/src/context/ai_assistant_app_service_provider.tsx +++ /dev/null @@ -1,15 +0,0 @@ -/* - * 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 { createContext } from 'react'; -import type { AIAssistantAppService } from '../service/create_app_service'; - -export const AIAssistantAppServiceContext = createContext( - undefined -); - -export const AIAssistantAppServiceProvider = AIAssistantAppServiceContext.Provider; diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts index ba28c3c05b8d9..9006ff034919d 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts @@ -4,17 +4,17 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { useContext } from 'react'; -import { AIAssistantAppServiceContext } from '../context/ai_assistant_app_service_provider'; + +import { useKibana } from './use_kibana'; export function useAIAssistantAppService() { - const services = useContext(AIAssistantAppServiceContext); + const { services } = useKibana(); - if (!services) { + if (!services.observabilityAIAssistant?.service) { throw new Error( - 'AIAssistantAppServiceContext not set. Did you wrap your component in `AIAssistantAppServiceProvider`?' + 'AI Assistant Service is not available. Did you provide this component in your plugin contract?' ); } - return services; + return services.observabilityAIAssistant.service; } diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts index a937051f65d7d..484183b6928e5 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts @@ -70,7 +70,7 @@ export function useConversation({ }, } = useKibana(); - const initialConversationId = initialConversationIdFromProps; + const initialConversationId = useOnce(initialConversationIdFromProps); const initialMessages = useOnce(initialMessagesFromProps); const initialTitle = useOnce(initialTitleFromProps); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts index 3ae1eb58b32b6..00dab01456af0 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_once.ts @@ -10,10 +10,6 @@ import { useRef } from 'react'; export function useOnce(variable: T): T { const ref = useRef(variable); - if (!ref.current && variable) { - ref.current = variable; - } - if (ref.current !== variable) { // eslint-disable-next-line no-console console.trace( diff --git a/x-pack/packages/kbn-ai-assistant/src/index.ts b/x-pack/packages/kbn-ai-assistant/src/index.ts index 48dc54d98e267..ba2265e88715f 100644 --- a/x-pack/packages/kbn-ai-assistant/src/index.ts +++ b/x-pack/packages/kbn-ai-assistant/src/index.ts @@ -6,7 +6,6 @@ */ export * from './conversation/conversation_view'; -export * from './context/ai_assistant_app_service_provider'; export * from './service/create_app_service'; export * from './hooks'; export * from './chat'; diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts index 8ed8c861dfbba..00dab01456af0 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts +++ b/x-pack/plugins/observability_solution/observability_ai_assistant/public/hooks/use_once.ts @@ -9,9 +9,6 @@ import { useRef } from 'react'; export function useOnce(variable: T): T { const ref = useRef(variable); - if (!ref.current && variable) { - ref.current = variable; - } if (ref.current !== variable) { // eslint-disable-next-line no-console diff --git a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx index b69b6a7e898f5..49776f4622250 100644 --- a/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx +++ b/x-pack/plugins/observability_solution/observability_ai_assistant_app/public/utils/shared_providers.tsx @@ -11,7 +11,7 @@ import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme'; import { RedirectAppLinks } from '@kbn/shared-ux-link-redirect-app'; import React, { useMemo } from 'react'; import type { Observable } from 'rxjs'; -import { AIAssistantAppServiceProvider, AIAssistantAppService } from '@kbn/ai-assistant'; +import { AIAssistantAppService } from '@kbn/ai-assistant'; import type { ObservabilityAIAssistantAppPluginStartDependencies } from '../types'; export function SharedProviders({ @@ -44,11 +44,7 @@ export function SharedProviders({ }} > - - - {children} - - + {children} diff --git a/x-pack/plugins/search_assistant/public/application.tsx b/x-pack/plugins/search_assistant/public/application.tsx index a86954d51f0c8..1bbf7063ec373 100644 --- a/x-pack/plugins/search_assistant/public/application.tsx +++ b/x-pack/plugins/search_assistant/public/application.tsx @@ -11,7 +11,6 @@ import type { AppMountParameters, CoreStart } from '@kbn/core/public'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; import { I18nProvider } from '@kbn/i18n-react'; -import { AIAssistantAppServiceContext } from '@kbn/ai-assistant'; import type { SearchAssistantPluginStartDependencies } from './types'; import { SearchAssistantRouter } from './components/routes/router'; @@ -24,9 +23,7 @@ export const renderApp = ( - - - + , From a48c5b904166add9b3ec9a7c7c85b363fc093d82 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 8 Oct 2024 18:26:51 +0200 Subject: [PATCH 21/23] Fix i18n --- .../translations/translations/fr-FR.json | 88 +++++++++++++++++++ .../translations/translations/ja-JP.json | 88 +++++++++++++++++++ .../translations/translations/zh-CN.json | 88 +++++++++++++++++++ 3 files changed, 264 insertions(+) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index d1191cda63aee..d244a29900c9c 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -9387,6 +9387,94 @@ "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "Les actions sont indisponibles - les informations de licence ne sont pas disponibles actuellement.", "xpack.actions.subActionsFramework.urlValidationError": "Erreur lors de la validation de l'URL : {message}", "xpack.actions.urlAllowedHostsConfigurationError": "Le {field} cible \"{value}\" n'est pas ajouté à la configuration Kibana xpack.actions.allowedHosts", + "xpack.aiAssistant.askAssistantButton.buttonLabel": "Demander à l'assistant", + "xpack.aiAssistant.askAssistantButton.popoverContent": "Obtenez des informations relatives à vos données grâce à l'assistant d'Elastic", + "xpack.aiAssistant.assistantSetup.title": "Bienvenue sur l'assistant d'intelligence artificielle d'Elastic", + "xpack.aiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "Menu", + "xpack.aiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "Plus d'actions", + "xpack.aiAssistant.chatCollapsedItems.hideEvents": "Masquer {count} événements", + "xpack.aiAssistant.chatCollapsedItems.showEvents": "Montrer {count} événements", + "xpack.aiAssistant.chatCollapsedItems.toggleButtonLabel": "Afficher/masquer les éléments", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "Développer la liste des conversations", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "Nouveau chat", + "xpack.aiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "Réduire la liste des conversations", + "xpack.aiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "Développer la liste des conversations", + "xpack.aiAssistant.chatFlyout.euiToolTip.newChatLabel": "Nouveau chat", + "xpack.aiAssistant.chatHeader.actions.connector": "Connecteur", + "xpack.aiAssistant.chatHeader.actions.copyConversation": "Copier la conversation", + "xpack.aiAssistant.chatHeader.actions.knowledgeBase": "Gérer la base de connaissances", + "xpack.aiAssistant.chatHeader.actions.settings": "Réglages de l'assistant d'IA", + "xpack.aiAssistant.chatHeader.actions.title": "Actions", + "xpack.aiAssistant.chatHeader.editConversationInput": "Modifier la conversation", + "xpack.aiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "Accéder aux conversations", + "xpack.aiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "Afficher / Masquer le mode menu volant", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "Ancrer le chat", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "Désancrer le chat", + "xpack.aiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "Accéder aux conversations", + "xpack.aiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", + "xpack.aiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "Envoyer", + "xpack.aiAssistant.chatTimeline.actions.copyMessage": "Copier le message", + "xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful": "Message copié", + "xpack.aiAssistant.chatTimeline.actions.editPrompt": "Modifier l'invite", + "xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "Inspecter l'invite", + "xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Assistant d'Elastic", + "xpack.aiAssistant.chatTimeline.messages.system.label": "Système", + "xpack.aiAssistant.chatTimeline.messages.user.label": "Vous", + "xpack.aiAssistant.checkingKbAvailability": "Vérification de la disponibilité de la base de connaissances", + "xpack.aiAssistant.conversationStartTitle": "a démarré une conversation", + "xpack.aiAssistant.couldNotFindConversationContent": "Impossible de trouver une conversation avec l'ID {conversationId}. Assurez-vous que la conversation existe et que vous y avez accès.", + "xpack.aiAssistant.couldNotFindConversationTitle": "Conversation introuvable", + "xpack.aiAssistant.disclaimer.disclaimerLabel": "Ce chat est soutenu par une intégration avec votre fournisseur LLM. Il arrive que les grands modèles de langage (LLM) présentent comme correctes des informations incorrectes. Elastic prend en charge la configuration ainsi que la connexion au fournisseur LLM et à votre base de connaissances, mais n'est pas responsable des réponses fournies par le LLM.", + "xpack.aiAssistant.emptyConversationTitle": "Nouvelle conversation", + "xpack.aiAssistant.errorSettingUpKnowledgeBase": "Impossible de configurer la base de connaissances", + "xpack.aiAssistant.errorUpdatingConversation": "Impossible de mettre à jour la conversation", + "xpack.aiAssistant.executedFunctionFailureEvent": "impossible d'exécuter la fonction {functionName}", + "xpack.aiAssistant.failedToGetStatus": "Échec de l'obtention du statut du modèle.", + "xpack.aiAssistant.failedToSetupKnowledgeBase": "Échec de la configuration de la base de connaissances.", + "xpack.aiAssistant.flyout.confirmDeleteButtonText": "Supprimer la conversation", + "xpack.aiAssistant.flyout.confirmDeleteConversationContent": "Cette action ne peut pas être annulée.", + "xpack.aiAssistant.flyout.confirmDeleteConversationTitle": "Supprimer cette conversation ?", + "xpack.aiAssistant.flyout.failedToDeleteConversation": "Impossible de supprimer la conversation", + "xpack.aiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "Sélectionner la fonction", + "xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction": "Effacer la fonction", + "xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "Sélectionner une fonction", + "xpack.aiAssistant.hideExpandConversationButton.hide": "Masquer les chats", + "xpack.aiAssistant.hideExpandConversationButton.show": "Afficher les chats", + "xpack.aiAssistant.incorrectLicense.body": "Une licence d'entreprise est requise pour utiliser l'assistant d'intelligence artificielle d'Elastic.", + "xpack.aiAssistant.incorrectLicense.manageLicense": "Gérer la licence", + "xpack.aiAssistant.incorrectLicense.subscriptionPlansButton": "Plans d'abonnement", + "xpack.aiAssistant.incorrectLicense.title": "Mettez votre licence à niveau", + "xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel": "Configurer un connecteur GenAI", + "xpack.aiAssistant.initialSetupPanel.setupConnector.description2": "Commencez à travailler avec l'assistant AI Elastic en configurant un connecteur pour votre fournisseur d'IA. Le modèle doit prendre en charge les appels de fonction. Lorsque vous utilisez OpenAI ou Azure, nous vous recommandons d'utiliser GPT4.", + "xpack.aiAssistant.installingKb": "Configuration de la base de connaissances", + "xpack.aiAssistant.newChatButton": "Nouveau chat", + "xpack.aiAssistant.poweredByModel": "Alimenté par {model}", + "xpack.aiAssistant.prompt.functionList.filter": "Filtre", + "xpack.aiAssistant.prompt.functionList.functionList": "Liste de fonctions", + "xpack.aiAssistant.prompt.placeholder": "Envoyer un message à l'assistant", + "xpack.aiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "Sélectionner une option", + "xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel": "Gérer les connecteurs", + "xpack.aiAssistant.setupKb": "Améliorez votre expérience en configurant la base de connaissances.", + "xpack.aiAssistant.simulatedFunctionCallingCalloutLabel": "L'appel de fonctions simulées est activé. Vous risquez de voir les performances se dégrader.", + "xpack.aiAssistant.suggestedFunctionEvent": "a demandé la fonction {functionName}", + "xpack.aiAssistant.technicalPreviewBadgeDescription": "GTP4 est nécessaire pour bénéficier d'une meilleure expérience avec les appels de fonctions (par exemple lors de la réalisation d'analyse de la cause d'un problème, de la visualisation de données et autres). GPT3.5 peut fonctionner pour certains des workflows les plus simples comme les explications d'erreurs ou pour bénéficier d'une expérience comparable à ChatGPT au sein de Kibana à partir du moment où les appels de fonctions ne sont pas fréquents.", + "xpack.aiAssistant.userExecutedFunctionEvent": "a exécuté la fonction {functionName}", + "xpack.aiAssistant.userSuggestedFunctionEvent": "a demandé la fonction {functionName}", + "xpack.aiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} ou vérifiez {trainedModelsLink} pour vous assurer que {modelName} est déployé et en cours d'exécution.", + "xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "Configuration de la base de connaissances", + "xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "Inspecter les problèmes", + "xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "Problèmes", + "xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "La base de connaissances a été installée avec succès", + "xpack.aiAssistant.welcomeMessage.modelIsNotDeployedLabel": "Le modèle {modelName} n'est pas déployé", + "xpack.aiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "L'état d'allocation de {modelName} est {allocationState}", + "xpack.aiAssistant.welcomeMessage.modelIsNotStartedLabel": "L'état de déploiement de {modelName} est {deploymentState}", + "xpack.aiAssistant.welcomeMessage.retryButtonLabel": "Installer la base de connaissances", + "xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel": "Modèles entraînés", + "xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel": "Nous configurons votre base de connaissances. Cette opération peut prendre quelques minutes. Vous pouvez continuer à utiliser l'Assistant lors de ce processus.", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "Impossible de charger les connecteurs", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "Vous n'avez pas les autorisations requises pour charger les connecteurs", + "xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "Votre base de connaissances n'a pas été configurée.", + "xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "Réessayer l'installation", "xpack.aiops.actions.openChangePointInMlAppName": "Ouvrir dans AIOps Labs", "xpack.aiops.analysis.columnSelectorAriaLabel": "Filtrer les colonnes", "xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected": "Au moins une colonne doit être sélectionnée.", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9cbf3d3af14b8..267c8de3798e6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -9141,6 +9141,94 @@ "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "グラフを利用できません。現在ライセンス情報が利用できません。", "xpack.actions.subActionsFramework.urlValidationError": "URLの検証エラー:{message}", "xpack.actions.urlAllowedHostsConfigurationError": "ターゲット{field}「{value}」はKibana構成xpack.actions.allowedHostsに追加されていません", + "xpack.aiAssistant.askAssistantButton.buttonLabel": "アシスタントに聞く", + "xpack.aiAssistant.askAssistantButton.popoverContent": "Elastic Assistantでデータに関するインサイトを得ましょう", + "xpack.aiAssistant.assistantSetup.title": "Elastic AI Assistantへようこそ", + "xpack.aiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "メニュー", + "xpack.aiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "さらにアクションを表示", + "xpack.aiAssistant.chatCollapsedItems.hideEvents": "{count}件のイベントを非表示", + "xpack.aiAssistant.chatCollapsedItems.showEvents": "{count}件のイベントを表示", + "xpack.aiAssistant.chatCollapsedItems.toggleButtonLabel": "アイテムを表示/非表示", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "会話リストを展開", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新しいチャット", + "xpack.aiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "会話リストを折りたたむ", + "xpack.aiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "会話リストを展開", + "xpack.aiAssistant.chatFlyout.euiToolTip.newChatLabel": "新しいチャット", + "xpack.aiAssistant.chatHeader.actions.connector": "コネクター", + "xpack.aiAssistant.chatHeader.actions.copyConversation": "会話をコピー", + "xpack.aiAssistant.chatHeader.actions.knowledgeBase": "ナレッジベースを管理", + "xpack.aiAssistant.chatHeader.actions.settings": "AI Assistant設定", + "xpack.aiAssistant.chatHeader.actions.title": "アクション", + "xpack.aiAssistant.chatHeader.editConversationInput": "会話を編集", + "xpack.aiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "会話に移動", + "xpack.aiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "フライアウトモードを切り替え", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "チャットを固定", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "チャットを固定解除", + "xpack.aiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "会話に移動", + "xpack.aiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", + "xpack.aiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "送信", + "xpack.aiAssistant.chatTimeline.actions.copyMessage": "メッセージをコピー", + "xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful": "コピーされたメッセージ", + "xpack.aiAssistant.chatTimeline.actions.editPrompt": "プロンプトを編集", + "xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "プロンプトを検査", + "xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic Assistant", + "xpack.aiAssistant.chatTimeline.messages.system.label": "システム", + "xpack.aiAssistant.chatTimeline.messages.user.label": "あなた", + "xpack.aiAssistant.checkingKbAvailability": "ナレッジベースの利用可能性を確認中", + "xpack.aiAssistant.conversationStartTitle": "会話を開始しました", + "xpack.aiAssistant.couldNotFindConversationContent": "id {conversationId}の会話が見つかりませんでした。会話が存在し、それにアクセスできることを確認してください。", + "xpack.aiAssistant.couldNotFindConversationTitle": "会話が見つかりません", + "xpack.aiAssistant.disclaimer.disclaimerLabel": "このチャットは、LLMプロバイダーとの統合によって提供されています。LLMは、正しくない情報を正しい情報であるかのように表示する場合があることが知られています。Elasticは、構成やLLMプロバイダーへの接続、お客様のナレッジベースへの接続はサポートしますが、LLMの応答については責任を負いません。", + "xpack.aiAssistant.emptyConversationTitle": "新しい会話", + "xpack.aiAssistant.errorSettingUpKnowledgeBase": "ナレッジベースをセットアップできませんでした", + "xpack.aiAssistant.errorUpdatingConversation": "会話を更新できませんでした", + "xpack.aiAssistant.executedFunctionFailureEvent": "関数{functionName}の実行に失敗しました", + "xpack.aiAssistant.failedToGetStatus": "モデルステータスを取得できませんでした。", + "xpack.aiAssistant.failedToSetupKnowledgeBase": "ナレッジベースをセットアップできませんでした。", + "xpack.aiAssistant.flyout.confirmDeleteButtonText": "会話を削除", + "xpack.aiAssistant.flyout.confirmDeleteConversationContent": "この操作は元に戻すことができません。", + "xpack.aiAssistant.flyout.confirmDeleteConversationTitle": "この会話を削除しますか?", + "xpack.aiAssistant.flyout.failedToDeleteConversation": "会話を削除できませんでした", + "xpack.aiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "関数を選択", + "xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction": "関数を消去", + "xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "関数を選択", + "xpack.aiAssistant.hideExpandConversationButton.hide": "チャットを非表示", + "xpack.aiAssistant.hideExpandConversationButton.show": "チャットを表示", + "xpack.aiAssistant.incorrectLicense.body": "Elastic AI Assistantを使用するにはEnterpriseライセンスが必要です。", + "xpack.aiAssistant.incorrectLicense.manageLicense": "ライセンスの管理", + "xpack.aiAssistant.incorrectLicense.subscriptionPlansButton": "サブスクリプションオプション", + "xpack.aiAssistant.incorrectLicense.title": "ライセンスをアップグレード", + "xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel": "GenAIコネクターをセットアップ", + "xpack.aiAssistant.initialSetupPanel.setupConnector.description2": "Elastic AI Assistantの使用を開始するには、AIプロバイダーのコネクターを設定します。モデルは関数呼び出しをサポートしている必要があります。OpenAIまたはAzureを使用するときには、GPT4を使用することをお勧めします。", + "xpack.aiAssistant.installingKb": "ナレッジベースをセットアップ中", + "xpack.aiAssistant.newChatButton": "新しいチャット", + "xpack.aiAssistant.poweredByModel": "{model}で駆動", + "xpack.aiAssistant.prompt.functionList.filter": "フィルター", + "xpack.aiAssistant.prompt.functionList.functionList": "関数リスト", + "xpack.aiAssistant.prompt.placeholder": "アシスタントにメッセージを送信", + "xpack.aiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "オプションを選択", + "xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel": "コネクターを管理", + "xpack.aiAssistant.setupKb": "ナレッジベースを設定することで、エクスペリエンスが改善されます。", + "xpack.aiAssistant.simulatedFunctionCallingCalloutLabel": "シミュレートされた関数呼び出しが有効です。パフォーマンスが劣化する場合があります。", + "xpack.aiAssistant.suggestedFunctionEvent": "関数{functionName}を要求しました", + "xpack.aiAssistant.technicalPreviewBadgeDescription": "関数呼び出し(根本原因分析やデータの視覚化など)を使用する際に、より一貫性のあるエクスペリエンスを実現するために、GPT4が必要です。GPT3.5は、エラーの説明などのシンプルなワークフローの一部や、頻繁な関数呼び出しの使用が必要とされないKibana内のエクスペリエンスなどのChatGPTで機能します。", + "xpack.aiAssistant.userExecutedFunctionEvent": "関数{functionName}を実行しました", + "xpack.aiAssistant.userSuggestedFunctionEvent": "関数{functionName}を要求しました", + "xpack.aiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink}か、{trainedModelsLink}を確認して、{modelName}がデプロイされ、実行中であることを確かめてください。", + "xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "ナレッジベースをセットアップ中", + "xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "問題を検査", + "xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "問題", + "xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "ナレッジベースは正常にインストールされました", + "xpack.aiAssistant.welcomeMessage.modelIsNotDeployedLabel": "モデル\"{modelName}\"はデプロイされていません", + "xpack.aiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "\"{modelName}\"の割り当て状態は{allocationState}です", + "xpack.aiAssistant.welcomeMessage.modelIsNotStartedLabel": "\"{modelName}\"のデプロイ状態は{deploymentState}です", + "xpack.aiAssistant.welcomeMessage.retryButtonLabel": "ナレッジベースをインストール", + "xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel": "学習済みモデル", + "xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel": "ナレッジベースをセットアップしています。これには数分かかる場合があります。この処理の実行中には、アシスタントを使用し続けることができます。", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "コネクターを読み込めませんでした", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "コネクターを取得するために必要な権限が不足しています", + "xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "ナレッジベースはセットアップされていません。", + "xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "インストールを再試行", "xpack.aiops.actions.openChangePointInMlAppName": "AIOps Labsで開く", "xpack.aiops.analysis.columnSelectorAriaLabel": "列のフィルタリング", "xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected": "1つ以上の列を選択する必要があります。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index e1619b4adeb87..a648f0e401ab0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -9158,6 +9158,94 @@ "xpack.actions.serverSideErrors.unavailableLicenseInformationErrorMessage": "操作不可用 - 许可信息当前不可用。", "xpack.actions.subActionsFramework.urlValidationError": "验证 URL 时出错:{message}", "xpack.actions.urlAllowedHostsConfigurationError": "目标 {field} 的“{value}”未添加到 Kibana 配置 xpack.actions.allowedHosts", + "xpack.aiAssistant.askAssistantButton.buttonLabel": "询问助手", + "xpack.aiAssistant.askAssistantButton.popoverContent": "使用 Elastic 助手深入了解您的数据", + "xpack.aiAssistant.assistantSetup.title": "欢迎使用 Elastic AI 助手", + "xpack.aiAssistant.chatActionsMenu.euiButtonIcon.menuLabel": "菜单", + "xpack.aiAssistant.chatActionsMenu.euiToolTip.moreActionsLabel": "更多操作", + "xpack.aiAssistant.chatCollapsedItems.hideEvents": "隐藏 {count} 个事件", + "xpack.aiAssistant.chatCollapsedItems.showEvents": "显示 {count} 个事件", + "xpack.aiAssistant.chatCollapsedItems.toggleButtonLabel": "显示/隐藏项目", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.expandConversationListLabel": "展开对话列表", + "xpack.aiAssistant.chatFlyout.euiButtonIcon.newChatLabel": "新聊天", + "xpack.aiAssistant.chatFlyout.euiToolTip.collapseConversationListLabel": "折叠对话列表", + "xpack.aiAssistant.chatFlyout.euiToolTip.expandConversationListLabel": "展开对话列表", + "xpack.aiAssistant.chatFlyout.euiToolTip.newChatLabel": "新聊天", + "xpack.aiAssistant.chatHeader.actions.connector": "连接器", + "xpack.aiAssistant.chatHeader.actions.copyConversation": "复制对话", + "xpack.aiAssistant.chatHeader.actions.knowledgeBase": "管理知识库", + "xpack.aiAssistant.chatHeader.actions.settings": "AI 助手设置", + "xpack.aiAssistant.chatHeader.actions.title": "操作", + "xpack.aiAssistant.chatHeader.editConversationInput": "编辑对话", + "xpack.aiAssistant.chatHeader.euiButtonIcon.navigateToConversationsLabel": "导航到对话", + "xpack.aiAssistant.chatHeader.euiButtonIcon.toggleFlyoutModeLabel": "切换浮出控件模式", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.dock": "停靠聊天", + "xpack.aiAssistant.chatHeader.euiToolTip.flyoutModeLabel.undock": "取消停靠聊天", + "xpack.aiAssistant.chatHeader.euiToolTip.navigateToConversationsLabel": "导航到对话", + "xpack.aiAssistant.chatPromptEditor.codeEditor.payloadEditorLabel": "payloadEditor", + "xpack.aiAssistant.chatPromptEditor.euiButtonIcon.submitLabel": "提交", + "xpack.aiAssistant.chatTimeline.actions.copyMessage": "复制消息", + "xpack.aiAssistant.chatTimeline.actions.copyMessageSuccessful": "已复制消息", + "xpack.aiAssistant.chatTimeline.actions.editPrompt": "编辑提示", + "xpack.aiAssistant.chatTimeline.actions.inspectPrompt": "检查提示", + "xpack.aiAssistant.chatTimeline.messages.elasticAssistant.label": "Elastic 助手", + "xpack.aiAssistant.chatTimeline.messages.system.label": "系统", + "xpack.aiAssistant.chatTimeline.messages.user.label": "您", + "xpack.aiAssistant.checkingKbAvailability": "正在检查知识库的可用性", + "xpack.aiAssistant.conversationStartTitle": "已开始对话", + "xpack.aiAssistant.couldNotFindConversationContent": "找不到 ID 为 {conversationId} 的对话。请确保该对话存在并且您具有访问权限。", + "xpack.aiAssistant.couldNotFindConversationTitle": "未找到对话", + "xpack.aiAssistant.disclaimer.disclaimerLabel": "通过集成 LLM 提供商来支持此聊天。众所周知,LLM 有时会提供错误信息,好像它是正确的。Elastic 支持配置并连接到 LLM 提供商和知识库,但不对 LLM 响应负责。", + "xpack.aiAssistant.emptyConversationTitle": "新对话", + "xpack.aiAssistant.errorSettingUpKnowledgeBase": "无法设置知识库", + "xpack.aiAssistant.errorUpdatingConversation": "无法更新对话", + "xpack.aiAssistant.executedFunctionFailureEvent": "无法执行函数 {functionName}", + "xpack.aiAssistant.failedToGetStatus": "无法获取模型状态。", + "xpack.aiAssistant.failedToSetupKnowledgeBase": "无法设置知识库。", + "xpack.aiAssistant.flyout.confirmDeleteButtonText": "删除对话", + "xpack.aiAssistant.flyout.confirmDeleteConversationContent": "此操作无法撤消。", + "xpack.aiAssistant.flyout.confirmDeleteConversationTitle": "删除此对话?", + "xpack.aiAssistant.flyout.failedToDeleteConversation": "无法删除对话", + "xpack.aiAssistant.functionListPopover.euiButtonIcon.selectAFunctionLabel": "选择函数", + "xpack.aiAssistant.functionListPopover.euiToolTip.clearFunction": "清除函数", + "xpack.aiAssistant.functionListPopover.euiToolTip.selectAFunctionLabel": "选择函数", + "xpack.aiAssistant.hideExpandConversationButton.hide": "隐藏聊天", + "xpack.aiAssistant.hideExpandConversationButton.show": "显示聊天", + "xpack.aiAssistant.incorrectLicense.body": "您需要企业级许可证才能使用 Elastic AI 助手。", + "xpack.aiAssistant.incorrectLicense.manageLicense": "管理许可证", + "xpack.aiAssistant.incorrectLicense.subscriptionPlansButton": "订阅计划", + "xpack.aiAssistant.incorrectLicense.title": "升级您的许可证", + "xpack.aiAssistant.initialSetupPanel.setupConnector.buttonLabel": "设置 GenAI 连接器", + "xpack.aiAssistant.initialSetupPanel.setupConnector.description2": "通过为您的 AI 提供商设置连接器,开始使用 Elastic AI 助手。此模型需要支持函数调用。使用 OpenAI 或 Azure 时,建议使用 GPT4。", + "xpack.aiAssistant.installingKb": "正在设置知识库", + "xpack.aiAssistant.newChatButton": "新聊天", + "xpack.aiAssistant.poweredByModel": "由 {model} 提供支持", + "xpack.aiAssistant.prompt.functionList.filter": "筛选", + "xpack.aiAssistant.prompt.functionList.functionList": "函数列表", + "xpack.aiAssistant.prompt.placeholder": "向助手发送消息", + "xpack.aiAssistant.promptEditorNaturalLanguage.euiSelectable.selectAnOptionLabel": "选择选项", + "xpack.aiAssistant.settingsPage.goToConnectorsButtonLabel": "管理连接器", + "xpack.aiAssistant.setupKb": "通过设置知识库来改进体验。", + "xpack.aiAssistant.simulatedFunctionCallingCalloutLabel": "模拟函数调用已启用。您可能会面临性能降级。", + "xpack.aiAssistant.suggestedFunctionEvent": "已请求函数 {functionName}", + "xpack.aiAssistant.technicalPreviewBadgeDescription": "需要 GPT4 以在使用函数调用时(例如,执行根本原因分析、数据可视化等时候)获得更加一致的体验。GPT3.5 可作用于某些更简单的工作流(如解释错误),或在 Kibana 中获得不需要频繁使用函数调用的与 ChatGPT 类似的体验。", + "xpack.aiAssistant.userExecutedFunctionEvent": "已执行函数 {functionName}", + "xpack.aiAssistant.userSuggestedFunctionEvent": "已请求函数 {functionName}", + "xpack.aiAssistant.welcomeMessage.div.checkTrainedModelsToLabel": " {retryInstallingLink} 或检查 {trainedModelsLink},确保 {modelName} 已部署并正在运行。", + "xpack.aiAssistant.welcomeMessage.div.settingUpKnowledgeBaseLabel": "正在设置知识库", + "xpack.aiAssistant.welcomeMessage.inspectErrorsButtonEmptyLabel": "检查问题", + "xpack.aiAssistant.welcomeMessage.issuesDescriptionListTitleLabel": "问题", + "xpack.aiAssistant.welcomeMessage.knowledgeBaseSuccessfullyInstalledLabel": "已成功安装知识库", + "xpack.aiAssistant.welcomeMessage.modelIsNotDeployedLabel": "未部署模型 {modelName}", + "xpack.aiAssistant.welcomeMessage.modelIsNotFullyAllocatedLabel": "{modelName} 的分配状态为 {allocationState}", + "xpack.aiAssistant.welcomeMessage.modelIsNotStartedLabel": "{modelName} 的部署状态为 {deploymentState}", + "xpack.aiAssistant.welcomeMessage.retryButtonLabel": "安装知识库", + "xpack.aiAssistant.welcomeMessage.trainedModelsLinkLabel": "已训练模型", + "xpack.aiAssistant.welcomeMessage.weAreSettingUpTextLabel": "我们正在设置您的知识库。这可能需要若干分钟。此进程处于运行状态时,您可以继续使用该助手。", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsErrorTextLabel": "无法加载连接器", + "xpack.aiAssistant.welcomeMessageConnectors.connectorsForbiddenTextLabel": "缺少获取连接器所需的权限", + "xpack.aiAssistant.welcomeMessageKnowledgeBase.yourKnowledgeBaseIsNotSetUpCorrectlyLabel": "尚未设置您的知识库。", + "xpack.aiAssistant.welcomeMessageKnowledgeBaseSetupErrorPanel.retryInstallingLinkLabel": "重试安装", "xpack.aiops.actions.openChangePointInMlAppName": "在 Aiops 实验室中打开", "xpack.aiops.analysis.columnSelectorAriaLabel": "筛选列", "xpack.aiops.analysis.columnSelectorNotEnoughColumnsSelected": "必须至少选择一列。", From 7e03c295b42132f78781f2cf809ef54c5d11b759 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Tue, 8 Oct 2024 18:31:34 +0200 Subject: [PATCH 22/23] Fix missing provider type --- .../src/hooks/use_ai_assistant_app_service.ts | 2 +- .../src/hooks/use_conversation.test.tsx | 8 ++------ .../src/utils/storybook_decorator.stories.tsx | 10 ++++------ 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts index 9006ff034919d..bb1f93079eb09 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_ai_assistant_app_service.ts @@ -12,7 +12,7 @@ export function useAIAssistantAppService() { if (!services.observabilityAIAssistant?.service) { throw new Error( - 'AI Assistant Service is not available. Did you provide this component in your plugin contract?' + 'AI Assistant Service is not available. Did you provide this service in your plugin contract?' ); } diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx index 7edc789a0994c..4c4ced36c8796 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.test.tsx @@ -19,7 +19,6 @@ import { StreamingChatResponseEventType, StreamingChatResponseEventWithoutError, } from '@kbn/observability-ai-assistant-plugin/common'; -import { AIAssistantAppServiceProvider } from '../context/ai_assistant_app_service_provider'; import { EMPTY_CONVERSATION_TITLE } from '../i18n'; import type { AIAssistantAppService } from '../service/create_app_service'; import { @@ -74,6 +73,7 @@ const useKibanaMockServices = { }, } as unknown as NotificationsStart, }), + service: mockService, }, }; @@ -83,11 +83,7 @@ describe('useConversation', () => { beforeEach(() => { jest.clearAllMocks(); wrapper = ({ children }: PropsWithChildren) => ( - - - {children} - - + {children} ); }); diff --git a/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx b/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx index 8fb328eb8e972..d6292803b42af 100644 --- a/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/utils/storybook_decorator.stories.tsx @@ -13,7 +13,6 @@ import { } from '@kbn/observability-ai-assistant-plugin/public'; import { Subject } from 'rxjs'; import { coreMock } from '@kbn/core/public/mocks'; -import { AIAssistantAppServiceProvider } from '../context/ai_assistant_app_service_provider'; import { AIAssistantAppService } from '../service/create_app_service'; const mockService: AIAssistantAppService = { @@ -41,15 +40,14 @@ export function KibanaReactStorybookDecorator(Story: ComponentType) { observabilityAIAssistant: { ObservabilityAIAssistantChatServiceContext, ObservabilityAIAssistantMultipaneFlyoutContext, + service: mockService, }, triggersActionsUi: { getAddRuleFlyout: {}, getAddConnectorFlyout: {} }, }} > - - - - - + + + ); } From cbbb6cca64a9c458522c10a91764446ce4505825 Mon Sep 17 00:00:00 2001 From: Sander Philipse Date: Thu, 10 Oct 2024 11:26:42 +0200 Subject: [PATCH 23/23] Replace \? with ! --- x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts | 2 +- .../kbn-ai-assistant/src/hooks/use_conversation_list.ts | 2 +- x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts | 2 +- .../packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx | 2 +- .../src/hooks/use_simulated_function_calling.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts index 484183b6928e5..744e071d5b1ba 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation.ts @@ -100,7 +100,7 @@ export function useConversation({ }, }) .catch((err) => { - notifications?.toasts.addError(err, { + notifications!.toasts.addError(err, { title: i18n.translate('xpack.aiAssistant.errorUpdatingConversation', { defaultMessage: 'Could not update conversation', }), diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts index d6ba75568dfd1..d0db7665a30b6 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_conversation_list.ts @@ -61,7 +61,7 @@ export function useConversationList(): UseConversationListResult { conversations.refresh(); } catch (err) { - notifications?.toasts.addError(err, { + notifications!.toasts.addError(err, { title: i18n.translate('xpack.aiAssistant.flyout.failedToDeleteConversation', { defaultMessage: 'Could not delete conversation', }), diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts index 6721ee0360cbd..c169358653a49 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_current_user.ts @@ -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); diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx b/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx index f5410ed671b95..0b949fcdbff0e 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_knowledge_base.tsx @@ -70,7 +70,7 @@ export function useKnowledgeBase(): UseKnowledgeBaseResult { return install(); } setInstallError(error); - notifications?.toasts.addError(error, { + notifications!.toasts.addError(error, { title: i18n.translate('xpack.aiAssistant.errorSettingUpKnowledgeBase', { defaultMessage: 'Could not set up Knowledge Base', }), diff --git a/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts b/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts index 93287fc58aa79..4515f2126dbfd 100644 --- a/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts +++ b/x-pack/packages/kbn-ai-assistant/src/hooks/use_simulated_function_calling.ts @@ -13,7 +13,7 @@ export function useSimulatedFunctionCalling() { services: { uiSettings }, } = useKibana(); - const simulatedFunctionCallingEnabled = uiSettings?.get( + const simulatedFunctionCallingEnabled = uiSettings!.get( aiAssistantSimulatedFunctionCalling, false );