mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Search][Playground] Add connectors to sum models (#180428)
- Add state for connector page to have one source of truth and have ability to control edit flyout by URL - Add connectorId route to translate it to state and able easily control edit flyout by URL - Use new link in playground Having a link like `insightsAndAlerting/triggersActionsConnectors/connectors/:connectorId` will transformed to `insightsAndAlerting/triggersActionsConnectors/connectors#?_a=(initialConnector:(actionType:OpenAI,actionTypeId:.gen-ai,compatibility:!.....)`. @elastic/response-ops could you check is it appropriate changes? If yes I will proceed with adding tests for it
This commit is contained in:
parent
518b751beb
commit
0893fe72e4
15 changed files with 239 additions and 152 deletions
|
@ -106,6 +106,9 @@ export const Chat = () => {
|
||||||
borderRight: euiTheme.border.thin,
|
borderRight: euiTheme.border.thin,
|
||||||
paddingTop: euiTheme.size.l,
|
paddingTop: euiTheme.size.l,
|
||||||
paddingBottom: euiTheme.size.l,
|
paddingBottom: euiTheme.size.l,
|
||||||
|
// don't allow the chat to shrink below 66.6% of the screen
|
||||||
|
flexBasis: 0,
|
||||||
|
minWidth: '66.6%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EuiFlexGroup direction="column" className="eui-fullHeight">
|
<EuiFlexGroup direction="column" className="eui-fullHeight">
|
||||||
|
@ -210,7 +213,7 @@ export const Chat = () => {
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
||||||
<EuiFlexItem grow={1}>
|
<EuiFlexItem grow={1} css={{ flexBasis: 0, minWidth: '33.3%' }}>
|
||||||
<ChatSidebar selectedIndicesCount={selectedIndicesCount} />
|
<ChatSidebar selectedIndicesCount={selectedIndicesCount} />
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
|
|
|
@ -35,16 +35,12 @@ export const SetUpConnectorPanelForStartChat: React.FC = () => {
|
||||||
|
|
||||||
return connectors && !isConnectorListLoading ? (
|
return connectors && !isConnectorListLoading ? (
|
||||||
<>
|
<>
|
||||||
{!!Object.keys(connectors).length && showCallout && (
|
{!!connectors.length && showCallout && (
|
||||||
<EuiCallOut
|
<EuiCallOut
|
||||||
title={i18n.translate('xpack.searchPlayground.emptyPrompts.setUpConnector.settled', {
|
title={i18n.translate('xpack.searchPlayground.emptyPrompts.setUpConnector.settled', {
|
||||||
defaultMessage:
|
defaultMessage: '{connectorName} connector added',
|
||||||
'{connectorsNames} {count, plural, one {connector} other {connectors}} added',
|
|
||||||
values: {
|
values: {
|
||||||
connectorsNames: Object.values(connectors)
|
connectorName: connectors[0].title,
|
||||||
.map((connector) => connector.title)
|
|
||||||
.join(', '),
|
|
||||||
count: Object.values(connectors).length,
|
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
iconType="check"
|
iconType="check"
|
||||||
|
|
|
@ -30,8 +30,20 @@ describe('SummarizationModel', () => {
|
||||||
|
|
||||||
it('renders correctly with models', () => {
|
it('renders correctly with models', () => {
|
||||||
const models = [
|
const models = [
|
||||||
{ name: 'Model1', disabled: false, icon: MockIcon, connectorId: 'connector1' },
|
{
|
||||||
{ name: 'Model2', disabled: true, icon: MockIcon, connectorId: 'connector2' },
|
name: 'Model1',
|
||||||
|
disabled: false,
|
||||||
|
icon: MockIcon,
|
||||||
|
connectorId: 'connector1',
|
||||||
|
connectorName: 'nameconnector1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Model2',
|
||||||
|
disabled: true,
|
||||||
|
icon: MockIcon,
|
||||||
|
connectorId: 'connector2',
|
||||||
|
connectorName: 'nameconnector2',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
const { getByTestId } = render(
|
const { getByTestId } = render(
|
||||||
<SummarizationModel selectedModel={models[1]} models={models} onSelect={jest.fn()} />
|
<SummarizationModel selectedModel={models[1]} models={models} onSelect={jest.fn()} />
|
||||||
|
|
|
@ -8,14 +8,15 @@
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
EuiButtonIcon,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
EuiFormRow,
|
EuiFormRow,
|
||||||
EuiIcon,
|
EuiIcon,
|
||||||
EuiIconTip,
|
EuiIconTip,
|
||||||
EuiLink,
|
|
||||||
EuiSuperSelect,
|
EuiSuperSelect,
|
||||||
EuiText,
|
EuiText,
|
||||||
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
|
@ -30,31 +31,64 @@ interface SummarizationModelProps {
|
||||||
models: LLMModel[];
|
models: LLMModel[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getOptionValue = (model: LLMModel): string => model.connectorId + model.name;
|
||||||
|
|
||||||
export const SummarizationModel: React.FC<SummarizationModelProps> = ({
|
export const SummarizationModel: React.FC<SummarizationModelProps> = ({
|
||||||
selectedModel,
|
selectedModel,
|
||||||
models,
|
models,
|
||||||
onSelect,
|
onSelect,
|
||||||
}) => {
|
}) => {
|
||||||
const managementLink = useManagementLink();
|
const managementLink = useManagementLink(selectedModel.connectorId);
|
||||||
const onChange = (modelName: string) => {
|
const onChange = (modelValue: string) => {
|
||||||
const model = models.find(({ name }) => name === modelName);
|
const newSelectedModel = models.find((model) => getOptionValue(model) === modelValue);
|
||||||
|
|
||||||
if (model) {
|
if (newSelectedModel) {
|
||||||
onSelect(model);
|
onSelect(newSelectedModel);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const modelsOption: Array<EuiSuperSelectOption<string>> = useMemo(
|
const modelsOption: Array<EuiSuperSelectOption<string>> = useMemo(
|
||||||
() =>
|
() =>
|
||||||
models.map(({ name, disabled, icon, connectorId }) => ({
|
models.map((model) => ({
|
||||||
value: name,
|
value: getOptionValue(model),
|
||||||
disabled,
|
disabled: model.disabled,
|
||||||
inputDisplay: (
|
inputDisplay: (
|
||||||
<EuiFlexGroup alignItems="center">
|
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiIcon type={icon} />
|
<EuiIcon type={model.icon} />
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem>{name}</EuiFlexItem>
|
<EuiFlexGroup
|
||||||
|
justifyContent="spaceBetween"
|
||||||
|
alignItems="center"
|
||||||
|
gutterSize="s"
|
||||||
|
css={{ overflow: 'hidden' }}
|
||||||
|
>
|
||||||
|
<EuiText size="s">{model.name}</EuiText>
|
||||||
|
{model.showConnectorName && model.connectorName && (
|
||||||
|
<EuiText
|
||||||
|
size="xs"
|
||||||
|
color="subdued"
|
||||||
|
css={{ overflow: 'hidden', textOverflow: 'ellipsis', textWrap: 'nowrap' }}
|
||||||
|
>
|
||||||
|
<span title={model.connectorName}>{model.connectorName}</span>
|
||||||
|
</EuiText>
|
||||||
|
)}
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
),
|
||||||
|
dropdownDisplay: (
|
||||||
|
<EuiFlexGroup alignItems="center" gutterSize="s">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiIcon type={model.icon} />
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexGroup gutterSize="xs" direction="column">
|
||||||
|
<EuiText size="s">{model.name}</EuiText>
|
||||||
|
{model.showConnectorName && model.connectorName && (
|
||||||
|
<EuiText size="xs" color="subdued">
|
||||||
|
{model.connectorName}
|
||||||
|
</EuiText>
|
||||||
|
)}
|
||||||
|
</EuiFlexGroup>
|
||||||
</EuiFlexGroup>
|
</EuiFlexGroup>
|
||||||
),
|
),
|
||||||
})),
|
})),
|
||||||
|
@ -63,6 +97,7 @@ export const SummarizationModel: React.FC<SummarizationModelProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<EuiFormRow
|
<EuiFormRow
|
||||||
|
css={{ '.euiFormLabel': { display: 'flex', alignItems: 'center' } }}
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -77,20 +112,34 @@ export const SummarizationModel: React.FC<SummarizationModelProps> = ({
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
labelAppend={
|
labelAppend={
|
||||||
<EuiText size="xs">
|
<EuiToolTip
|
||||||
<EuiLink target="_blank" href={managementLink} data-test-subj="manageConnectorsLink">
|
delay="long"
|
||||||
<FormattedMessage
|
content={i18n.translate(
|
||||||
id="xpack.searchPlayground.sidebar.summarizationModel.manageConnectors"
|
'xpack.searchPlayground.sidebar.summarizationModel.manageConnectorTooltip',
|
||||||
defaultMessage="Manage GenAI connectors"
|
{
|
||||||
/>
|
defaultMessage: 'Manage connector',
|
||||||
</EuiLink>
|
}
|
||||||
</EuiText>
|
)}
|
||||||
|
>
|
||||||
|
<EuiButtonIcon
|
||||||
|
target="_blank"
|
||||||
|
href={managementLink}
|
||||||
|
data-test-subj="manageConnectorsLink"
|
||||||
|
iconType="wrench"
|
||||||
|
aria-label={i18n.translate(
|
||||||
|
'xpack.searchPlayground.sidebar.summarizationModel.manageConnectorLink',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Manage connector',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</EuiToolTip>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<EuiSuperSelect
|
<EuiSuperSelect
|
||||||
data-test-subj="summarizationModelSelect"
|
data-test-subj="summarizationModelSelect"
|
||||||
options={modelsOption}
|
options={modelsOption}
|
||||||
valueOfSelected={selectedModel.name}
|
valueOfSelected={getOptionValue(selectedModel)}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
</EuiFormRow>
|
</EuiFormRow>
|
||||||
|
|
|
@ -14,9 +14,7 @@ jest.mock('./use_load_connectors', () => ({
|
||||||
useLoadConnectors: jest.fn(),
|
useLoadConnectors: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const mockConnectors = {
|
const mockConnectors = [{ id: 'connectorId1', title: 'OpenAI Connector', type: LLMs.openai }];
|
||||||
[LLMs.openai]: { id: 'connectorId1', title: 'OpenAI Connector' },
|
|
||||||
};
|
|
||||||
const mockUseLoadConnectors = (data: any) => {
|
const mockUseLoadConnectors = (data: any) => {
|
||||||
(useLoadConnectors as jest.Mock).mockReturnValue({ data });
|
(useLoadConnectors as jest.Mock).mockReturnValue({ data });
|
||||||
};
|
};
|
||||||
|
@ -32,57 +30,32 @@ describe('useLLMsModels Hook', () => {
|
||||||
const { result } = renderHook(() => useLLMsModels());
|
const { result } = renderHook(() => useLLMsModels());
|
||||||
|
|
||||||
expect(result.current).toEqual([
|
expect(result.current).toEqual([
|
||||||
{
|
|
||||||
connectorId: undefined,
|
|
||||||
disabled: true,
|
|
||||||
icon: expect.any(Function),
|
|
||||||
name: 'Azure OpenAI',
|
|
||||||
value: undefined,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
connectorId: 'connectorId1',
|
connectorId: 'connectorId1',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
icon: expect.any(Function),
|
icon: expect.any(Function),
|
||||||
name: 'gpt-3.5-turbo',
|
id: 'connectorId1gpt-3.5-turbo ',
|
||||||
|
name: 'gpt-3.5-turbo ',
|
||||||
|
showConnectorName: false,
|
||||||
value: 'gpt-3.5-turbo',
|
value: 'gpt-3.5-turbo',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
connectorId: 'connectorId1',
|
connectorId: 'connectorId1',
|
||||||
disabled: false,
|
disabled: false,
|
||||||
icon: expect.any(Function),
|
icon: expect.any(Function),
|
||||||
name: 'gpt-4',
|
id: 'connectorId1gpt-4 ',
|
||||||
|
name: 'gpt-4 ',
|
||||||
|
showConnectorName: false,
|
||||||
value: 'gpt-4',
|
value: 'gpt-4',
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns LLMModels as disabled when no connectors are available', () => {
|
it('returns emptyd when connectors not available', () => {
|
||||||
mockUseLoadConnectors({});
|
mockUseLoadConnectors([]);
|
||||||
|
|
||||||
const { result } = renderHook(() => useLLMsModels());
|
const { result } = renderHook(() => useLLMsModels());
|
||||||
|
|
||||||
expect(result.current).toEqual([
|
expect(result.current).toEqual([]);
|
||||||
{
|
|
||||||
connectorId: undefined,
|
|
||||||
disabled: true,
|
|
||||||
icon: expect.any(Function),
|
|
||||||
name: 'Azure OpenAI',
|
|
||||||
value: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
connectorId: undefined,
|
|
||||||
disabled: true,
|
|
||||||
icon: expect.any(Function),
|
|
||||||
name: 'gpt-3.5-turbo',
|
|
||||||
value: 'gpt-3.5-turbo',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
connectorId: undefined,
|
|
||||||
disabled: true,
|
|
||||||
icon: expect.any(Function),
|
|
||||||
name: 'gpt-4',
|
|
||||||
value: 'gpt-4',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,48 +7,82 @@
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { OpenAILogo } from '@kbn/stack-connectors-plugin/public/common';
|
import { OpenAILogo } from '@kbn/stack-connectors-plugin/public/common';
|
||||||
import { ComponentType } from 'react';
|
import { ComponentType, useMemo } from 'react';
|
||||||
import { LLMs } from '../../common/types';
|
import { LLMs } from '../../common/types';
|
||||||
import { LLMModel, SummarizationModelName } from '../types';
|
import { LLMModel } from '../types';
|
||||||
import { useLoadConnectors } from './use_load_connectors';
|
import { useLoadConnectors } from './use_load_connectors';
|
||||||
|
|
||||||
const llmModels: Array<{
|
const mapLlmToModels: Record<
|
||||||
llm: LLMs;
|
LLMs,
|
||||||
icon: ComponentType;
|
|
||||||
models: Array<{ label: string; value?: string }>;
|
|
||||||
}> = [
|
|
||||||
{
|
{
|
||||||
llm: LLMs.openai_azure,
|
icon: ComponentType;
|
||||||
|
getModels: (
|
||||||
|
connectorName: string,
|
||||||
|
includeName: boolean
|
||||||
|
) => Array<{ label: string; value?: string }>;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
|
[LLMs.openai_azure]: {
|
||||||
icon: OpenAILogo,
|
icon: OpenAILogo,
|
||||||
models: [
|
getModels: (connectorName, includeName) => [
|
||||||
{
|
{
|
||||||
label: i18n.translate('xpack.searchPlayground.openAIAzureModel', {
|
label: i18n.translate('xpack.searchPlayground.openAIAzureModel', {
|
||||||
defaultMessage: 'Azure OpenAI',
|
defaultMessage: 'Azure OpenAI {name}',
|
||||||
|
values: { name: includeName ? `(${connectorName})` : '' },
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
[LLMs.openai]: {
|
||||||
llm: LLMs.openai,
|
|
||||||
icon: OpenAILogo,
|
icon: OpenAILogo,
|
||||||
models: Object.values(SummarizationModelName).map((model) => ({ label: model, value: model })),
|
getModels: (connectorName, includeName) =>
|
||||||
|
['gpt-3.5-turbo', 'gpt-4'].map((model) => ({
|
||||||
|
label: `${model} ${includeName ? `(${connectorName})` : ''}`,
|
||||||
|
value: model,
|
||||||
|
})),
|
||||||
},
|
},
|
||||||
];
|
};
|
||||||
|
|
||||||
export const useLLMsModels = (): LLMModel[] => {
|
export const useLLMsModels = (): LLMModel[] => {
|
||||||
const { data: connectors } = useLoadConnectors();
|
const { data: connectors } = useLoadConnectors();
|
||||||
|
|
||||||
return llmModels.reduce<LLMModel[]>(
|
const mapConnectorTypeToCount = useMemo(
|
||||||
(result, { llm, icon, models }) => [
|
() =>
|
||||||
...result,
|
connectors?.reduce<Partial<Record<LLMs, number>>>(
|
||||||
...models.map(({ label, value }) => ({
|
(result, connector) => ({
|
||||||
name: label,
|
...result,
|
||||||
value,
|
[connector.type]: (result[connector.type] || 0) + 1,
|
||||||
icon,
|
}),
|
||||||
disabled: !connectors?.[llm],
|
{}
|
||||||
connectorId: connectors?.[llm]?.id,
|
),
|
||||||
})),
|
[connectors]
|
||||||
],
|
);
|
||||||
[]
|
|
||||||
|
return useMemo(
|
||||||
|
() =>
|
||||||
|
connectors?.reduce<LLMModel[]>((result, connector) => {
|
||||||
|
const llmParams = mapLlmToModels[connector.type];
|
||||||
|
|
||||||
|
if (!llmParams) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showConnectorName = Number(mapConnectorTypeToCount?.[connector.type]) > 1;
|
||||||
|
|
||||||
|
return [
|
||||||
|
...result,
|
||||||
|
...llmParams.getModels(connector.name, false).map(({ label, value }) => ({
|
||||||
|
id: connector?.id + label,
|
||||||
|
name: label,
|
||||||
|
value,
|
||||||
|
connectorName: connector.name,
|
||||||
|
showConnectorName,
|
||||||
|
icon: llmParams.icon,
|
||||||
|
disabled: !connector,
|
||||||
|
connectorId: connector.id,
|
||||||
|
})),
|
||||||
|
];
|
||||||
|
}, []) || [],
|
||||||
|
[connectors, mapConnectorTypeToCount]
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -73,8 +73,8 @@ describe('useLoadConnectors', () => {
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useLoadConnectors());
|
const { result, waitForNextUpdate } = renderHook(() => useLoadConnectors());
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
|
|
||||||
await expect(result.current).resolves.toStrictEqual({
|
await expect(result.current).resolves.toStrictEqual([
|
||||||
openai: {
|
{
|
||||||
actionTypeId: '.gen-ai',
|
actionTypeId: '.gen-ai',
|
||||||
config: {
|
config: {
|
||||||
apiProvider: 'OpenAI',
|
apiProvider: 'OpenAI',
|
||||||
|
@ -82,8 +82,9 @@ describe('useLoadConnectors', () => {
|
||||||
id: '1',
|
id: '1',
|
||||||
isMissingSecrets: false,
|
isMissingSecrets: false,
|
||||||
title: 'OpenAI',
|
title: 'OpenAI',
|
||||||
|
type: 'openai',
|
||||||
},
|
},
|
||||||
openai_azure: {
|
{
|
||||||
actionTypeId: '.gen-ai',
|
actionTypeId: '.gen-ai',
|
||||||
config: {
|
config: {
|
||||||
apiProvider: 'Azure OpenAI',
|
apiProvider: 'Azure OpenAI',
|
||||||
|
@ -91,8 +92,9 @@ describe('useLoadConnectors', () => {
|
||||||
id: '3',
|
id: '3',
|
||||||
isMissingSecrets: false,
|
isMissingSecrets: false,
|
||||||
title: 'OpenAI Azure',
|
title: 'OpenAI Azure',
|
||||||
|
type: 'openai_azure',
|
||||||
},
|
},
|
||||||
});
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -27,46 +27,44 @@ type OpenAIConnector = UserConfiguredActionConnector<
|
||||||
Record<string, unknown>
|
Record<string, unknown>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
const mapLLMToActionParam: Record<
|
const connectorTypeToLLM: Array<{
|
||||||
LLMs,
|
actionId: string;
|
||||||
|
actionProvider?: string;
|
||||||
|
match: (connector: ActionConnector) => boolean;
|
||||||
|
transform: (connector: ActionConnector) => PlaygroundConnector;
|
||||||
|
}> = [
|
||||||
{
|
{
|
||||||
actionId: string;
|
|
||||||
actionProvider?: string;
|
|
||||||
match: (connector: ActionConnector) => boolean;
|
|
||||||
transform: (connector: ActionConnector) => PlaygroundConnector;
|
|
||||||
}
|
|
||||||
> = {
|
|
||||||
[LLMs.openai_azure]: {
|
|
||||||
actionId: OPENAI_CONNECTOR_ID,
|
actionId: OPENAI_CONNECTOR_ID,
|
||||||
actionProvider: OpenAiProviderType.AzureAi,
|
actionProvider: OpenAiProviderType.AzureAi,
|
||||||
match: (connector) =>
|
match: (connector) =>
|
||||||
|
connector.actionTypeId === OPENAI_CONNECTOR_ID &&
|
||||||
(connector as OpenAIConnector).config.apiProvider === OpenAiProviderType.AzureAi,
|
(connector as OpenAIConnector).config.apiProvider === OpenAiProviderType.AzureAi,
|
||||||
transform: (connector) => ({
|
transform: (connector) => ({
|
||||||
...connector,
|
...connector,
|
||||||
title: i18n.translate('xpack.searchPlayground.openAIAzureConnectorTitle', {
|
title: i18n.translate('xpack.searchPlayground.openAIAzureConnectorTitle', {
|
||||||
defaultMessage: 'OpenAI Azure',
|
defaultMessage: 'OpenAI Azure',
|
||||||
}),
|
}),
|
||||||
|
type: LLMs.openai_azure,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
[LLMs.openai]: {
|
{
|
||||||
actionId: OPENAI_CONNECTOR_ID,
|
actionId: OPENAI_CONNECTOR_ID,
|
||||||
match: (connector) =>
|
match: (connector) =>
|
||||||
|
connector.actionTypeId === OPENAI_CONNECTOR_ID &&
|
||||||
(connector as OpenAIConnector).config.apiProvider === OpenAiProviderType.OpenAi,
|
(connector as OpenAIConnector).config.apiProvider === OpenAiProviderType.OpenAi,
|
||||||
transform: (connector) => ({
|
transform: (connector) => ({
|
||||||
...connector,
|
...connector,
|
||||||
title: i18n.translate('xpack.searchPlayground.openAIConnectorTitle', {
|
title: i18n.translate('xpack.searchPlayground.openAIConnectorTitle', {
|
||||||
defaultMessage: 'OpenAI',
|
defaultMessage: 'OpenAI',
|
||||||
}),
|
}),
|
||||||
|
type: LLMs.openai,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
};
|
];
|
||||||
|
|
||||||
type PlaygroundConnector = ActionConnector & { title: string };
|
type PlaygroundConnector = ActionConnector & { title: string; type: LLMs };
|
||||||
|
|
||||||
export const useLoadConnectors = (): UseQueryResult<
|
export const useLoadConnectors = (): UseQueryResult<PlaygroundConnector[], IHttpFetchError> => {
|
||||||
Record<LLMs, PlaygroundConnector>,
|
|
||||||
IHttpFetchError
|
|
||||||
> => {
|
|
||||||
const {
|
const {
|
||||||
services: { http, notifications },
|
services: { http, notifications },
|
||||||
} = useKibana();
|
} = useKibana();
|
||||||
|
@ -76,19 +74,15 @@ export const useLoadConnectors = (): UseQueryResult<
|
||||||
async () => {
|
async () => {
|
||||||
const queryResult = await loadConnectors({ http });
|
const queryResult = await loadConnectors({ http });
|
||||||
|
|
||||||
return Object.entries(mapLLMToActionParam).reduce<Partial<Record<LLMs, PlaygroundConnector>>>(
|
return queryResult.reduce<PlaygroundConnector[]>((result, connector) => {
|
||||||
(result, [llm, { actionId, match, transform }]) => {
|
const { transform } = connectorTypeToLLM.find(({ match }) => match(connector)) || {};
|
||||||
const targetConnector = queryResult.find(
|
|
||||||
(connector) =>
|
|
||||||
!connector.isMissingSecrets &&
|
|
||||||
connector.actionTypeId === actionId &&
|
|
||||||
(match?.(connector) ?? true)
|
|
||||||
);
|
|
||||||
|
|
||||||
return targetConnector ? { ...result, [llm]: transform(targetConnector) } : result;
|
if (!connector.isMissingSecrets && !!transform) {
|
||||||
},
|
return [...result, transform(connector)];
|
||||||
{}
|
}
|
||||||
);
|
|
||||||
|
return result;
|
||||||
|
}, []);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
retry: false,
|
retry: false,
|
||||||
|
|
|
@ -40,13 +40,14 @@ describe('useManagementLink Hook', () => {
|
||||||
const expectedUrl =
|
const expectedUrl =
|
||||||
'http://localhost:5601/app/management/insightsAndAlerting/triggersActionsConnectors';
|
'http://localhost:5601/app/management/insightsAndAlerting/triggersActionsConnectors';
|
||||||
mockGetUrl.mockResolvedValue(expectedUrl);
|
mockGetUrl.mockResolvedValue(expectedUrl);
|
||||||
const { result, waitForNextUpdate } = renderHook(() => useManagementLink());
|
const connectorId = 'test-connector-id';
|
||||||
|
const { result, waitForNextUpdate } = renderHook(() => useManagementLink(connectorId));
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
|
|
||||||
expect(result.current).toBe(expectedUrl);
|
expect(result.current).toBe(expectedUrl);
|
||||||
expect(mockGetUrl).toHaveBeenCalledWith({
|
expect(mockGetUrl).toHaveBeenCalledWith({
|
||||||
sectionId: 'insightsAndAlerting',
|
sectionId: 'insightsAndAlerting',
|
||||||
appId: 'triggersActionsConnectors',
|
appId: 'triggersActionsConnectors/connectors/test-connector-id',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -62,8 +63,8 @@ describe('useManagementLink Hook', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const connectorId = 'test-connector-id';
|
||||||
const { result } = renderHook(() => useManagementLink());
|
const { result } = renderHook(() => useManagementLink(connectorId));
|
||||||
|
|
||||||
expect(result.current).toBe('');
|
expect(result.current).toBe('');
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import { useEffect, useMemo, useState } from 'react';
|
||||||
import { useKibana } from './use_kibana';
|
import { useKibana } from './use_kibana';
|
||||||
|
|
||||||
export const useManagementLink = () => {
|
export const useManagementLink = (connectorId: string) => {
|
||||||
const {
|
const {
|
||||||
services: { share },
|
services: { share },
|
||||||
} = useKibana();
|
} = useKibana();
|
||||||
|
@ -21,12 +21,12 @@ export const useManagementLink = () => {
|
||||||
const getLink = async () => {
|
const getLink = async () => {
|
||||||
const link = await managementLocator?.getUrl({
|
const link = await managementLocator?.getUrl({
|
||||||
sectionId: 'insightsAndAlerting',
|
sectionId: 'insightsAndAlerting',
|
||||||
appId: 'triggersActionsConnectors',
|
appId: `triggersActionsConnectors/connectors/${connectorId}`,
|
||||||
});
|
});
|
||||||
setManagementLink(link || '');
|
setManagementLink(link || '');
|
||||||
};
|
};
|
||||||
getLink();
|
getLink();
|
||||||
}, [managementLocator]);
|
}, [managementLocator, connectorId]);
|
||||||
|
|
||||||
return managementLink;
|
return managementLink;
|
||||||
};
|
};
|
||||||
|
|
|
@ -104,11 +104,6 @@ export interface AIMessage extends Message {
|
||||||
retrievalDocs: Doc[];
|
retrievalDocs: Doc[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum SummarizationModelName {
|
|
||||||
gpt3_5_turbo = 'gpt-3.5-turbo',
|
|
||||||
gpt_4 = 'gpt-4',
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ElasticsearchIndex {
|
export interface ElasticsearchIndex {
|
||||||
count: number; // Elasticsearch _count
|
count: number; // Elasticsearch _count
|
||||||
has_in_progress_syncs?: boolean; // these default to false if not a connector or crawler
|
has_in_progress_syncs?: boolean; // these default to false if not a connector or crawler
|
||||||
|
@ -194,7 +189,9 @@ export interface UseChatHelpers {
|
||||||
export interface LLMModel {
|
export interface LLMModel {
|
||||||
name: string;
|
name: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
|
showConnectorName?: boolean;
|
||||||
|
connectorId: string;
|
||||||
|
connectorName: string;
|
||||||
icon: ComponentType;
|
icon: ComponentType;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
connectorId?: string;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ export type Section = 'connectors' | 'rules' | 'alerts' | 'logs';
|
||||||
|
|
||||||
export const routeToHome = `/`;
|
export const routeToHome = `/`;
|
||||||
export const routeToConnectors = `/connectors`;
|
export const routeToConnectors = `/connectors`;
|
||||||
|
export const routeToConnectorEdit = `/connectors/:connectorId`;
|
||||||
export const routeToRules = `/rules`;
|
export const routeToRules = `/rules`;
|
||||||
export const routeToLogs = `/logs`;
|
export const routeToLogs = `/logs`;
|
||||||
export const legacyRouteToAlerts = `/alerts`;
|
export const legacyRouteToAlerts = `/alerts`;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { Routes, Route } from '@kbn/shared-ux-router';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import { EuiSpacer, EuiButtonEmpty, EuiPageHeader, EuiPageTemplate } from '@elastic/eui';
|
import { EuiSpacer, EuiButtonEmpty, EuiPageHeader, EuiPageTemplate } from '@elastic/eui';
|
||||||
import { routeToConnectors, routeToLogs, Section } from '../../../constants';
|
import { routeToConnectorEdit, routeToConnectors, routeToLogs, Section } from '../../../constants';
|
||||||
import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb';
|
import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb';
|
||||||
import { getCurrentDocTitle } from '../../../lib/doc_title';
|
import { getCurrentDocTitle } from '../../../lib/doc_title';
|
||||||
import { suspendedComponentWithProps } from '../../../lib/suspended_component_with_props';
|
import { suspendedComponentWithProps } from '../../../lib/suspended_component_with_props';
|
||||||
|
@ -126,7 +126,7 @@ export const ActionsConnectorsHome: React.FunctionComponent<RouteComponentProps<
|
||||||
<Route exact path={routeToLogs} component={renderLogsList} />
|
<Route exact path={routeToLogs} component={renderLogsList} />
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
path={routeToConnectors}
|
path={[routeToConnectors, routeToConnectorEdit]}
|
||||||
component={suspendedComponentWithProps(ConnectorsList, 'xl')}
|
component={suspendedComponentWithProps(ConnectorsList, 'xl')}
|
||||||
/>
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
@ -29,6 +29,15 @@ const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;
|
||||||
const actionTypeRegistry = actionTypeRegistryMock.create();
|
const actionTypeRegistry = actionTypeRegistryMock.create();
|
||||||
const mocks = coreMock.createSetup();
|
const mocks = coreMock.createSetup();
|
||||||
const { loadAllActions, loadActionTypes } = jest.requireMock('../../../lib/action_connector_api');
|
const { loadAllActions, loadActionTypes } = jest.requireMock('../../../lib/action_connector_api');
|
||||||
|
const mockGetParams = jest.fn().mockReturnValue({});
|
||||||
|
const mockGetLocation = jest.fn().mockReturnValue({ search: '' });
|
||||||
|
const mockGetHistory = jest.fn().mockReturnValue({ push: jest.fn(), createHref: jest.fn() });
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
useParams: () => mockGetParams(),
|
||||||
|
useLocation: () => mockGetLocation(),
|
||||||
|
useHistory: () => mockGetHistory(),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('actions_connectors_list', () => {
|
describe('actions_connectors_list', () => {
|
||||||
describe('component empty', () => {
|
describe('component empty', () => {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import { omit } from 'lodash';
|
||||||
import { FormattedMessage } from '@kbn/i18n-react';
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import { withTheme, EuiTheme } from '@kbn/kibana-react-plugin/common';
|
import { withTheme, EuiTheme } from '@kbn/kibana-react-plugin/common';
|
||||||
import { getConnectorCompatibility } from '@kbn/actions-plugin/common';
|
import { getConnectorCompatibility } from '@kbn/actions-plugin/common';
|
||||||
|
import { useHistory, useLocation, useParams } from 'react-router-dom';
|
||||||
import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api';
|
import { loadAllActions, loadActionTypes, deleteActions } from '../../../lib/action_connector_api';
|
||||||
import {
|
import {
|
||||||
hasDeleteActionsCapability,
|
hasDeleteActionsCapability,
|
||||||
|
@ -54,6 +55,13 @@ import { CreateConnectorFlyout } from '../../action_connector_form/create_connec
|
||||||
import { EditConnectorFlyout } from '../../action_connector_form/edit_connector_flyout';
|
import { EditConnectorFlyout } from '../../action_connector_form/edit_connector_flyout';
|
||||||
import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb';
|
import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb';
|
||||||
import { getCurrentDocTitle } from '../../../lib/doc_title';
|
import { getCurrentDocTitle } from '../../../lib/doc_title';
|
||||||
|
import { routeToConnectors } from '../../../constants';
|
||||||
|
|
||||||
|
interface EditConnectorProps {
|
||||||
|
initialConnector?: ActionConnector;
|
||||||
|
tab?: EditConnectorTabs;
|
||||||
|
isFix?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
const ConnectorIconTipWithSpacing = withTheme(({ theme }: { theme: EuiTheme }) => {
|
const ConnectorIconTipWithSpacing = withTheme(({ theme }: { theme: EuiTheme }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -89,6 +97,9 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
||||||
chrome,
|
chrome,
|
||||||
docLinks,
|
docLinks,
|
||||||
} = useKibana().services;
|
} = useKibana().services;
|
||||||
|
const { connectorId } = useParams<{ connectorId?: string }>();
|
||||||
|
const history = useHistory();
|
||||||
|
const location = useLocation();
|
||||||
const canDelete = hasDeleteActionsCapability(capabilities);
|
const canDelete = hasDeleteActionsCapability(capabilities);
|
||||||
const canSave = hasSaveActionsCapability(capabilities);
|
const canSave = hasSaveActionsCapability(capabilities);
|
||||||
|
|
||||||
|
@ -97,13 +108,9 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
||||||
const [pageIndex, setPageIndex] = useState<number>(0);
|
const [pageIndex, setPageIndex] = useState<number>(0);
|
||||||
const [selectedItems, setSelectedItems] = useState<ActionConnectorTableItem[]>([]);
|
const [selectedItems, setSelectedItems] = useState<ActionConnectorTableItem[]>([]);
|
||||||
const [isLoadingActionTypes, setIsLoadingActionTypes] = useState<boolean>(false);
|
const [isLoadingActionTypes, setIsLoadingActionTypes] = useState<boolean>(false);
|
||||||
const [isLoadingActions, setIsLoadingActions] = useState<boolean>(false);
|
const [isLoadingActions, setIsLoadingActions] = useState<boolean>(true);
|
||||||
const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false);
|
const [addFlyoutVisible, setAddFlyoutVisibility] = useState<boolean>(false);
|
||||||
const [editConnectorProps, setEditConnectorProps] = useState<{
|
const [editConnectorProps, setEditConnectorProps] = useState<EditConnectorProps>({});
|
||||||
initialConnector?: ActionConnector;
|
|
||||||
tab?: EditConnectorTabs;
|
|
||||||
isFix?: boolean;
|
|
||||||
}>({});
|
|
||||||
const [connectorsToDelete, setConnectorsToDelete] = useState<string[]>([]);
|
const [connectorsToDelete, setConnectorsToDelete] = useState<string[]>([]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadActions();
|
loadActions();
|
||||||
|
@ -164,6 +171,19 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (connectorId && !isLoadingActions) {
|
||||||
|
const connector = actions.find((action) => action.id === connectorId);
|
||||||
|
if (connector) {
|
||||||
|
editItem(connector, EditConnectorTabs.Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkToConnectors = history.createHref({ pathname: routeToConnectors });
|
||||||
|
|
||||||
|
window.history.replaceState(null, '', linkToConnectors);
|
||||||
|
}
|
||||||
|
}, [actions, connectorId, history, isLoadingActions, location]);
|
||||||
|
|
||||||
function setDeleteConnectorWarning(connectors: string[]) {
|
function setDeleteConnectorWarning(connectors: string[]) {
|
||||||
const show = connectors.some((c) => {
|
const show = connectors.some((c) => {
|
||||||
const action = actions.find((a) => a.id === c);
|
const action = actions.find((a) => a.id === c);
|
||||||
|
@ -197,11 +217,7 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function editItem(
|
function editItem(actionConnector: ActionConnector, tab: EditConnectorTabs, isFix?: boolean) {
|
||||||
actionConnector: ActionConnector,
|
|
||||||
tab: EditConnectorTabs,
|
|
||||||
isFix?: boolean
|
|
||||||
) {
|
|
||||||
setEditConnectorProps({ initialConnector: actionConnector, tab, isFix: isFix ?? false });
|
setEditConnectorProps({ initialConnector: actionConnector, tab, isFix: isFix ?? false });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue