diff --git a/packages/kbn-ai-playground/components/sources_panel/sources_panel_for_start_chat.tsx b/packages/kbn-ai-playground/components/sources_panel/sources_panel_for_start_chat.tsx index 6e6c4aafc4c09..c85aed881f8a3 100644 --- a/packages/kbn-ai-playground/components/sources_panel/sources_panel_for_start_chat.tsx +++ b/packages/kbn-ai-playground/components/sources_panel/sources_panel_for_start_chat.tsx @@ -37,7 +37,7 @@ export const SourcesPanelForStartChat: React.FC = () => { const defaultFields = getDefaultQueryFields(fields); elasticsearchQueryOnChange(createQuery(defaultFields, fields)); } - }, [selectedIndices, fields, elasticsearchQueryOnChange]); + }, [fields, elasticsearchQueryOnChange]); return ( { + const { http } = useKibana().services; + const managementApiKeysLinks = http.basePath.prepend('/app/management/security/api_keys'); + const { + control, + getValues, + reset, + formState: { isDirty, isValid }, + handleSubmit, + } = useForm(); + const { action, isLoading, isSuccess, isError } = useCreateApiKeyQuery(); + const onSubmit = async (data: ApiKeyForm) => { + await action(data); + + reset(getValues()); + }; + + return ( + + + ( + + )} + /> + + + + ( + + + + + + } + > + + + )} + /> + + + + + {isSuccess && !isDirty ? ( + + + + ) : ( + + + + )} + + + + + + + + ); +}; diff --git a/packages/kbn-ai-playground/components/view_code/vide_code_flyout.tsx b/packages/kbn-ai-playground/components/view_code/vide_code_flyout.tsx new file mode 100644 index 0000000000000..031ee9cedf076 --- /dev/null +++ b/packages/kbn-ai-playground/components/view_code/vide_code_flyout.tsx @@ -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 { + EuiFormLabel, + EuiCodeBlock, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiSpacer, + EuiSteps, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n-react'; +import React, { useMemo } from 'react'; +import { CreateApiKeyForm } from './create_api_key_form'; + +interface VideCodeFlyoutProps { + onClose: () => void; +} + +export const VideCodeFlyout: React.FC = ({ onClose }) => { + const steps = useMemo( + () => [ + { + title: i18n.translate('aiPlayground.viewCode.flyout.step.apiKeyTitle', { + defaultMessage: 'Generate and copy an API key', + }), + children: ( + <> + +

+ +

+
+ + + + ), + }, + { + title: i18n.translate('aiPlayground.viewCode.flyout.step.createApplication', { + defaultMessage: 'Create application', + }), + children: ( + <> + + + + + + npm install + + + ), + }, + ], + [] + ); + + return ( + + + +

+ +

+
+ + +

+ +

+
+
+ + + +
+ ); +}; diff --git a/packages/kbn-ai-playground/components/view_code/view_code_action.tsx b/packages/kbn-ai-playground/components/view_code/view_code_action.tsx index 9c1ddd753b90e..2cd8cddafada4 100644 --- a/packages/kbn-ai-playground/components/view_code/view_code_action.tsx +++ b/packages/kbn-ai-playground/components/view_code/view_code_action.tsx @@ -7,88 +7,30 @@ */ import React, { useState } from 'react'; -import { - EuiFlyout, - EuiFlyoutHeader, - EuiFlyoutBody, - EuiTitle, - EuiCodeBlock, - EuiButton, - EuiText, - EuiSpacer, - EuiSteps, - EuiCode, -} from '@elastic/eui'; +import { EuiButton } from '@elastic/eui'; import { useFormContext } from 'react-hook-form'; -import { ChatForm } from '../../types'; +import { ChatForm, ChatFormFields } from '../../types'; +import { VideCodeFlyout } from './vide_code_flyout'; +import { FormattedMessage } from '@kbn/i18n-react'; interface ViewCodeActionProps {} export const ViewCodeAction: React.FC = () => { - const { getValues } = useFormContext(); + const { watch } = useFormContext(); const [showFlyout, setShowFlyout] = useState(false); - const selectedIndices: string[] = getValues('indices'); - - let flyout; - - const steps = [ - { - title: 'Generate and copy an API key', - children: ( - <> - -

Run this code snippet to install things.

-
- - npm install - - ), - }, - { - title: 'Create application', - children: ( - -

- Now that you've completed step 2, go find the thing. -

-

- Go to Overview >> Endpoints note Elasticsearch{' '} - as <thing>. -

-
- ), - }, - ]; - - if (showFlyout) { - flyout = ( - setShowFlyout(false)}> - - -

Download Code

-
- - -

Download the code to use in your application.

-
-
- - - -
- ); - } + const selectedIndices = watch(ChatFormFields.indices); return ( <> - {flyout} + {showFlyout && setShowFlyout(false)} />} setShowFlyout(true)} disabled={!selectedIndices || selectedIndices?.length === 0} > - Download Code + ); diff --git a/packages/kbn-ai-playground/components/view_query/view_query_action.tsx b/packages/kbn-ai-playground/components/view_query/view_query_action.tsx index beca5323aed26..1f718fcae2269 100644 --- a/packages/kbn-ai-playground/components/view_query/view_query_action.tsx +++ b/packages/kbn-ai-playground/components/view_query/view_query_action.tsx @@ -33,9 +33,9 @@ import { createQuery, getDefaultQueryFields } from '../../lib/create_query'; interface ViewQueryActionProps {} export const ViewQueryAction: React.FC = () => { - const { getValues } = useFormContext(); + const { watch } = useFormContext(); const [showFlyout, setShowFlyout] = useState(false); - const selectedIndices: string[] = getValues('indices'); + const selectedIndices: string[] = watch('indices'); const { fields } = useIndicesFields(selectedIndices || []); const defaultFields = useMemo(() => getDefaultQueryFields(fields), [fields]); const [queryFields, setQueryFields] = useState(defaultFields); diff --git a/packages/kbn-ai-playground/hooks/useCreateApiKeyQuery.ts b/packages/kbn-ai-playground/hooks/useCreateApiKeyQuery.ts new file mode 100644 index 0000000000000..8826c129e6abf --- /dev/null +++ b/packages/kbn-ai-playground/hooks/useCreateApiKeyQuery.ts @@ -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 { useKibana } from '@kbn/kibana-react-plugin/public'; +import { AIPlaygroundPluginStartDeps } from '@kbn/ai-playground/types'; +import { useMutation } from '@tanstack/react-query'; +import { useFormContext } from 'react-hook-form'; +import { ChatFormFields } from '../types'; + +interface UseApiKeyQueryParams { + name: string; + expiresInDays: number; +} + +export const useCreateApiKeyQuery = () => { + const { services } = useKibana(); + const { getValues } = useFormContext(); + + const { data, isError, isLoading, isSuccess, mutateAsync } = useMutation({ + mutationFn: async ({ name, expiresInDays }: UseApiKeyQueryParams) => { + const response = await services.http.post<{ + apiKey: { encoded: string; name: string; expiration: number }; + }>('/internal/enterprise_search/ai_playground/api_key', { + body: JSON.stringify({ + name, + expiresInDays, + indices: getValues(ChatFormFields.indices), + }), + }); + + return response.apiKey.encoded; + }, + }); + + return { + apiKey: data, + isLoading, + isSuccess, + isError, + action: mutateAsync, + }; +}; diff --git a/packages/kbn-ai-playground/hooks/useIndicesFields.tsx b/packages/kbn-ai-playground/hooks/useIndicesFields.tsx index f8b5704a63b27..0ac716dd5f16f 100644 --- a/packages/kbn-ai-playground/hooks/useIndicesFields.tsx +++ b/packages/kbn-ai-playground/hooks/useIndicesFields.tsx @@ -16,6 +16,7 @@ export const useIndicesFields = (indices: string[]) => { const { data, isLoading } = useQuery({ enabled: indices.length > 0, queryKey: ['fields', indices.toString()], + initialData: {}, queryFn: async () => { const response = await services.http.post( '/internal/enterprise_search/ai_playground/query_source_fields', @@ -30,5 +31,5 @@ export const useIndicesFields = (indices: string[]) => { }, }); - return { fields: data || {}, isLoading }; + return { fields: data!, isLoading }; }; diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/ai_playground.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/ai_playground.ts index 132bd539a91af..cdd9c62962cae 100644 --- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/ai_playground.ts +++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/ai_playground.ts @@ -15,6 +15,7 @@ import { streamFactory } from '@kbn/ml-response-stream/server'; import { RouteDependencies } from '../../plugin'; import { elasticsearchErrorHandler } from '../../utils/elasticsearch_error_handler'; +import { createApiKey } from '@kbn/enterprise-search-plugin/server/lib/analytics/create_api_key'; export function registerAIPlaygroundRoutes({ log, router }: RouteDependencies) { router.post( @@ -105,4 +106,42 @@ export function registerAIPlaygroundRoutes({ log, router }: RouteDependencies) { return response.ok(responseWithHeaders); }) ); + + router.post( + { + path: '/internal/enterprise_search/ai_playground/api_key', + validate: { + body: schema.object({ + name: schema.string(), + expiresInDays: schema.number(), + indices: schema.arrayOf(schema.string()), + }), + }, + }, + elasticsearchErrorHandler(log, async (context, request, response) => { + const { name, expiresInDays, indices } = request.body; + const { client } = (await context.core).elasticsearch; + + const apiKey = await client.asCurrentUser.security.createApiKey({ + name, + expiration: `${expiresInDays}d`, + role_descriptors: { + [`aiPlaygroud-${name}-role`]: { + cluster: [], + indices: [ + { + names: indices, + privileges: ['read'], + }, + ], + }, + }, + }); + + return response.ok({ + body: { apiKey }, + headers: { 'content-type': 'application/json' }, + }); + }) + ); }