diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 5f2e9ed4992d4..4d7cc07659dde 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -717,6 +717,7 @@ x-pack/plugins/search_connectors @elastic/search-kibana
packages/kbn-search-errors @elastic/kibana-data-discovery
examples/search_examples @elastic/kibana-data-discovery
packages/kbn-search-index-documents @elastic/search-kibana
+x-pack/plugins/search_inference_endpoints @elastic/search-kibana
x-pack/plugins/search_notebooks @elastic/search-kibana
x-pack/plugins/search_playground @elastic/search-kibana
packages/kbn-search-response-warnings @elastic/kibana-data-discovery
diff --git a/config/serverless.es.yml b/config/serverless.es.yml
index 0a609b0d40b2c..531fa5520c0ae 100644
--- a/config/serverless.es.yml
+++ b/config/serverless.es.yml
@@ -64,5 +64,8 @@ data_visualizer.resultLinks.fileBeat.enabled: false
# Search Playground
xpack.searchPlayground.ui.enabled: true
+# Search InferenceEndpoints
+xpack.searchInferenceEndpoints.ui.enabled: false
+
# Search Notebooks
xpack.search.notebooks.catalog.url: https://elastic-enterprise-search.s3.us-east-2.amazonaws.com/serverless/catalog.json
diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc
index 8bd8e4b00040e..f14a0de00211f 100644
--- a/docs/developer/plugin-list.asciidoc
+++ b/docs/developer/plugin-list.asciidoc
@@ -785,6 +785,10 @@ It uses Chromium and Puppeteer underneath to run the browser in headless mode.
|This plugin contains common assets and endpoints for the use of connectors in Kibana. Primarily used by the enterprise_search and serverless_search plugins.
+|{kib-repo}blob/{branch}/x-pack/plugins/search_inference_endpoints/README.md[searchInferenceEndpoints]
+|The Inference Endpoints is a tool used to manage inference endpoints
+
+
|{kib-repo}blob/{branch}/x-pack/plugins/search_notebooks/README.mdx[searchNotebooks]
|This plugin contains endpoints and components for rendering search python notebooks in the persistent dev console.
diff --git a/package.json b/package.json
index ccf6429b22738..b142765c25ed8 100644
--- a/package.json
+++ b/package.json
@@ -728,6 +728,7 @@
"@kbn/search-errors": "link:packages/kbn-search-errors",
"@kbn/search-examples-plugin": "link:examples/search_examples",
"@kbn/search-index-documents": "link:packages/kbn-search-index-documents",
+ "@kbn/search-inference-endpoints": "link:x-pack/plugins/search_inference_endpoints",
"@kbn/search-notebooks": "link:x-pack/plugins/search_notebooks",
"@kbn/search-playground": "link:x-pack/plugins/search_playground",
"@kbn/search-response-warnings": "link:packages/kbn-search-response-warnings",
diff --git a/packages/deeplinks/search/constants.ts b/packages/deeplinks/search/constants.ts
index fb8a0c459cbd0..36d31d22dfe21 100644
--- a/packages/deeplinks/search/constants.ts
+++ b/packages/deeplinks/search/constants.ts
@@ -8,6 +8,7 @@
export const ENTERPRISE_SEARCH_APP_ID = 'enterpriseSearch';
export const ENTERPRISE_SEARCH_CONTENT_APP_ID = 'enterpriseSearchContent';
+export const ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID = 'enterpriseSearchInferenceEndpoints';
export const ENTERPRISE_SEARCH_APPLICATIONS_APP_ID = 'enterpriseSearchApplications';
export const ENTERPRISE_SEARCH_ANALYTICS_APP_ID = 'enterpriseSearchAnalytics';
export const ENTERPRISE_SEARCH_APPSEARCH_APP_ID = 'appSearch';
@@ -15,3 +16,4 @@ export const ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID = 'workplaceSearch';
export const SERVERLESS_ES_APP_ID = 'serverlessElasticsearch';
export const SERVERLESS_ES_CONNECTORS_ID = 'serverlessConnectors';
export const SERVERLESS_ES_SEARCH_PLAYGROUND_ID = 'searchPlayground';
+export const SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID = 'searchInferenceEndpoints';
diff --git a/packages/deeplinks/search/deep_links.ts b/packages/deeplinks/search/deep_links.ts
index 352640e6d4947..8eeceff8f8ca2 100644
--- a/packages/deeplinks/search/deep_links.ts
+++ b/packages/deeplinks/search/deep_links.ts
@@ -16,6 +16,7 @@ import {
ENTERPRISE_SEARCH_APPSEARCH_APP_ID,
ENTERPRISE_SEARCH_WORKPLACESEARCH_APP_ID,
SERVERLESS_ES_SEARCH_PLAYGROUND_ID,
+ SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID,
} from './constants';
export type EnterpriseSearchApp = typeof ENTERPRISE_SEARCH_APP_ID;
@@ -27,6 +28,7 @@ export type EnterpriseSearchWorkplaceSearchApp = typeof ENTERPRISE_SEARCH_WORKPL
export type ServerlessSearchApp = typeof SERVERLESS_ES_APP_ID;
export type ConnectorsId = typeof SERVERLESS_ES_CONNECTORS_ID;
export type SearchPlaygroundId = typeof SERVERLESS_ES_SEARCH_PLAYGROUND_ID;
+export type SearchInferenceEndpointsId = typeof SERVERLESS_ES_SEARCH_INFERENCE_ENDPOINTS_ID;
export type ContentLinkId = 'searchIndices' | 'connectors' | 'webCrawlers';
@@ -44,6 +46,7 @@ export type DeepLinkId =
| ServerlessSearchApp
| ConnectorsId
| SearchPlaygroundId
+ | SearchInferenceEndpointsId
| `${EnterpriseSearchContentApp}:${ContentLinkId}`
| `${EnterpriseSearchApplicationsApp}:${ApplicationsLinkId}`
| `${EnterpriseSearchAppsearchApp}:${AppsearchLinkId}`;
diff --git a/packages/deeplinks/search/index.ts b/packages/deeplinks/search/index.ts
index ba15b8c4890c3..663d625e9fd72 100644
--- a/packages/deeplinks/search/index.ts
+++ b/packages/deeplinks/search/index.ts
@@ -9,6 +9,7 @@
export {
ENTERPRISE_SEARCH_APP_ID,
ENTERPRISE_SEARCH_CONTENT_APP_ID,
+ ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID,
ENTERPRISE_SEARCH_APPLICATIONS_APP_ID,
ENTERPRISE_SEARCH_ANALYTICS_APP_ID,
ENTERPRISE_SEARCH_APPSEARCH_APP_ID,
diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml
index a7ec0e03f3dda..336cfd2c6b93d 100644
--- a/packages/kbn-optimizer/limits.yml
+++ b/packages/kbn-optimizer/limits.yml
@@ -31,7 +31,7 @@ pageLoadAssetSize:
dataQuality: 19384
datasetQuality: 50624
dataViewEditor: 28082
- dataViewFieldEditor: 27000
+ dataViewFieldEditor: 42021
dataViewManagement: 5300
dataViews: 65000
dataVisualizer: 27530
@@ -41,8 +41,8 @@ pageLoadAssetSize:
discoverShared: 17111
embeddable: 87309
embeddableEnhanced: 22107
- enterpriseSearch: 50858
- esqlDataGrid: 24598
+ enterpriseSearch: 66810
+ esqlDataGrid: 24582
esUiShared: 326654
eventAnnotation: 30000
eventAnnotationListing: 25841
@@ -132,6 +132,7 @@ pageLoadAssetSize:
screenshotMode: 17856
screenshotting: 22870
searchConnectors: 30000
+ searchInferenceEndpoints: 20470
searchNotebooks: 18942
searchPlayground: 19325
searchprofiler: 67080
diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
index 0e12abb64bcc7..4ffe9d483a1a4 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts
@@ -135,6 +135,7 @@ export const applicationUsageSchema = {
canvas: commonSchema,
enterpriseSearch: commonSchema,
enterpriseSearchContent: commonSchema,
+ enterpriseSearchInferenceEndpoints: commonSchema,
enterpriseSearchAnalytics: commonSchema,
enterpriseSearchApplications: commonSchema,
enterpriseSearchAISearch: commonSchema,
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index 417a59ad71bf7..1e2c942ba2a5c 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -2098,6 +2098,137 @@
}
}
},
+ "enterpriseSearchInferenceEndpoints": {
+ "properties": {
+ "appId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application being tracked"
+ }
+ },
+ "viewId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "Always `main`"
+ }
+ },
+ "clicks_total": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application since we started counting them"
+ }
+ },
+ "clicks_7_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 7 days"
+ }
+ },
+ "clicks_30_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 30 days"
+ }
+ },
+ "clicks_90_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application over the last 90 days"
+ }
+ },
+ "minutes_on_screen_total": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen since we started counting them."
+ }
+ },
+ "minutes_on_screen_7_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 7 days"
+ }
+ },
+ "minutes_on_screen_30_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 30 days"
+ }
+ },
+ "minutes_on_screen_90_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen over the last 90 days"
+ }
+ },
+ "views": {
+ "type": "array",
+ "items": {
+ "properties": {
+ "appId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application being tracked"
+ }
+ },
+ "viewId": {
+ "type": "keyword",
+ "_meta": {
+ "description": "The application view being tracked"
+ }
+ },
+ "clicks_total": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the application sub view since we started counting them"
+ }
+ },
+ "clicks_7_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 7 days"
+ }
+ },
+ "clicks_30_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 30 days"
+ }
+ },
+ "clicks_90_days": {
+ "type": "long",
+ "_meta": {
+ "description": "General number of clicks in the active application sub view over the last 90 days"
+ }
+ },
+ "minutes_on_screen_total": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application sub view is active and on-screen since we started counting them."
+ }
+ },
+ "minutes_on_screen_7_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
+ }
+ },
+ "minutes_on_screen_30_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
+ }
+ },
+ "minutes_on_screen_90_days": {
+ "type": "float",
+ "_meta": {
+ "description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"enterpriseSearchAnalytics": {
"properties": {
"appId": {
diff --git a/test/plugin_functional/test_suites/core_plugins/rendering.ts b/test/plugin_functional/test_suites/core_plugins/rendering.ts
index 7ccbc90d8760e..31e0a44b9e823 100644
--- a/test/plugin_functional/test_suites/core_plugins/rendering.ts
+++ b/test/plugin_functional/test_suites/core_plugins/rendering.ts
@@ -314,6 +314,7 @@ export default function ({ getService }: PluginFunctionalProviderContext) {
'xpack.rollup.ui.enabled (boolean)',
'xpack.saved_object_tagging.cache_refresh_interval (duration)',
'xpack.searchPlayground.ui.enabled (boolean)',
+ 'xpack.searchInferenceEndpoints.ui.enabled (boolean)',
'xpack.security.loginAssistanceMessage (string)',
'xpack.security.sameSiteCookies (alternatives)',
'xpack.security.showInsecureClusterWarning (boolean)',
diff --git a/tsconfig.base.json b/tsconfig.base.json
index cd3deff3a294e..da0daf44cd411 100644
--- a/tsconfig.base.json
+++ b/tsconfig.base.json
@@ -1428,6 +1428,8 @@
"@kbn/search-examples-plugin/*": ["examples/search_examples/*"],
"@kbn/search-index-documents": ["packages/kbn-search-index-documents"],
"@kbn/search-index-documents/*": ["packages/kbn-search-index-documents/*"],
+ "@kbn/search-inference-endpoints": ["x-pack/plugins/search_inference_endpoints"],
+ "@kbn/search-inference-endpoints/*": ["x-pack/plugins/search_inference_endpoints/*"],
"@kbn/search-notebooks": ["x-pack/plugins/search_notebooks"],
"@kbn/search-notebooks/*": ["x-pack/plugins/search_notebooks/*"],
"@kbn/search-playground": ["x-pack/plugins/search_playground"],
diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json
index 8059502946861..2943af3cf46d5 100644
--- a/x-pack/.i18nrc.json
+++ b/x-pack/.i18nrc.json
@@ -94,6 +94,7 @@
"xpack.screenshotting": "plugins/screenshotting",
"xpack.searchNotebooks": "plugins/search_notebooks",
"xpack.searchPlayground": "plugins/search_playground",
+ "xpack.searchInferenceEndpoints": "plugins/search_inference_endpoints",
"xpack.searchProfiler": "plugins/searchprofiler",
"xpack.security": "plugins/security",
"xpack.server": "legacy/server",
diff --git a/x-pack/plugins/enterprise_search/common/constants.ts b/x-pack/plugins/enterprise_search/common/constants.ts
index 8f49e3cae3c02..9830f66a441ed 100644
--- a/x-pack/plugins/enterprise_search/common/constants.ts
+++ b/x-pack/plugins/enterprise_search/common/constants.ts
@@ -8,6 +8,7 @@
import {
ENTERPRISE_SEARCH_APP_ID,
ENTERPRISE_SEARCH_CONTENT_APP_ID,
+ ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID,
ENTERPRISE_SEARCH_APPLICATIONS_APP_ID,
ENTERPRISE_SEARCH_ANALYTICS_APP_ID,
ENTERPRISE_SEARCH_APPSEARCH_APP_ID,
@@ -176,6 +177,22 @@ export const VECTOR_SEARCH_PLUGIN = {
URL: '/app/enterprise_search/vector_search',
};
+export const INFERENCE_ENDPOINTS_PLUGIN = {
+ ID: ENTERPRISE_SEARCH_INFERENCE_ENDPOINTS_APP_ID,
+ NAME: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.productName', {
+ defaultMessage: 'Inference Endpoints',
+ }),
+ NAV_TITLE: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.navTitle', {
+ defaultMessage: 'Relevance',
+ }),
+ DESCRIPTION: i18n.translate('xpack.enterpriseSearch.inferenceEndpoints.description', {
+ defaultMessage: 'View for managing inference endpoints.',
+ }),
+ URL: '/app/enterprise_search/relevance',
+ LOGO: 'logoEnterpriseSearch',
+ SUPPORT_URL: 'https://discuss.elastic.co/c/enterprise-search/',
+};
+
export const LICENSED_SUPPORT_URL = 'https://support.elastic.co';
export const JSON_HEADER = {
diff --git a/x-pack/plugins/enterprise_search/kibana.jsonc b/x-pack/plugins/enterprise_search/kibana.jsonc
index cefa247025aa5..3855fd3d9e1f9 100644
--- a/x-pack/plugins/enterprise_search/kibana.jsonc
+++ b/x-pack/plugins/enterprise_search/kibana.jsonc
@@ -31,6 +31,7 @@
"console",
"searchConnectors",
"searchPlayground",
+ "searchInferenceEndpoints",
"embeddable",
"discover",
"charts",
diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts
index 5aa7f5236e269..cca5523ded681 100644
--- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/kea_logic/kibana_logic.mock.ts
@@ -64,6 +64,7 @@ export const mockKibanaValues = {
hasWebCrawler: true,
},
renderHeaderActions: jest.fn(),
+ searchInferenceEndpoints: null,
searchPlayground: searchPlaygroundMock.createStart(),
security: securityMock.createStart(),
setBreadcrumbs: jest.fn(),
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx
new file mode 100644
index 0000000000000..440638b57d4b6
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/inference_endpoints.tsx
@@ -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 React from 'react';
+
+import { useValues } from 'kea';
+
+import { i18n } from '@kbn/i18n';
+
+import { KibanaLogic } from '../../shared/kibana';
+
+import { EnterpriseSearchRelevancePageTemplate } from './layout/page_template';
+
+export const InferenceEndpoints: React.FC = () => {
+ const { searchInferenceEndpoints } = useValues(KibanaLogic);
+
+ if (!searchInferenceEndpoints) {
+ return null;
+ }
+ return (
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts
new file mode 100644
index 0000000000000..0a155ce20b555
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 { EnterpriseSearchRelevancePageTemplate } from './page_template';
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx
new file mode 100644
index 0000000000000..6b9e6b891a59f
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.test.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+jest.mock('../../../shared/layout/nav', () => ({
+ useEnterpriseSearchNav: () => [],
+}));
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { i18n } from '@kbn/i18n';
+
+import { SetEnterpriseSearchRelevanceChrome } from '../../../shared/kibana_chrome';
+import { EnterpriseSearchPageTemplateWrapper } from '../../../shared/layout';
+import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
+
+import { EnterpriseSearchRelevancePageTemplate } from './page_template';
+
+describe('EnterpriseSearchRelevancePageTemplate', () => {
+ it('renders', () => {
+ const wrapper = shallow(
+
+
+ {i18n.translate('xpack.enterpriseSearch..div.worldLabel', { defaultMessage: 'world' })}
+
+
+ );
+
+ expect(wrapper.type()).toEqual(EnterpriseSearchPageTemplateWrapper);
+ expect(wrapper.prop('solutionNav')).toEqual({ items: [], name: 'Search' });
+ expect(wrapper.find('.hello').text()).toEqual('world');
+ });
+
+ describe('page chrome', () => {
+ it('takes a breadcrumb array & renders a product-specific page chrome', () => {
+ const wrapper = shallow();
+ const setPageChrome = wrapper
+ .find(EnterpriseSearchPageTemplateWrapper)
+ .prop('setPageChrome') as any;
+
+ expect(setPageChrome.type).toEqual(SetEnterpriseSearchRelevanceChrome);
+ expect(setPageChrome.props.trail).toEqual(['Some page']);
+ });
+ });
+
+ describe('page telemetry', () => {
+ it('takes a metric & renders product-specific telemetry viewed event', () => {
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('viewed');
+ expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('metric')).toEqual('some_page');
+ });
+ });
+
+ describe('props', () => {
+ it('passes down any ...pageTemplateProps that EnterpriseSearchPageTemplateWrapper accepts', () => {
+ const wrapper = shallow(
+ }
+ />
+ );
+
+ expect(
+ wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('pageHeader')!.pageTitle
+ ).toEqual('hello world');
+ expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('isLoading')).toEqual(false);
+ expect(wrapper.find(EnterpriseSearchPageTemplateWrapper).prop('emptyState')).toEqual(
);
+ });
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx
new file mode 100644
index 0000000000000..258b9c9a68ae1
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/layout/page_template.tsx
@@ -0,0 +1,37 @@
+/*
+ * 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 { SEARCH_PRODUCT_NAME } from '../../../../../common/constants';
+import { SetEnterpriseSearchRelevanceChrome } from '../../../shared/kibana_chrome';
+import { EnterpriseSearchPageTemplateWrapper, PageTemplateProps } from '../../../shared/layout';
+import { useEnterpriseSearchNav } from '../../../shared/layout';
+import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
+
+export const EnterpriseSearchRelevancePageTemplate: React.FC = ({
+ children,
+ pageChrome,
+ pageViewTelemetry,
+ ...pageTemplateProps
+}) => {
+ return (
+ }
+ >
+ {pageViewTelemetry && (
+
+ )}
+ {children}
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts
new file mode 100644
index 0000000000000..482c1a58faa9c
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 { NotFound } from './not_found';
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx
new file mode 100644
index 0000000000000..7b5436958edb7
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.test.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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 { shallow } from 'enzyme';
+
+import { NotFoundPrompt } from '../../../shared/not_found';
+import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
+import { EnterpriseSearchRelevancePageTemplate } from '../layout';
+
+import { NotFound } from '.';
+
+describe('NotFound', () => {
+ const wrapper = shallow();
+
+ it('renders the shared not found prompt', () => {
+ expect(wrapper.find(NotFoundPrompt)).toHaveLength(1);
+ });
+
+ it('renders a telemetry error event', () => {
+ expect(wrapper.find(SendEnterpriseSearchTelemetry).prop('action')).toEqual('error');
+ });
+
+ it('passes optional preceding page chrome', () => {
+ wrapper.setProps({ pageChrome: ['Inference Endpoints', 'some-index'] });
+
+ expect(wrapper.find(EnterpriseSearchRelevancePageTemplate).prop('pageChrome')).toEqual([
+ 'Inference Endpoints',
+ 'some-index',
+ '404',
+ ]);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx
new file mode 100644
index 0000000000000..1c599fdc884e3
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/components/not_found/not_found.tsx
@@ -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 React from 'react';
+
+import { INFERENCE_ENDPOINTS_PLUGIN } from '../../../../../common/constants';
+import { PageTemplateProps } from '../../../shared/layout';
+import { NotFoundPrompt } from '../../../shared/not_found';
+import { SendEnterpriseSearchTelemetry } from '../../../shared/telemetry';
+import { EnterpriseSearchRelevancePageTemplate } from '../layout';
+
+export const NotFound: React.FC = ({ pageChrome = [] }) => {
+ return (
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx
new file mode 100644
index 0000000000000..5ac28448c7c4d
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.test.tsx
@@ -0,0 +1,46 @@
+/*
+ * 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 { setMockValues } from '../__mocks__/kea_logic';
+import '../__mocks__/shallow_useeffect.mock';
+import '../__mocks__/enterprise_search_url.mock';
+
+import React from 'react';
+
+import { shallow } from 'enzyme';
+
+import { VersionMismatchPage } from '../shared/version_mismatch';
+
+import { EnterpriseSearchRelevance, EnterpriseSearchRelevanceConfigured } from '.';
+
+describe('EnterpriseSearchRelevance', () => {
+ it('renders VersionMismatchPage when there are mismatching versions', () => {
+ setMockValues({ config: { canDeployEntSearch: true, host: 'host' } });
+ const wrapper = shallow(
+
+ );
+
+ expect(wrapper.find(VersionMismatchPage)).toHaveLength(1);
+ });
+
+ it('renders EnterpriseSearchRelevanceConfigured when config.host is set & available', () => {
+ setMockValues({
+ config: { canDeployEntSearch: true, host: 'some.url' },
+ errorConnectingMessage: '',
+ });
+ const wrapper = shallow();
+
+ expect(wrapper.find(EnterpriseSearchRelevanceConfigured)).toHaveLength(1);
+ });
+
+ it('renders EnterpriseSearchRelevanceConfigured when config.host is not set & Ent Search cannot be deployed', () => {
+ setMockValues({ config: { canDeployEntSearch: false, host: '' }, errorConnectingMessage: '' });
+ const wrapper = shallow();
+
+ expect(wrapper.find(EnterpriseSearchRelevanceConfigured)).toHaveLength(1);
+ });
+});
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx
new file mode 100644
index 0000000000000..6e75d8055619b
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/index.tsx
@@ -0,0 +1,65 @@
+/*
+ * 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 { Redirect } from 'react-router-dom';
+
+import { useValues } from 'kea';
+
+import { Route, Routes } from '@kbn/shared-ux-router';
+
+import { isVersionMismatch } from '../../../common/is_version_mismatch';
+import { InitialAppData } from '../../../common/types';
+import { ErrorStatePrompt } from '../shared/error_state';
+import { HttpLogic } from '../shared/http';
+import { VersionMismatchPage } from '../shared/version_mismatch';
+
+import { InferenceEndpoints } from './components/inference_endpoints';
+import { NotFound } from './components/not_found';
+import { INFERENCE_ENDPOINTS_PATH, ERROR_STATE_PATH, ROOT_PATH } from './routes';
+
+export const EnterpriseSearchRelevance: React.FC = (props) => {
+ const { errorConnectingMessage } = useValues(HttpLogic);
+ const { enterpriseSearchVersion, kibanaVersion } = props;
+ const incompatibleVersions = isVersionMismatch(enterpriseSearchVersion, kibanaVersion);
+
+ const showView = () => {
+ if (incompatibleVersions) {
+ return (
+
+ );
+ }
+
+ return )} />;
+ };
+
+ return (
+
+
+ {errorConnectingMessage ? : }
+
+ {showView()}
+
+ );
+};
+
+export const EnterpriseSearchRelevanceConfigured: React.FC> = () => {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js
new file mode 100644
index 0000000000000..d186396d14a07
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/jest.config.js
@@ -0,0 +1,28 @@
+/*
+ * 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 = {
+ preset: '@kbn/test',
+ rootDir: '../../../../../..',
+ roots: [
+ '/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance',
+ ],
+ collectCoverage: true,
+ coverageReporters: ['text', 'html'],
+ collectCoverageFrom: [
+ '/x-pack/plugins/enterprise_search/public/applications/**/*.{ts,tsx}',
+ '!/x-pack/plugins/enterprise_search/public/*.ts',
+ '!/x-pack/plugins/enterprise_search/server/*.ts',
+ '!/x-pack/plugins/enterprise_search/public/applications/test_helpers/**/*.{ts,tsx}',
+ ],
+ coverageDirectory:
+ '/target/kibana-coverage/jest/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance',
+ modulePathIgnorePatterns: [
+ '/x-pack/plugins/enterprise_search/public/applications/app_search/cypress',
+ '/x-pack/plugins/enterprise_search/public/applications/workplace_search/cypress',
+ ],
+};
diff --git a/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.ts
new file mode 100644
index 0000000000000..5ea7ad1c781a8
--- /dev/null
+++ b/x-pack/plugins/enterprise_search/public/applications/enterprise_search_relevance/routes.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 const ROOT_PATH = '/';
+export const ERROR_STATE_PATH = '/error_state';
+export const INFERENCE_ENDPOINTS_PATH = `${ROOT_PATH}inference_endpoints`;
diff --git a/x-pack/plugins/enterprise_search/public/applications/index.tsx b/x-pack/plugins/enterprise_search/public/applications/index.tsx
index d6bc0e0da5d44..ea45e121470e2 100644
--- a/x-pack/plugins/enterprise_search/public/applications/index.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/index.tsx
@@ -128,6 +128,7 @@ export const renderApp = (
HeaderActions ? renderHeaderActions.bind(null, HeaderActions, store, params) : undefined
),
searchPlayground: plugins.searchPlayground,
+ searchInferenceEndpoints: plugins.searchInferenceEndpoints,
security,
setBreadcrumbs: chrome.setBreadcrumbs,
setChromeIsVisible: chrome.setIsVisible,
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts
index f9da95c1b2667..da18e9e8bb44f 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana/kibana_logic.ts
@@ -29,6 +29,7 @@ import { LensPublicStart } from '@kbn/lens-plugin/public';
import { MlPluginStart } from '@kbn/ml-plugin/public';
import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants';
import { ConnectorDefinition } from '@kbn/search-connectors-plugin/public';
+import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import { AuthenticatedUser, SecurityPluginStart } from '@kbn/security-plugin/public';
import { SharePluginStart } from '@kbn/share-plugin/public';
@@ -65,6 +66,7 @@ export interface KibanaLogicProps {
productFeatures: ProductFeatures;
renderHeaderActions(HeaderActions?: FC): void;
searchPlayground?: SearchPlaygroundPluginStart;
+ searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart;
security?: SecurityPluginStart;
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
setChromeIsVisible(isVisible: boolean): void;
@@ -97,6 +99,7 @@ export interface KibanaValues {
productFeatures: ProductFeatures;
renderHeaderActions(HeaderActions?: FC): void;
searchPlayground: SearchPlaygroundPluginStart | null;
+ searchInferenceEndpoints: SearchInferenceEndpointsPluginStart | null;
security: SecurityPluginStart | null;
setBreadcrumbs(crumbs: ChromeBreadcrumb[]): void;
setChromeIsVisible(isVisible: boolean): void;
@@ -141,6 +144,7 @@ export const KibanaLogic = kea>({
productFeatures: [props.productFeatures, {}],
renderHeaderActions: [props.renderHeaderActions, {}],
searchPlayground: [props.searchPlayground || null, {}],
+ searchInferenceEndpoints: [props.searchInferenceEndpoints || null, {}],
security: [props.security || null, {}],
setBreadcrumbs: [props.setBreadcrumbs, {}],
setChromeIsVisible: [props.setChromeIsVisible, {}],
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts
index d30ceee39b75b..ac3e6d7a6437d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/generate_breadcrumbs.ts
@@ -14,6 +14,7 @@ import {
ANALYTICS_PLUGIN,
APP_SEARCH_PLUGIN,
ENTERPRISE_SEARCH_CONTENT_PLUGIN,
+ INFERENCE_ENDPOINTS_PLUGIN,
ENTERPRISE_SEARCH_OVERVIEW_PLUGIN,
ENTERPRISE_SEARCH_PRODUCT_NAME,
AI_SEARCH_PLUGIN,
@@ -151,6 +152,9 @@ export const useEnterpriseSearchContentBreadcrumbs = (breadcrumbs: Breadcrumbs =
...breadcrumbs,
]);
+export const useEnterpriseSearchRelevanceBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
+ useSearchBreadcrumbs([{ text: INFERENCE_ENDPOINTS_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]);
+
export const useSearchExperiencesBreadcrumbs = (breadcrumbs: Breadcrumbs = []) =>
useSearchBreadcrumbs([{ text: SEARCH_EXPERIENCES_PLUGIN.NAV_TITLE, path: '/' }, ...breadcrumbs]);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts
index 75e72ab5e0208..ae2151287b1d1 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/index.ts
@@ -9,6 +9,7 @@ export {
SetSearchChrome,
SetAnalyticsChrome,
SetEnterpriseSearchContentChrome,
+ SetEnterpriseSearchRelevanceChrome,
SetElasticsearchChrome,
SetAiSearchChrome,
SetAppSearchChrome,
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx
index 5c6758509c01d..ac85bd89b6ba9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/kibana_chrome/set_chrome.tsx
@@ -19,6 +19,7 @@ import {
useEnterpriseSearchApplicationsBreadcrumbs,
useAnalyticsBreadcrumbs,
useEnterpriseSearchContentBreadcrumbs,
+ useEnterpriseSearchRelevanceBreadcrumbs,
useAiSearchBreadcrumbs,
useElasticsearchBreadcrumbs,
useAppSearchBreadcrumbs,
@@ -177,6 +178,19 @@ export const SetEnterpriseSearchContentChrome: React.FC = ({ tra
return null;
};
+export const SetEnterpriseSearchRelevanceChrome: React.FC = ({ trail = [] }) => {
+ const { setBreadcrumbs } = useValues(KibanaLogic);
+
+ const crumbs = useGenerateBreadcrumbs(trail);
+ const breadcrumbs = useEnterpriseSearchRelevanceBreadcrumbs(crumbs);
+
+ useEffect(() => {
+ setBreadcrumbs(breadcrumbs);
+ }, [trail]);
+
+ return null;
+};
+
export const SetSearchExperiencesChrome: React.FC = ({ trail = [] }) => {
const { setBreadcrumbs, setDocTitle } = useValues(KibanaLogic);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
index 3930e0f6c36f1..f818dfb9141f3 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.test.tsx
@@ -84,6 +84,18 @@ const baseNavItems = [
],
name: 'Build',
},
+ {
+ id: 'relevance',
+ items: [
+ {
+ href: '/app/enterprise_search/relevance/inference_endpoints',
+ id: 'inference_endpoints',
+ items: undefined,
+ name: 'Inference Endpoints',
+ },
+ ],
+ name: 'Relevance',
+ },
{
id: 'es_getting_started',
items: [
@@ -244,6 +256,7 @@ describe('useEnterpriseSearchApplicationNav', () => {
expect(navItems?.slice(1).map((ni) => ni.name)).toEqual([
'Content',
'Build',
+ 'Relevance',
'Getting started',
'Enterprise Search',
]);
@@ -303,6 +316,7 @@ describe('useEnterpriseSearchApplicationNav', () => {
expect(navItems?.slice(1).map((ni) => ni.name)).toEqual([
'Content',
'Build',
+ 'Relevance',
'Getting started',
'Enterprise Search',
]);
diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
index c2268d254197d..bd53ed235d4cb 100644
--- a/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/shared/layout/nav.tsx
@@ -23,6 +23,7 @@ import {
AI_SEARCH_PLUGIN,
VECTOR_SEARCH_PLUGIN,
WORKPLACE_SEARCH_PLUGIN,
+ INFERENCE_ENDPOINTS_PLUGIN,
} from '../../../../common/constants';
import {
SEARCH_APPLICATIONS_PATH,
@@ -35,6 +36,8 @@ import {
CRAWLERS_PATH,
SEARCH_INDICES_PATH,
} from '../../enterprise_search_content/routes';
+
+import { INFERENCE_ENDPOINTS_PATH } from '../../enterprise_search_relevance/routes';
import { KibanaLogic } from '../kibana';
import { generateNavLink } from './nav_link_helpers';
@@ -147,6 +150,25 @@ export const useEnterpriseSearchNav = (alwaysReturn = false) => {
defaultMessage: 'Build',
}),
},
+ {
+ id: 'relevance',
+ items: [
+ {
+ id: 'inference_endpoints',
+ name: i18n.translate('xpack.enterpriseSearch.nav.inferenceEndpointsTitle', {
+ defaultMessage: 'Inference Endpoints',
+ }),
+ ...generateNavLink({
+ shouldNotCreateHref: true,
+ shouldShowActiveForSubroutes: true,
+ to: INFERENCE_ENDPOINTS_PLUGIN.URL + INFERENCE_ENDPOINTS_PATH,
+ }),
+ },
+ ],
+ name: i18n.translate('xpack.enterpriseSearch.nav.relevanceTitle', {
+ defaultMessage: 'Relevance',
+ }),
+ },
{
id: 'es_getting_started',
items: [
diff --git a/x-pack/plugins/enterprise_search/public/navigation_tree.ts b/x-pack/plugins/enterprise_search/public/navigation_tree.ts
index 9dc1d324fd118..dc079aec90688 100644
--- a/x-pack/plugins/enterprise_search/public/navigation_tree.ts
+++ b/x-pack/plugins/enterprise_search/public/navigation_tree.ts
@@ -206,6 +206,13 @@ export const getNavigationTreeDefinition = ({
defaultMessage: 'Build',
}),
},
+ {
+ children: [{ link: 'searchInferenceEndpoints' }],
+ id: 'relevance',
+ title: i18n.translate('xpack.enterpriseSearch.searchNav.relevance', {
+ defaultMessage: 'Relevance',
+ }),
+ },
{
children: [
{
diff --git a/x-pack/plugins/enterprise_search/public/plugin.ts b/x-pack/plugins/enterprise_search/public/plugin.ts
index 96c4bab2b8059..9c03fb623fa3d 100644
--- a/x-pack/plugins/enterprise_search/public/plugin.ts
+++ b/x-pack/plugins/enterprise_search/public/plugin.ts
@@ -32,6 +32,7 @@ import { MlPluginStart } from '@kbn/ml-plugin/public';
import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public';
import { ELASTICSEARCH_URL_PLACEHOLDER } from '@kbn/search-api-panels/constants';
import { SearchConnectorsPluginStart } from '@kbn/search-connectors-plugin/public';
+import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
import { SharePluginSetup, SharePluginStart } from '@kbn/share-plugin/public';
@@ -48,6 +49,7 @@ import {
SEARCH_PRODUCT_NAME,
VECTOR_SEARCH_PLUGIN,
WORKPLACE_SEARCH_PLUGIN,
+ INFERENCE_ENDPOINTS_PLUGIN,
} from '../common/constants';
import {
CreatIndexLocatorDefinition,
@@ -63,6 +65,7 @@ import {
CRAWLERS_PATH,
} from './applications/enterprise_search_content/routes';
+import { INFERENCE_ENDPOINTS_PATH } from './applications/enterprise_search_relevance/routes';
import { docLinks } from './applications/shared/doc_links';
import type { DynamicSideNavItems } from './navigation_tree';
@@ -94,6 +97,7 @@ export interface PluginsStart {
navigation: NavigationPublicPluginStart;
searchConnectors?: SearchConnectorsPluginStart;
searchPlayground?: SearchPlaygroundPluginStart;
+ searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart;
security?: SecurityPluginStart;
share?: SharePluginStart;
}
@@ -128,6 +132,19 @@ const contentLinks: AppDeepLink[] = [
},
];
+const relevanceLinks: AppDeepLink[] = [
+ {
+ id: 'inferenceEndpoints',
+ path: `/${INFERENCE_ENDPOINTS_PATH}`,
+ title: i18n.translate(
+ 'xpack.enterpriseSearch.navigation.relevanceInferenceEndpointsLinkLabel',
+ {
+ defaultMessage: 'Inference Endpoints',
+ }
+ ),
+ },
+];
+
const applicationsLinks: AppDeepLink[] = [
{
id: 'playground',
@@ -395,6 +412,31 @@ export class EnterpriseSearchPlugin implements Plugin {
title: ANALYTICS_PLUGIN.NAME,
});
+ core.application.register({
+ appRoute: INFERENCE_ENDPOINTS_PLUGIN.URL,
+ category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
+ deepLinks: relevanceLinks,
+ euiIconType: INFERENCE_ENDPOINTS_PLUGIN.LOGO,
+ id: INFERENCE_ENDPOINTS_PLUGIN.ID,
+ mount: async (params: AppMountParameters) => {
+ const kibanaDeps = await this.getKibanaDeps(core, params, cloud);
+ const { chrome, http } = kibanaDeps.core;
+ chrome.docTitle.change(INFERENCE_ENDPOINTS_PLUGIN.NAME);
+
+ await this.getInitialData(http);
+ const pluginData = this.getPluginData();
+
+ const { renderApp } = await import('./applications');
+ const { EnterpriseSearchRelevance } = await import(
+ './applications/enterprise_search_relevance'
+ );
+
+ return renderApp(EnterpriseSearchRelevance, kibanaDeps, pluginData);
+ },
+ title: INFERENCE_ENDPOINTS_PLUGIN.NAME,
+ visibleIn: [],
+ });
+
core.application.register({
appRoute: SEARCH_EXPERIENCES_PLUGIN.URL,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
diff --git a/x-pack/plugins/enterprise_search/tsconfig.json b/x-pack/plugins/enterprise_search/tsconfig.json
index 53f364e26cac2..3a66b8350be4f 100644
--- a/x-pack/plugins/enterprise_search/tsconfig.json
+++ b/x-pack/plugins/enterprise_search/tsconfig.json
@@ -70,6 +70,7 @@
"@kbn/es-errors",
"@kbn/search-connectors-plugin",
"@kbn/search-playground",
+ "@kbn/search-inference-endpoints",
"@kbn/utility-types",
"@kbn/index-management",
"@kbn/deeplinks-search",
diff --git a/x-pack/plugins/search_inference_endpoints/README.md b/x-pack/plugins/search_inference_endpoints/README.md
new file mode 100644
index 0000000000000..3c28f965e7bf5
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/README.md
@@ -0,0 +1,3 @@
+# Inference Endpoints
+
+The Inference Endpoints is a tool used to manage inference endpoints
diff --git a/x-pack/plugins/search_inference_endpoints/common/constants.ts b/x-pack/plugins/search_inference_endpoints/common/constants.ts
new file mode 100644
index 0000000000000..cdba227626a55
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/common/constants.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const PLUGIN_ID = 'searchInferenceEndpoints';
+export const PLUGIN_NAME = 'InferenceEndpoints';
+
+export const INFERENCE_ENDPOINTS_QUERY_KEY = 'inferenceEndpointsQueryKey';
diff --git a/x-pack/plugins/search_inference_endpoints/common/doc_links.ts b/x-pack/plugins/search_inference_endpoints/common/doc_links.ts
new file mode 100644
index 0000000000000..c3d8d31dffa6d
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/common/doc_links.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 { DocLinks } from '@kbn/doc-links';
+
+class InferenceEndpointsDocLinks {
+ public nlpImportModel: string = '';
+ public supportedNlpModels: string = '';
+
+ constructor() {}
+
+ setDocLinks(newDocLinks: DocLinks) {
+ this.nlpImportModel = newDocLinks.ml.nlpImportModel;
+ this.supportedNlpModels = newDocLinks.ml.supportedNlpModels;
+ }
+}
+
+export const docLinks = new InferenceEndpointsDocLinks();
diff --git a/x-pack/plugins/search_inference_endpoints/common/translations.ts b/x-pack/plugins/search_inference_endpoints/common/translations.ts
new file mode 100644
index 0000000000000..e58829812829b
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/common/translations.ts
@@ -0,0 +1,96 @@
+/*
+ * 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 INFERENCE_ENDPOINT_LABEL = i18n.translate(
+ 'xpack.searchInferenceEndpoints.inferenceEndpointsLabel',
+ {
+ defaultMessage: 'Inference Endpoints',
+ }
+);
+
+export const MANAGE_INFERENCE_ENDPOINTS_LABEL = i18n.translate(
+ 'xpack.searchInferenceEndpoints.allInferenceEndpoints.description',
+ {
+ defaultMessage: 'Manage your inference endpoints.',
+ }
+);
+
+export const ADD_ENDPOINT_LABEL = i18n.translate(
+ 'xpack.searchInferenceEndpoints.newInferenceEndpointButtonLabel',
+ {
+ defaultMessage: 'Add endpoint',
+ }
+);
+
+export const CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION = i18n.translate(
+ 'xpack.searchInferenceEndpoints.addEmptyPrompt.createFirstInferenceEndpointDescription',
+ {
+ defaultMessage:
+ 'Connect to your third-party model provider to create an inference endpoint for semantic search.',
+ }
+);
+
+export const START_WITH_PREPARED_ENDPOINTS_LABEL = i18n.translate(
+ 'xpack.searchInferenceEndpoints.addEmptyPrompt.startWithPreparedEndpointsLabel',
+ {
+ defaultMessage: 'Get started quickly with our prepared endpoints:',
+ }
+);
+
+export const ELSER_TITLE = i18n.translate(
+ 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserTitle',
+ {
+ defaultMessage: 'ELSER',
+ }
+);
+
+export const ELSER_DESCRIPTION = i18n.translate(
+ 'xpack.searchInferenceEndpoints.addEmptyPrompt.elserDescription',
+ {
+ defaultMessage:
+ 'ELSER is a sparse vector NLP model trained by Elastic for semantic search. Recommended for English language.',
+ }
+);
+
+export const E5_TITLE = i18n.translate('xpack.searchInferenceEndpoints.addEmptyPrompt.e5Title', {
+ defaultMessage: 'Multilingual E5',
+});
+
+export const E5_DESCRIPTION = i18n.translate(
+ 'xpack.searchInferenceEndpoints.addEmptyPrompt.e5Description',
+ {
+ defaultMessage:
+ 'E5 is a dense vector NLP model that enables you to perform multi-lingual semantic search.',
+ }
+);
+
+export const ERROR_TITLE = i18n.translate('xpack.searchInferenceEndpoints.inferenceId.errorTitle', {
+ defaultMessage: 'Error adding inference endpoint',
+});
+
+export const UNABLE_TO_CREATE_INFERENCE_ENDPOINT = i18n.translate(
+ 'xpack.searchInferenceEndpoints.inferenceFlyoutWrapperComponent.unableTocreateInferenceEndpointError',
+ {
+ defaultMessage: 'Unable to create an inference endpoint.',
+ }
+);
+
+export const INFERENCE_ENDPOINT_ALREADY_EXISTS = i18n.translate(
+ 'xpack.searchInferenceEndpoints.inferenceFlyoutWrapperComponent.inferenceEndpointAlreadyExistsError',
+ {
+ defaultMessage: 'Inference Endpoint id already exists',
+ }
+);
+
+export const FORBIDDEN_TO_ACCESS_TRAINED_MODELS = i18n.translate(
+ 'xpack.searchInferenceEndpoints.inferenceFlyoutWrapperComponent.forbiddenToAccessTrainedModelsError',
+ {
+ defaultMessage: 'Forbidden to access trained models',
+ }
+);
diff --git a/x-pack/plugins/search_inference_endpoints/common/types.ts b/x-pack/plugins/search_inference_endpoints/common/types.ts
new file mode 100644
index 0000000000000..ef29d987309f8
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/common/types.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 enum APIRoutes {
+ GET_INFERENCE_ENDPOINTS = '/internal/inference_endpoints/endpoints',
+}
+
+export interface SearchInferenceEndpointsConfigType {
+ ui: {
+ enabled: boolean;
+ };
+}
diff --git a/x-pack/plugins/search_inference_endpoints/jest.config.js b/x-pack/plugins/search_inference_endpoints/jest.config.js
new file mode 100644
index 0000000000000..8c19afbe9fb98
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/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 = {
+ preset: '@kbn/test',
+ rootDir: '../../..',
+ roots: ['/x-pack/plugins/search_inference_endpoints'],
+ coverageDirectory:
+ '/target/kibana-coverage/jest/x-pack/plugins/search_inference_endpoints',
+ coverageReporters: ['text', 'html'],
+ collectCoverageFrom: [
+ '/x-pack/plugins/search_inference_endpoints/{public,server}/**/*.{ts,tsx}',
+ ],
+};
diff --git a/x-pack/plugins/search_inference_endpoints/kibana.jsonc b/x-pack/plugins/search_inference_endpoints/kibana.jsonc
new file mode 100644
index 0000000000000..6cdca96205588
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/kibana.jsonc
@@ -0,0 +1,26 @@
+{
+ "type": "plugin",
+ "id": "@kbn/search-inference-endpoints",
+ "owner": "@elastic/search-kibana",
+ "plugin": {
+ "id": "searchInferenceEndpoints",
+ "server": true,
+ "browser": true,
+ "configPath": [
+ "xpack",
+ "searchInferenceEndpoints"
+ ],
+ "requiredPlugins": [
+ "actions",
+ "share",
+ ],
+ "optionalPlugins": [
+ "cloud",
+ "console",
+ "ml"
+ ],
+ "requiredBundles": [
+ "kibanaReact"
+ ]
+ }
+}
diff --git a/x-pack/plugins/search_inference_endpoints/public/application.tsx b/x-pack/plugins/search_inference_endpoints/public/application.tsx
new file mode 100644
index 0000000000000..c14f24a678281
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/application.tsx
@@ -0,0 +1,38 @@
+/*
+ * 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 ReactDOM from 'react-dom';
+import { 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 { AppPluginStartDependencies } from './types';
+
+export const renderApp = async (
+ core: CoreStart,
+ services: AppPluginStartDependencies,
+ element: HTMLElement
+) => {
+ const { InferenceEndpointsRouter } = await import('./inference_endpoints_router');
+
+ ReactDOM.render(
+
+
+
+
+
+
+
+
+ ,
+ element
+ );
+
+ return () => ReactDOM.unmountComponentAtNode(element);
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/assets/images/inference_endpoint.svg b/x-pack/plugins/search_inference_endpoints/public/assets/images/inference_endpoint.svg
new file mode 100644
index 0000000000000..927eccf9106a6
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/assets/images/inference_endpoint.svg
@@ -0,0 +1,4338 @@
+
+
\ No newline at end of file
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts
new file mode 100644
index 0000000000000..1b7e72149fd43
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/constants.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 {
+ SortFieldInferenceEndpoint,
+ QueryParams,
+ AlInferenceEndpointsTableState,
+ SortOrder,
+} from './types';
+
+export const DEFAULT_TABLE_ACTIVE_PAGE = 1;
+export const DEFAULT_TABLE_LIMIT = 10;
+
+export const DEFAULT_QUERY_PARAMS: QueryParams = {
+ page: DEFAULT_TABLE_ACTIVE_PAGE,
+ perPage: DEFAULT_TABLE_LIMIT,
+ sortField: SortFieldInferenceEndpoint.endpoint,
+ sortOrder: SortOrder.asc,
+};
+
+export const DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE: AlInferenceEndpointsTableState = {
+ queryParams: DEFAULT_QUERY_PARAMS,
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/endpoints_table.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/endpoints_table.tsx
new file mode 100644
index 0000000000000..af4755c72fc11
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/endpoints_table.tsx
@@ -0,0 +1,40 @@
+/*
+ * 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 type { EuiBasicTableProps, Pagination } from '@elastic/eui';
+import { EuiBasicTable } from '@elastic/eui';
+
+import type { InferenceEndpointUI } from './types';
+
+interface EndpointsTableProps {
+ columns: EuiBasicTableProps['columns'];
+ data: InferenceEndpointUI[];
+ onChange: EuiBasicTableProps['onChange'];
+ pagination: Pagination;
+ sorting: EuiBasicTableProps['sorting'];
+}
+
+export const EndpointsTable: React.FC = ({
+ columns,
+ data,
+ onChange,
+ pagination,
+ sorting,
+}) => {
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/table_columns.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/table_columns.ts
new file mode 100644
index 0000000000000..39e957f908684
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/table_columns.ts
@@ -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 { i18n } from '@kbn/i18n';
+
+export const TABLE_COLUMNS = [
+ {
+ field: 'endpoint',
+ name: i18n.translate('xpack.searchInferenceEndpoints.inferenceEndpoints.table.endpoint', {
+ defaultMessage: 'Endpoint',
+ }),
+ sortable: true,
+ width: '50%',
+ },
+ {
+ field: 'provider',
+ name: i18n.translate('xpack.searchInferenceEndpoints.inferenceEndpoints.table.provider', {
+ defaultMessage: 'Provider',
+ }),
+ sortable: false,
+ width: '110px',
+ },
+ {
+ field: 'type',
+ name: i18n.translate('xpack.searchInferenceEndpoints.inferenceEndpoints.table.type', {
+ defaultMessage: 'Type',
+ }),
+ sortable: false,
+ width: '90px',
+ },
+];
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx
new file mode 100644
index 0000000000000..091ff8270dd63
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.test.tsx
@@ -0,0 +1,49 @@
+/*
+ * 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 { screen } from '@testing-library/react';
+import { render } from '@testing-library/react';
+import { TabularPage } from './tabular_page';
+import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+
+const inferenceEndpoints = [
+ {
+ model_id: 'my-elser-model-05',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: {
+ num_allocations: 1,
+ num_threads: 1,
+ model_id: '.elser_model_2',
+ },
+ task_settings: {},
+ },
+ {
+ model_id: 'my-elser-model-04',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: {
+ num_allocations: 1,
+ num_threads: 1,
+ model_id: '.elser_model_2',
+ },
+ task_settings: {},
+ },
+] as InferenceAPIConfigResponse[];
+
+describe('When the tabular page is loaded', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('should display all model_ids in the table', () => {
+ const rows = screen.getAllByRole('row');
+ expect(rows[1]).toHaveTextContent('my-elser-model-04');
+ expect(rows[2]).toHaveTextContent('my-elser-model-05');
+ });
+});
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx
new file mode 100644
index 0000000000000..2fb84dc4b99de
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/tabular_page.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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, { useCallback } from 'react';
+
+import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+
+import { useTableData } from '../../hooks/use_table_data';
+
+import { useAllInferenceEndpointsState } from '../../hooks/use_all_inference_endpoints_state';
+import { EndpointsTable } from './endpoints_table';
+import { TABLE_COLUMNS } from './table_columns';
+
+interface TabularPageProps {
+ inferenceEndpoints: InferenceAPIConfigResponse[];
+}
+
+export const TabularPage: React.FC = ({ inferenceEndpoints }) => {
+ const { queryParams, setQueryParams } = useAllInferenceEndpointsState();
+
+ const { paginatedSortedTableData, pagination, sorting } = useTableData(
+ inferenceEndpoints,
+ queryParams
+ );
+
+ const handleTableChange = useCallback(
+ ({ page, sort }) => {
+ const newQueryParams = {
+ ...queryParams,
+ ...(sort && {
+ sortField: sort.field,
+ sortOrder: sort.direction,
+ }),
+ ...(page && {
+ page: page.index + 1,
+ perPage: page.size,
+ }),
+ };
+ setQueryParams(newQueryParams);
+ },
+ [queryParams, setQueryParams]
+ );
+
+ return (
+
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/types.ts b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/types.ts
new file mode 100644
index 0000000000000..4afba1dda110a
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/all_inference_endpoints/types.ts
@@ -0,0 +1,41 @@
+/*
+ * 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 const INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES = [10, 25, 50, 100];
+
+export enum SortFieldInferenceEndpoint {
+ endpoint = 'endpoint',
+}
+export enum SortOrder {
+ asc = 'asc',
+ desc = 'desc',
+}
+
+export interface SortingParams {
+ sortField: SortFieldInferenceEndpoint;
+ sortOrder: SortOrder;
+}
+
+export interface QueryParams extends SortingParams {
+ page: number;
+ perPage: number;
+}
+
+export interface AlInferenceEndpointsTableState {
+ queryParams: QueryParams;
+}
+
+export interface EuiBasicTableSortTypes {
+ direction: SortOrder;
+ field: string;
+}
+
+export interface InferenceEndpointUI {
+ endpoint: string;
+ provider: string;
+ type: string;
+}
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/app.tsx b/x-pack/plugins/search_inference_endpoints/public/components/app.tsx
new file mode 100644
index 0000000000000..30c74898680a0
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/app.tsx
@@ -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 React from 'react';
+import { InferenceEndpoints } from './inference_endpoints';
+
+export const App: React.FC = () => {
+ return ;
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss
new file mode 100644
index 0000000000000..b85859948b0be
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.scss
@@ -0,0 +1,3 @@
+.addEmptyPrompt {
+ max-width: 860px;
+}
\ No newline at end of file
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx
new file mode 100644
index 0000000000000..f9b7e11e48d94
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.test.tsx
@@ -0,0 +1,33 @@
+/*
+ * 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 { fireEvent, screen } from '@testing-library/react';
+import { AddEmptyPrompt } from './add_empty_prompt';
+
+import { renderReactTestingLibraryWithI18n as render } from '@kbn/test-jest-helpers';
+import '@testing-library/jest-dom';
+const setIsInferenceFlyoutVisibleMock = jest.fn();
+
+describe('When empty prompt is loaded', () => {
+ beforeEach(() => {
+ render();
+ });
+
+ it('should display the description for creation of the first inference endpoint', () => {
+ expect(
+ screen.getByText(
+ /Connect to your third-party model provider to create an inference endpoint for semantic search./
+ )
+ ).toBeInTheDocument();
+ });
+
+ it('calls setIsInferenceFlyoutVisible when the addInferenceEndpoint button is clicked', async () => {
+ fireEvent.click(screen.getByTestId('addEndpointButtonForEmptyPrompt'));
+ expect(setIsInferenceFlyoutVisibleMock).toHaveBeenCalled();
+ });
+});
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx
new file mode 100644
index 0000000000000..69d74724016d6
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/add_empty_prompt.tsx
@@ -0,0 +1,79 @@
+/*
+ * 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 {
+ EuiButton,
+ EuiEmptyPrompt,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiImage,
+ EuiSpacer,
+} from '@elastic/eui';
+
+import * as i18n from '../../../common/translations';
+
+import inferenceEndpoint from '../../assets/images/inference_endpoint.svg';
+
+import { ElserPrompt } from './elser_prompt';
+import { MultilingualE5Prompt } from './multilingual_e5_prompt';
+
+import './add_empty_prompt.scss';
+
+interface AddEmptyPromptProps {
+ setIsInferenceFlyoutVisible: (value: boolean) => void;
+}
+
+export const AddEmptyPrompt: React.FC = ({ setIsInferenceFlyoutVisible }) => {
+ return (
+ {i18n.INFERENCE_ENDPOINT_LABEL}}
+ body={
+
+
+ {i18n.CREATE_FIRST_INFERENCE_ENDPOINT_DESCRIPTION}
+
+
+
+ setIsInferenceFlyoutVisible(true)}
+ >
+ {i18n.ADD_ENDPOINT_LABEL}
+
+
+
+
+ }
+ footer={
+
+
+ {i18n.START_WITH_PREPARED_ENDPOINTS_LABEL}
+
+
+
+
+
+
+
+
+
+
+
+ }
+ color="plain"
+ hasBorder
+ icon={}
+ />
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/elser_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/elser_prompt.tsx
new file mode 100644
index 0000000000000..db38b649fd66e
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/elser_prompt.tsx
@@ -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 React from 'react';
+
+import { EuiButton, EuiCard } from '@elastic/eui';
+
+import * as i18n from '../../../common/translations';
+
+interface ElserPromptProps {
+ setIsInferenceFlyoutVisible: (value: boolean) => void;
+}
+export const ElserPrompt: React.FC = ({ setIsInferenceFlyoutVisible }) => (
+ setIsInferenceFlyoutVisible(true)}>
+ {i18n.ADD_ENDPOINT_LABEL}
+
+ }
+ />
+);
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts
new file mode 100644
index 0000000000000..dcb2d12f6c986
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 { AddEmptyPrompt } from './add_empty_prompt';
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/multilingual_e5_prompt.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/multilingual_e5_prompt.tsx
new file mode 100644
index 0000000000000..69133909efcb2
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt/multilingual_e5_prompt.tsx
@@ -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 React from 'react';
+
+import { EuiButton, EuiCard } from '@elastic/eui';
+
+import * as i18n from '../../../common/translations';
+
+interface MultilingualE5PromptProps {
+ setIsInferenceFlyoutVisible: (value: boolean) => void;
+}
+
+export const MultilingualE5Prompt: React.FC = ({
+ setIsInferenceFlyoutVisible,
+}) => (
+ setIsInferenceFlyoutVisible(true)}>
+ {i18n.ADD_ENDPOINT_LABEL}
+
+ }
+ />
+);
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt_page.tsx b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt_page.tsx
new file mode 100644
index 0000000000000..ec958266e4e1f
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/empty_prompt_page.tsx
@@ -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.
+ */
+
+import React from 'react';
+
+import { AddEmptyPrompt } from './empty_prompt/add_empty_prompt';
+
+interface EmptyPromptPageProps {
+ setIsInferenceFlyoutVisible: (value: boolean) => void;
+}
+
+export const EmptyPromptPage: React.FC = ({
+ setIsInferenceFlyoutVisible,
+}) => ;
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/endpoints_router.tsx b/x-pack/plugins/search_inference_endpoints/public/components/endpoints_router.tsx
new file mode 100644
index 0000000000000..775a4efdbc15c
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/endpoints_router.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 { Route, Routes } from '@kbn/shared-ux-router';
+
+import { INFERENCE_ENDPOINTS_PATH } from './routes';
+
+import { InferenceEndpoints } from './inference_endpoints';
+
+export const EndpointsRouter: React.FC = () => {
+ return (
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.tsx
new file mode 100644
index 0000000000000..0fa200991b8d1
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints.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, { useState } from 'react';
+
+import { EuiPageTemplate } from '@elastic/eui';
+
+import { useQueryInferenceEndpoints } from '../hooks/use_inference_endpoints';
+import { TabularPage } from './all_inference_endpoints/tabular_page';
+import { EmptyPromptPage } from './empty_prompt_page';
+import { InferenceEndpointsHeader } from './inference_endpoints_header';
+import { InferenceFlyoutWrapperComponent } from './inference_flyout_wrapper_component';
+
+export const InferenceEndpoints: React.FC = () => {
+ const { inferenceEndpoints } = useQueryInferenceEndpoints();
+ const [isInferenceFlyoutVisible, setIsInferenceFlyoutVisible] = useState(false);
+
+ return (
+ <>
+ {inferenceEndpoints.length > 0 && (
+
+ )}
+
+ {inferenceEndpoints.length === 0 ? (
+
+ ) : (
+
+ )}
+
+ {isInferenceFlyoutVisible && (
+
+ )}
+ >
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints_header.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints_header.tsx
new file mode 100644
index 0000000000000..8b551af28bd4c
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_endpoints_header.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { EuiPageTemplate, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
+import * as i18n from '../../common/translations';
+
+interface InferenceEndpointsHeaderProps {
+ setIsInferenceFlyoutVisible: (isVisible: boolean) => void;
+}
+
+export const InferenceEndpointsHeader: React.FC = ({
+ setIsInferenceFlyoutVisible,
+}) => (
+ .euiFlexGroup': { flexWrap: 'wrap' } }}
+ data-test-subj="allInferenceEndpointsPage"
+ pageTitle={i18n.INFERENCE_ENDPOINT_LABEL}
+ description={i18n.MANAGE_INFERENCE_ENDPOINTS_LABEL}
+ rightSideItems={[
+
+
+ setIsInferenceFlyoutVisible(true)}
+ >
+ {i18n.ADD_ENDPOINT_LABEL}
+
+
+ ,
+ ]}
+ />
+);
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/inference_flyout_wrapper_component.tsx b/x-pack/plugins/search_inference_endpoints/public/components/inference_flyout_wrapper_component.tsx
new file mode 100644
index 0000000000000..bf1343fe3db8b
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/inference_flyout_wrapper_component.tsx
@@ -0,0 +1,172 @@
+/*
+ * 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 { EuiFlexItem, EuiCallOut, EuiText, EuiSpacer } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { InferenceFlyoutWrapper } from '@kbn/inference_integration_flyout/components/inference_flyout_wrapper';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+
+import { InferenceTaskType } from '@elastic/elasticsearch/lib/api/types';
+
+import { ModelConfig } from '@kbn/inference_integration_flyout/types';
+import { extractErrorProperties } from '@kbn/ml-error-utils';
+import { TrainedModelConfigResponse } from '@kbn/ml-plugin/common/types/trained_models';
+import { SUPPORTED_PYTORCH_TASKS, TRAINED_MODEL_TYPE } from '@kbn/ml-trained-models-utils';
+import { useMutation, useQueryClient } from '@tanstack/react-query';
+import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+import * as i18n from '../../common/translations';
+import { INFERENCE_ENDPOINTS_QUERY_KEY } from '../../common/constants';
+import { useKibana } from '../hooks/use_kibana';
+import { docLinks } from '../../common/doc_links';
+
+interface InferenceFlyoutWrapperComponentProps {
+ inferenceEndpoints: InferenceAPIConfigResponse[];
+ isInferenceFlyoutVisible: boolean;
+ setIsInferenceFlyoutVisible: (isVisible: boolean) => void;
+}
+
+export const InferenceFlyoutWrapperComponent: React.FC = ({
+ inferenceEndpoints,
+ isInferenceFlyoutVisible,
+ setIsInferenceFlyoutVisible,
+}) => {
+ const [inferenceAddError, setInferenceAddError] = useState(undefined);
+ const [isCreateInferenceApiLoading, setIsCreateInferenceApiLoading] = useState(false);
+ const [availableTrainedModels, setAvailableTrainedModels] = useState<
+ TrainedModelConfigResponse[]
+ >([]);
+
+ const [inferenceEndpointError, setInferenceEndpointError] = useState(
+ undefined
+ );
+
+ const queryClient = useQueryClient();
+
+ const {
+ services: { ml },
+ } = useKibana();
+
+ const createInferenceEndpointMutation = useMutation(
+ async ({
+ inferenceId,
+ taskType,
+ modelConfig,
+ }: {
+ inferenceId: string;
+ taskType: InferenceTaskType;
+ modelConfig: ModelConfig;
+ }) => {
+ if (!ml) {
+ throw new Error(i18n.UNABLE_TO_CREATE_INFERENCE_ENDPOINT);
+ }
+ await ml?.mlApi?.inferenceModels?.createInferenceEndpoint(inferenceId, taskType, modelConfig);
+ },
+ {
+ onSuccess: () => {
+ queryClient.invalidateQueries([INFERENCE_ENDPOINTS_QUERY_KEY]);
+ },
+ }
+ );
+
+ const onInferenceEndpointChange = useCallback(
+ async (inferenceId: string) => {
+ const modelsExist = inferenceEndpoints.some((i) => i.model_id === inferenceId);
+ if (modelsExist) {
+ setInferenceEndpointError(i18n.INFERENCE_ENDPOINT_ALREADY_EXISTS);
+ } else {
+ setInferenceEndpointError(undefined);
+ }
+ },
+ [inferenceEndpoints]
+ );
+
+ const onSaveInferenceCallback = useCallback(
+ async (inferenceId: string, taskType: InferenceTaskType, modelConfig: ModelConfig) => {
+ setIsCreateInferenceApiLoading(true);
+ try {
+ await createInferenceEndpointMutation.mutateAsync({ inferenceId, taskType, modelConfig });
+ setIsInferenceFlyoutVisible(!isInferenceFlyoutVisible);
+ } catch (error) {
+ const errorObj = extractErrorProperties(error);
+ setInferenceAddError(errorObj.message);
+ } finally {
+ setIsCreateInferenceApiLoading(false);
+ }
+ },
+ [createInferenceEndpointMutation, isInferenceFlyoutVisible, setIsInferenceFlyoutVisible]
+ );
+
+ const onFlyoutClose = useCallback(() => {
+ setInferenceAddError(undefined);
+ setIsInferenceFlyoutVisible(!isInferenceFlyoutVisible);
+ }, [isInferenceFlyoutVisible, setIsInferenceFlyoutVisible]);
+
+ useEffect(() => {
+ const fetchAvailableTrainedModels = async () => {
+ let models;
+ try {
+ models = await ml?.mlApi?.trainedModels?.getTrainedModels();
+ } catch (error) {
+ const errorObj = extractErrorProperties(error);
+ if (errorObj.statusCode === 403) {
+ setInferenceAddError(i18n.FORBIDDEN_TO_ACCESS_TRAINED_MODELS);
+ } else {
+ setInferenceAddError(errorObj.message);
+ }
+ } finally {
+ setAvailableTrainedModels(models || []);
+ }
+ };
+
+ fetchAvailableTrainedModels();
+ }, [ml]);
+
+ const trainedModels = useMemo(() => {
+ const availableTrainedModelsList = availableTrainedModels
+ .filter(
+ (model: TrainedModelConfigResponse) =>
+ model.model_type === TRAINED_MODEL_TYPE.PYTORCH &&
+ (model?.inference_config
+ ? Object.keys(model.inference_config).includes(SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING)
+ : {})
+ )
+ .map((model: TrainedModelConfigResponse) => model.model_id);
+
+ return availableTrainedModelsList;
+ }, [availableTrainedModels]);
+
+ return (
+
+
+
+
+
+
+
+
+ )
+ }
+ onInferenceEndpointChange={onInferenceEndpointChange}
+ inferenceEndpointError={inferenceEndpointError}
+ trainedModels={trainedModels}
+ onSaveInferenceEndpoint={onSaveInferenceCallback}
+ onFlyoutClose={onFlyoutClose}
+ isInferenceFlyoutVisible={isInferenceFlyoutVisible}
+ supportedNlpModels={docLinks.supportedNlpModels}
+ nlpImportModel={docLinks.nlpImportModel}
+ isCreateInferenceApiLoading={isCreateInferenceApiLoading}
+ setInferenceEndpointError={setInferenceEndpointError}
+ />
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/components/routes.ts b/x-pack/plugins/search_inference_endpoints/public/components/routes.ts
new file mode 100644
index 0000000000000..032b8c96e43d0
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/components/routes.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+export const ROOT_PATH = '/';
+export const SETUP_GUIDE_PATH = '/setup_guide';
+export const ERROR_STATE_PATH = '/error_state';
+export const INFERENCE_ENDPOINTS_PATH = `${ROOT_PATH}inference_endpoints`;
diff --git a/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx b/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx
new file mode 100644
index 0000000000000..fb733ec7ff5e6
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/embeddable.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 { dynamic } from '@kbn/shared-ux-utility';
+import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
+import { CoreStart } from '@kbn/core-lifecycle-browser';
+import { AppPluginStartDependencies } from './types';
+
+export const InferenceEndpoints = dynamic(async () => ({
+ default: (await import('./components/app')).App,
+}));
+
+export const InferenceEndpointsProvider = dynamic(async () => ({
+ default: (await import('./providers/inference_endpoints_provider')).InferenceEndpointsProvider,
+}));
+
+export const getInferenceEndpointsProvider =
+ (core: CoreStart, services: AppPluginStartDependencies) =>
+ (props: React.ComponentProps) =>
+ (
+
+
+
+ );
diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_all_inference_endpoints_state.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_all_inference_endpoints_state.tsx
new file mode 100644
index 0000000000000..4979cdf7994fc
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_all_inference_endpoints_state.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { useCallback, useState } from 'react';
+
+import type {
+ QueryParams,
+ AlInferenceEndpointsTableState,
+} from '../components/all_inference_endpoints/types';
+
+import { DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE } from '../components/all_inference_endpoints/constants';
+
+interface UseAllInferenceEndpointsStateReturn {
+ queryParams: QueryParams;
+ setQueryParams: (queryParam: Partial) => void;
+}
+
+export function useAllInferenceEndpointsState(): UseAllInferenceEndpointsStateReturn {
+ const [tableState, setTableState] = useState(
+ DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE
+ );
+ const setState = useCallback((state: AlInferenceEndpointsTableState) => {
+ setTableState(state);
+ }, []);
+
+ return {
+ queryParams: {
+ ...DEFAULT_INFERENCE_ENDPOINTS_TABLE_STATE.queryParams,
+ ...tableState.queryParams,
+ },
+ setQueryParams: (newQueryParams: Partial) => {
+ setState({
+ queryParams: { ...tableState.queryParams, ...newQueryParams },
+ });
+ },
+ };
+}
diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts
new file mode 100644
index 0000000000000..f400429bec250
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_inference_endpoints.ts
@@ -0,0 +1,32 @@
+/*
+ * 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 { useQuery } from '@tanstack/react-query';
+import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+import { APIRoutes } from '../types';
+import { useKibana } from './use_kibana';
+import { INFERENCE_ENDPOINTS_QUERY_KEY } from '../../common/constants';
+
+export const useQueryInferenceEndpoints = (): {
+ inferenceEndpoints: InferenceAPIConfigResponse[];
+ isLoading: boolean;
+} => {
+ const { services } = useKibana();
+
+ const { data, isLoading } = useQuery({
+ queryKey: [INFERENCE_ENDPOINTS_QUERY_KEY],
+ queryFn: async () => {
+ const response = await services.http.get<{
+ inference_endpoints: InferenceAPIConfigResponse[];
+ }>(APIRoutes.GET_INFERENCE_ENDPOINTS, {});
+
+ return response.inference_endpoints;
+ },
+ });
+
+ return { inferenceEndpoints: data || [], isLoading };
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_kibana.ts b/x-pack/plugins/search_inference_endpoints/public/hooks/use_kibana.ts
new file mode 100644
index 0000000000000..90fa7597e17dc
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_kibana.ts
@@ -0,0 +1,11 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { useKibana as _useKibana } from '@kbn/kibana-react-plugin/public';
+import { AppServicesContext } from '../types';
+
+export const useKibana = () => _useKibana();
diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx
new file mode 100644
index 0000000000000..e0026cbffff98
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.test.tsx
@@ -0,0 +1,92 @@
+/*
+ * 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 { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+import { renderHook } from '@testing-library/react-hooks';
+import { QueryParams } from '../components/all_inference_endpoints/types';
+import { useTableData } from './use_table_data';
+import { INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES } from '../components/all_inference_endpoints/types';
+
+const inferenceEndpoints = [
+ {
+ model_id: 'my-elser-model-04',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: {
+ num_allocations: 1,
+ num_threads: 1,
+ model_id: '.elser_model_2',
+ },
+ task_settings: {},
+ },
+ {
+ model_id: 'my-elser-model-01',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: {
+ num_allocations: 1,
+ num_threads: 1,
+ model_id: '.elser_model_2',
+ },
+ task_settings: {},
+ },
+ {
+ model_id: 'my-elser-model-05',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: {
+ num_allocations: 1,
+ num_threads: 1,
+ model_id: '.elser_model_2',
+ },
+ task_settings: {},
+ },
+] as InferenceAPIConfigResponse[];
+
+const queryParams = {
+ page: 1,
+ perPage: 10,
+ sortField: 'endpoint',
+ sortOrder: 'desc',
+} as QueryParams;
+
+describe('useTableData', () => {
+ it('should return correct pagination', () => {
+ const { result } = renderHook(() => useTableData(inferenceEndpoints, queryParams));
+
+ expect(result.current.pagination).toEqual({
+ pageIndex: 0,
+ pageSize: 10,
+ pageSizeOptions: INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES,
+ totalItemCount: 3,
+ });
+ });
+
+ it('should return correct sorting', () => {
+ const { result } = renderHook(() => useTableData(inferenceEndpoints, queryParams));
+
+ expect(result.current.sorting).toEqual({
+ sort: {
+ direction: 'desc',
+ field: 'endpoint',
+ },
+ });
+ });
+
+ it('should return correctly sorted data', () => {
+ const { result } = renderHook(() => useTableData(inferenceEndpoints, queryParams));
+
+ const expectedSortedData = [...inferenceEndpoints].sort((a, b) =>
+ b.model_id.localeCompare(a.model_id)
+ );
+
+ const sortedEndpoints = result.current.sortedTableData.map((item) => item.endpoint);
+ const expectedModelIds = expectedSortedData.map((item) => item.model_id);
+
+ expect(sortedEndpoints).toEqual(expectedModelIds);
+ });
+});
diff --git a/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx
new file mode 100644
index 0000000000000..304c469e06093
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/hooks/use_table_data.tsx
@@ -0,0 +1,81 @@
+/*
+ * 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 { EuiTableSortingType } from '@elastic/eui';
+import { Pagination } from '@elastic/eui';
+import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+import { useMemo } from 'react';
+import { DEFAULT_TABLE_LIMIT } from '../components/all_inference_endpoints/constants';
+import {
+ InferenceEndpointUI,
+ INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES,
+ QueryParams,
+ SortOrder,
+} from '../components/all_inference_endpoints/types';
+
+interface UseTableDataReturn {
+ tableData: InferenceEndpointUI[];
+ sortedTableData: InferenceEndpointUI[];
+ paginatedSortedTableData: InferenceEndpointUI[];
+ pagination: Pagination;
+ sorting: EuiTableSortingType;
+}
+
+export const useTableData = (
+ inferenceEndpoints: InferenceAPIConfigResponse[],
+ queryParams: QueryParams
+): UseTableDataReturn => {
+ const tableData: InferenceEndpointUI[] = useMemo(() => {
+ return inferenceEndpoints.map((endpoint) => ({
+ endpoint: endpoint.model_id,
+ provider: endpoint.service,
+ type: endpoint.task_type,
+ }));
+ }, [inferenceEndpoints]);
+
+ const sortedTableData: InferenceEndpointUI[] = useMemo(() => {
+ return [...tableData].sort((a, b) => {
+ const aValue = a[queryParams.sortField];
+ const bValue = b[queryParams.sortField];
+
+ if (queryParams.sortOrder === SortOrder.asc) {
+ return aValue.localeCompare(bValue);
+ } else {
+ return bValue.localeCompare(aValue);
+ }
+ });
+ }, [tableData, queryParams]);
+
+ const pagination: Pagination = useMemo(
+ () => ({
+ pageIndex: queryParams.page - 1,
+ pageSize: queryParams.perPage,
+ pageSizeOptions: INFERENCE_ENDPOINTS_TABLE_PER_PAGE_VALUES,
+ totalItemCount: inferenceEndpoints.length ?? 0,
+ }),
+ [inferenceEndpoints, queryParams]
+ );
+
+ const paginatedSortedTableData: InferenceEndpointUI[] = useMemo(() => {
+ const pageSize = pagination.pageSize || DEFAULT_TABLE_LIMIT;
+ const startIndex = pagination.pageIndex * pageSize;
+ const endIndex = startIndex + pageSize;
+ return sortedTableData.slice(startIndex, endIndex);
+ }, [sortedTableData, pagination]);
+
+ const sorting = useMemo(
+ () => ({
+ sort: {
+ direction: queryParams.sortOrder,
+ field: queryParams.sortField,
+ },
+ }),
+ [queryParams.sortField, queryParams.sortOrder]
+ );
+
+ return { tableData, sortedTableData, paginatedSortedTableData, pagination, sorting };
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/index.ts b/x-pack/plugins/search_inference_endpoints/public/index.ts
new file mode 100644
index 0000000000000..b06f1f64b909d
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/index.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 { PluginInitializerContext } from '@kbn/core/public';
+
+import { SearchInferenceEndpointsPlugin } from './plugin';
+
+export function plugin(initializerContext: PluginInitializerContext) {
+ return new SearchInferenceEndpointsPlugin(initializerContext);
+}
+
+export type {
+ SearchInferenceEndpointsPluginSetup,
+ SearchInferenceEndpointsPluginStart,
+} from './types';
diff --git a/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx
new file mode 100644
index 0000000000000..5dd017f263caa
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_overview.tsx
@@ -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 React, { useMemo } from 'react';
+
+import { EuiPageTemplate } from '@elastic/eui';
+
+import { App } from './components/app';
+import { useKibana } from './hooks/use_kibana';
+import { InferenceEndpointsProvider } from './providers/inference_endpoints_provider';
+
+export const InferenceEndpointsOverview: React.FC = () => {
+ const {
+ services: { console: consolePlugin },
+ } = useKibana();
+
+ const embeddableConsole = useMemo(
+ () => (consolePlugin?.EmbeddableConsole ? : null),
+ [consolePlugin]
+ );
+
+ return (
+
+
+
+ {embeddableConsole}
+
+
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_router.tsx b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_router.tsx
new file mode 100644
index 0000000000000..7165b01993232
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/inference_endpoints_router.tsx
@@ -0,0 +1,24 @@
+/*
+ * 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 { Redirect } from 'react-router-dom';
+import { InferenceEndpointsOverview } from './inference_endpoints_overview';
+
+import { ROOT_PATH, SEARCH_INFERENCE_ENDPOINTS_PATH } from './routes';
+
+export const InferenceEndpointsRouter: React.FC = () => {
+ return (
+
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/plugin.ts b/x-pack/plugins/search_inference_endpoints/public/plugin.ts
new file mode 100644
index 0000000000000..ab49281f82e4c
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/plugin.ts
@@ -0,0 +1,71 @@
+/*
+ * 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 {
+ AppMountParameters,
+ CoreSetup,
+ CoreStart,
+ Plugin,
+ PluginInitializerContext,
+} from '@kbn/core/public';
+import { PLUGIN_ID, PLUGIN_NAME } from '../common/constants';
+import { docLinks } from '../common/doc_links';
+import { InferenceEndpoints, getInferenceEndpointsProvider } from './embeddable';
+import {
+ AppPluginStartDependencies,
+ SearchInferenceEndpointsConfigType,
+ SearchInferenceEndpointsPluginSetup,
+ SearchInferenceEndpointsPluginStart,
+} from './types';
+
+export class SearchInferenceEndpointsPlugin
+ implements Plugin
+{
+ private config: SearchInferenceEndpointsConfigType;
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this.config = initializerContext.config.get();
+ }
+
+ public setup(
+ core: CoreSetup
+ ): SearchInferenceEndpointsPluginSetup {
+ if (!this.config.ui?.enabled) return {};
+
+ core.application.register({
+ id: PLUGIN_ID,
+ appRoute: '/app/search_inference_endpoints',
+ title: PLUGIN_NAME,
+ async mount({ element, history }: AppMountParameters) {
+ const { renderApp } = await import('./application');
+ const [coreStart, depsStart] = await core.getStartServices();
+ const startDeps: AppPluginStartDependencies = {
+ ...depsStart,
+ history,
+ };
+
+ return renderApp(coreStart, startDeps, element);
+ },
+ });
+
+ return {};
+ }
+
+ public start(
+ core: CoreStart,
+ deps: AppPluginStartDependencies
+ ): SearchInferenceEndpointsPluginStart {
+ docLinks.setDocLinks(core.docLinks.links);
+
+ return {
+ InferenceEdnpointsProvider: getInferenceEndpointsProvider(core, deps),
+ InferenceEndpoints,
+ };
+ }
+
+ public stop() {}
+}
diff --git a/x-pack/plugins/search_inference_endpoints/public/providers/inference_endpoints_provider.tsx b/x-pack/plugins/search_inference_endpoints/public/providers/inference_endpoints_provider.tsx
new file mode 100644
index 0000000000000..1813fbae2901e
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/providers/inference_endpoints_provider.tsx
@@ -0,0 +1,21 @@
+/*
+ * 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 { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import React, { FC, PropsWithChildren } from 'react';
+
+const queryClient = new QueryClient({});
+
+export interface InferenceEndpointsProviderProps {
+ children: React.ReactNode;
+}
+
+export const InferenceEndpointsProvider: FC> = ({
+ children,
+}) => {
+ return {children};
+};
diff --git a/x-pack/plugins/search_inference_endpoints/public/routes.ts b/x-pack/plugins/search_inference_endpoints/public/routes.ts
new file mode 100644
index 0000000000000..d2904473400ee
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/routes.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.
+ */
+
+export const ROOT_PATH = '/';
+export const SEARCH_INFERENCE_ENDPOINTS_PATH = `${ROOT_PATH}inference_endpoints`;
diff --git a/x-pack/plugins/search_inference_endpoints/public/types.ts b/x-pack/plugins/search_inference_endpoints/public/types.ts
new file mode 100644
index 0000000000000..6ac241fbe87b0
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/public/types.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 { ConsolePluginStart } from '@kbn/console-plugin/public';
+import { HttpStart } from '@kbn/core-http-browser';
+import { AppMountParameters } from '@kbn/core/public';
+import { MlPluginStart } from '@kbn/ml-plugin/public';
+import { SharePluginStart } from '@kbn/share-plugin/public';
+import React from 'react';
+import type { App } from './components/app';
+import type { InferenceEndpointsProvider } from './providers/inference_endpoints_provider';
+
+export * from '../common/types';
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface SearchInferenceEndpointsPluginSetup {}
+export interface SearchInferenceEndpointsPluginStart {
+ InferenceEdnpointsProvider: React.FC>;
+ InferenceEndpoints: React.FC>;
+}
+
+export interface AppPluginStartDependencies {
+ history: AppMountParameters['history'];
+ share: SharePluginStart;
+ console?: ConsolePluginStart;
+}
+
+export interface AppServicesContext {
+ http: HttpStart;
+ ml?: MlPluginStart;
+ console?: ConsolePluginStart;
+}
diff --git a/x-pack/plugins/search_inference_endpoints/server/config.ts b/x-pack/plugins/search_inference_endpoints/server/config.ts
new file mode 100644
index 0000000000000..5593fb2251aba
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/config.ts
@@ -0,0 +1,27 @@
+/*
+ * 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 { schema, TypeOf } from '@kbn/config-schema';
+import { PluginConfigDescriptor } from '@kbn/core/server';
+
+export * from './types';
+
+const configSchema = schema.object({
+ enabled: schema.boolean({ defaultValue: true }),
+ ui: schema.object({
+ enabled: schema.boolean({ defaultValue: false }),
+ }),
+});
+
+export type SearchInferenceEndpointsConfig = TypeOf;
+
+export const config: PluginConfigDescriptor = {
+ exposeToBrowser: {
+ ui: true,
+ },
+ schema: configSchema,
+};
diff --git a/x-pack/plugins/search_inference_endpoints/server/index.ts b/x-pack/plugins/search_inference_endpoints/server/index.ts
new file mode 100644
index 0000000000000..10fa965c40457
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/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 { PluginInitializerContext } from '@kbn/core/server';
+
+export { config } from './config';
+
+export async function plugin(initializerContext: PluginInitializerContext) {
+ const { SearchInferenceEndpointsPlugin } = await import('./plugin');
+ return new SearchInferenceEndpointsPlugin(initializerContext);
+}
+
+export type {
+ SearchInferenceEndpointsPluginSetup,
+ SearchInferenceEndpointsPluginStart,
+} from './types';
diff --git a/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.test.ts b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.test.ts
new file mode 100644
index 0000000000000..ee282f3056caa
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.test.ts
@@ -0,0 +1,68 @@
+/*
+ * 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 { ElasticsearchClient } from '@kbn/core/server';
+
+import { fetchInferenceEndpoints } from './fetch_inference_endpoints';
+
+describe('fetch indices', () => {
+ const mockInferenceEndpointsResponse = [
+ {
+ model_id: 'my-elser-model-03',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' },
+ task_settings: {},
+ },
+ {
+ model_id: 'my-elser-model-04',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' },
+ task_settings: {},
+ },
+ {
+ model_id: 'my-elser-model-05',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' },
+ task_settings: {},
+ },
+ {
+ model_id: 'my-elser-model-06',
+ task_type: 'sparse_embedding',
+ service: 'elser',
+ service_settings: { num_allocations: 1, num_threads: 1, model_id: '.elser_model_2' },
+ task_settings: {},
+ },
+ ];
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+
+ const mockClient = {
+ asCurrentUser: {
+ transport: {
+ request: jest.fn(),
+ },
+ },
+ };
+
+ it('returns all inference endpoints', async () => {
+ mockClient.asCurrentUser.transport.request.mockImplementationOnce(() => {
+ return Promise.resolve({ endpoints: mockInferenceEndpointsResponse });
+ });
+
+ const indexData = await fetchInferenceEndpoints(
+ mockClient.asCurrentUser as unknown as ElasticsearchClient
+ );
+
+ expect(indexData).toEqual({
+ inferenceEndpoints: mockInferenceEndpointsResponse,
+ });
+ });
+});
diff --git a/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.ts b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.ts
new file mode 100644
index 0000000000000..69b38f0d3bdb3
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/lib/fetch_inference_endpoints.ts
@@ -0,0 +1,26 @@
+/*
+ * 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 { ElasticsearchClient } from '@kbn/core/server';
+import { InferenceAPIConfigResponse } from '@kbn/ml-trained-models-utils';
+
+export const fetchInferenceEndpoints = async (
+ client: ElasticsearchClient
+): Promise<{
+ inferenceEndpoints: InferenceAPIConfigResponse[];
+}> => {
+ const { endpoints } = await client.transport.request<{
+ endpoints: any;
+ }>({
+ method: 'GET',
+ path: `/_inference/_all`,
+ });
+
+ return {
+ inferenceEndpoints: endpoints,
+ };
+};
diff --git a/x-pack/plugins/search_inference_endpoints/server/plugin.ts b/x-pack/plugins/search_inference_endpoints/server/plugin.ts
new file mode 100644
index 0000000000000..74a877824956f
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/plugin.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from '@kbn/core/server';
+import { defineRoutes } from './routes';
+import {
+ SearchInferenceEndpointsPluginSetup,
+ SearchInferenceEndpointsPluginStart,
+ SearchInferenceEndpointsPluginStartDependencies,
+} from './types';
+
+export class SearchInferenceEndpointsPlugin
+ implements
+ Plugin<
+ SearchInferenceEndpointsPluginSetup,
+ SearchInferenceEndpointsPluginStart,
+ {},
+ SearchInferenceEndpointsPluginStartDependencies
+ >
+{
+ private readonly logger: Logger;
+
+ constructor(initializerContext: PluginInitializerContext) {
+ this.logger = initializerContext.logger.get();
+ }
+
+ public setup(
+ core: CoreSetup<
+ SearchInferenceEndpointsPluginStartDependencies,
+ SearchInferenceEndpointsPluginStart
+ >
+ ) {
+ this.logger.debug('searchInferenceEndpoints: Setup');
+ const router = core.http.createRouter();
+
+ defineRoutes({ logger: this.logger, router });
+
+ return {};
+ }
+
+ public start(core: CoreStart) {
+ return {};
+ }
+
+ public stop() {}
+}
diff --git a/x-pack/plugins/search_inference_endpoints/server/routes.ts b/x-pack/plugins/search_inference_endpoints/server/routes.ts
new file mode 100644
index 0000000000000..d5f010b902c52
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/routes.ts
@@ -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 { IRouter } from '@kbn/core/server';
+import type { Logger } from '@kbn/logging';
+import { fetchInferenceEndpoints } from './lib/fetch_inference_endpoints';
+import { APIRoutes } from './types';
+import { errorHandler } from './utils/error_handler';
+
+export function defineRoutes({ logger, router }: { logger: Logger; router: IRouter }) {
+ router.get(
+ {
+ path: APIRoutes.GET_INFERENCE_ENDPOINTS,
+ validate: {},
+ },
+ errorHandler(logger)(async (context, request, response) => {
+ const {
+ client: { asCurrentUser },
+ } = (await context.core).elasticsearch;
+
+ const { inferenceEndpoints } = await fetchInferenceEndpoints(asCurrentUser);
+
+ return response.ok({
+ body: {
+ inference_endpoints: inferenceEndpoints,
+ },
+ headers: { 'content-type': 'application/json' },
+ });
+ })
+ );
+}
diff --git a/x-pack/plugins/search_inference_endpoints/server/types.ts b/x-pack/plugins/search_inference_endpoints/server/types.ts
new file mode 100644
index 0000000000000..fcc6ffa55ec0f
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/types.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 { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server';
+
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface SearchInferenceEndpointsPluginSetup {}
+// eslint-disable-next-line @typescript-eslint/no-empty-interface
+export interface SearchInferenceEndpointsPluginStart {}
+
+export interface SearchInferenceEndpointsPluginStartDependencies {
+ actions: ActionsPluginStartContract;
+}
+
+export * from '../common/types';
diff --git a/x-pack/plugins/search_inference_endpoints/server/utils/error_handler.ts b/x-pack/plugins/search_inference_endpoints/server/utils/error_handler.ts
new file mode 100644
index 0000000000000..3772786335fa8
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/server/utils/error_handler.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 { RequestHandlerWrapper } from '@kbn/core-http-server';
+import type { Logger } from '@kbn/logging';
+
+export const errorHandler: (logger: Logger) => RequestHandlerWrapper = (logger) => (handler) => {
+ return async (context, request, response) => {
+ try {
+ return await handler(context, request, response);
+ } catch (e) {
+ logger.error(e);
+ throw e;
+ }
+ };
+};
diff --git a/x-pack/plugins/search_inference_endpoints/tsconfig.json b/x-pack/plugins/search_inference_endpoints/tsconfig.json
new file mode 100644
index 0000000000000..a61af6c752250
--- /dev/null
+++ b/x-pack/plugins/search_inference_endpoints/tsconfig.json
@@ -0,0 +1,38 @@
+{
+ "extends": "../../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "target/types",
+ },
+ "include": [
+ "__mocks__/**/*",
+ "common/**/*",
+ "public/**/*",
+ "server/**/*"
+ ],
+ "kbn_references": [
+ "@kbn/config-schema",
+ "@kbn/core",
+ "@kbn/core-http-browser",
+ "@kbn/i18n",
+ "@kbn/i18n-react",
+ "@kbn/kibana-react-plugin",
+ "@kbn/inference_integration_flyout",
+ "@kbn/ml-plugin",
+ "@kbn/ml-trained-models-utils",
+ "@kbn/shared-ux-router",
+ "@kbn/core-http-server",
+ "@kbn/share-plugin",
+ "@kbn/actions-plugin",
+ "@kbn/shared-ux-utility",
+ "@kbn/core-lifecycle-browser",
+ "@kbn/logging",
+ "@kbn/react-kibana-context-render",
+ "@kbn/doc-links",
+ "@kbn/console-plugin",
+ "@kbn/test-jest-helpers",
+ "@kbn/ml-error-utils"
+ ],
+ "exclude": [
+ "target/**/*",
+ ]
+}
diff --git a/x-pack/plugins/serverless_search/kibana.jsonc b/x-pack/plugins/serverless_search/kibana.jsonc
index fc298a895174e..3a98a87c032a4 100644
--- a/x-pack/plugins/serverless_search/kibana.jsonc
+++ b/x-pack/plugins/serverless_search/kibana.jsonc
@@ -29,6 +29,7 @@
"optionalPlugins": [
"indexManagement",
"searchConnectors",
+ "searchInferenceEndpoints",
"usageCollection"
],
"requiredBundles": [
diff --git a/x-pack/plugins/serverless_search/public/navigation_tree.ts b/x-pack/plugins/serverless_search/public/navigation_tree.ts
index 5878031d99dc3..d21eee8de9c19 100644
--- a/x-pack/plugins/serverless_search/public/navigation_tree.ts
+++ b/x-pack/plugins/serverless_search/public/navigation_tree.ts
@@ -93,6 +93,25 @@ export const navigationTree: NavigationTreeDefinition = {
},
],
},
+ {
+ id: 'relevance',
+ title: i18n.translate('xpack.serverlessSearch.nav.relevance', {
+ defaultMessage: 'Relevance',
+ }),
+ spaceBefore: 'm',
+ children: [
+ {
+ id: 'searchInferenceEndpoints',
+ title: i18n.translate(
+ 'xpack.serverlessSearch.nav.relevance.searchInferenceEndpoints',
+ {
+ defaultMessage: 'Inference Endpoints',
+ }
+ ),
+ link: 'searchInferenceEndpoints',
+ },
+ ],
+ },
],
},
],
diff --git a/x-pack/plugins/serverless_search/public/types.ts b/x-pack/plugins/serverless_search/public/types.ts
index 5b502eb4f0d9e..e4eab5fbfd61d 100644
--- a/x-pack/plugins/serverless_search/public/types.ts
+++ b/x-pack/plugins/serverless_search/public/types.ts
@@ -8,6 +8,7 @@
import { CloudSetup, CloudStart } from '@kbn/cloud-plugin/public';
import { ConsolePluginStart } from '@kbn/console-plugin/public';
import { SearchPlaygroundPluginStart } from '@kbn/search-playground/public';
+import { SearchInferenceEndpointsPluginStart } from '@kbn/search-inference-endpoints/public';
import { ManagementSetup, ManagementStart } from '@kbn/management-plugin/public';
import { SecurityPluginStart } from '@kbn/security-plugin/public';
import { ServerlessPluginSetup, ServerlessPluginStart } from '@kbn/serverless/public';
@@ -32,6 +33,7 @@ export interface ServerlessSearchPluginStartDependencies {
cloud: CloudStart;
console: ConsolePluginStart;
searchPlayground: SearchPlaygroundPluginStart;
+ searchInferenceEndpoints?: SearchInferenceEndpointsPluginStart;
management: ManagementStart;
security: SecurityPluginStart;
serverless: ServerlessPluginStart;
diff --git a/x-pack/plugins/serverless_search/tsconfig.json b/x-pack/plugins/serverless_search/tsconfig.json
index f7d7fb3444554..8cf2dec121d46 100644
--- a/x-pack/plugins/serverless_search/tsconfig.json
+++ b/x-pack/plugins/serverless_search/tsconfig.json
@@ -49,5 +49,6 @@
"@kbn/index-management",
"@kbn/react-kibana-context-render",
"@kbn/search-playground",
+ "@kbn/search-inference-endpoints",
]
}
diff --git a/yarn.lock b/yarn.lock
index 27e604d8f4b6a..61bd27eecdc9a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6024,6 +6024,10 @@
version "0.0.0"
uid ""
+"@kbn/search-inference-endpoints@link:x-pack/plugins/search_inference_endpoints":
+ version "0.0.0"
+ uid ""
+
"@kbn/search-notebooks@link:x-pack/plugins/search_notebooks":
version "0.0.0"
uid ""