mirror of
https://github.com/elastic/kibana.git
synced 2025-06-28 03:01:21 -04:00
feat: workchat assistant list/details/edit page (#217984)
## Summary ### UI changes - assistant UI - list view - details view - modals: edit info, edit prompt, create - rename routes from `agents` to `assistatns` ### Server changes - Add `avatar` object to agent/assistnat saved object schema - changed schema from dynamic `strict` to `false` ### Recording https://github.com/user-attachments/assets/df689d87-2c0e-4e82-8dc1-46de4a9ab9d8 ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md) - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [ ] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) - [ ] This was checked for breaking HTTP API changes, and any breaking changes have been approved by the breaking-change committee. The `release_note:breaking` label should be applied in these situations. - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
821f74ea5d
commit
c5ff7aa155
32 changed files with 1449 additions and 379 deletions
|
@ -3873,7 +3873,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"workchat_agent": {
|
"workchat_agent": {
|
||||||
"dynamic": "strict",
|
"dynamic": false,
|
||||||
"properties": {
|
"properties": {
|
||||||
"access_control": {
|
"access_control": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
|
@ -18,11 +18,15 @@ export interface Agent {
|
||||||
user: UserNameAndId;
|
user: UserNameAndId;
|
||||||
public: boolean;
|
public: boolean;
|
||||||
configuration: Record<string, any>;
|
configuration: Record<string, any>;
|
||||||
|
avatar: {
|
||||||
|
color?: string;
|
||||||
|
text?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentCreateRequest = Pick<
|
export type AgentCreateRequest = Pick<
|
||||||
Agent,
|
Agent,
|
||||||
'name' | 'description' | 'configuration' | 'public'
|
'name' | 'description' | 'configuration' | 'public' | 'avatar'
|
||||||
> & {
|
> & {
|
||||||
id?: string;
|
id?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,17 +10,16 @@
|
||||||
*/
|
*/
|
||||||
export const appPaths = {
|
export const appPaths = {
|
||||||
home: '/',
|
home: '/',
|
||||||
|
|
||||||
chat: {
|
chat: {
|
||||||
new: ({ agentId }: { agentId: string }) => `/agents/${agentId}/chat`,
|
new: ({ agentId }: { agentId: string }) => `/assistants/${agentId}/chat`,
|
||||||
conversation: ({ agentId, conversationId }: { agentId: string; conversationId: string }) =>
|
conversation: ({ agentId, conversationId }: { agentId: string; conversationId: string }) =>
|
||||||
`/agents/${agentId}/chat/${conversationId}`,
|
`/assistants/${agentId}/chat/${conversationId}`,
|
||||||
},
|
},
|
||||||
|
assistants: {
|
||||||
agents: {
|
list: '/assistants',
|
||||||
list: '/agents',
|
create: '/assistants/create',
|
||||||
create: '/agents/create',
|
edit: ({ agentId }: { agentId: string }) => `/assistants/${agentId}/edit`,
|
||||||
edit: ({ agentId }: { agentId: string }) => `/agents/${agentId}/edit`,
|
workflow: ({ agentId }: { agentId: string }) => `/assistants/${agentId}/workflow`,
|
||||||
},
|
},
|
||||||
|
|
||||||
integrations: {
|
integrations: {
|
||||||
|
|
|
@ -1,174 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useMemo, useCallback } from 'react';
|
|
||||||
import {
|
|
||||||
EuiButton,
|
|
||||||
EuiFieldText,
|
|
||||||
EuiTextArea,
|
|
||||||
EuiFlexGroup,
|
|
||||||
EuiPanel,
|
|
||||||
EuiFlexItem,
|
|
||||||
EuiSpacer,
|
|
||||||
EuiForm,
|
|
||||||
EuiFormRow,
|
|
||||||
EuiSelect,
|
|
||||||
EuiDescribedFormGroup,
|
|
||||||
} from '@elastic/eui';
|
|
||||||
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
|
||||||
import { useNavigation } from '../../../hooks/use_navigation';
|
|
||||||
import { useKibana } from '../../../hooks/use_kibana';
|
|
||||||
import { useBreadcrumb } from '../../../hooks/use_breadcrumbs';
|
|
||||||
import { useAgentEdition } from '../../../hooks/use_agent_edition';
|
|
||||||
import { appPaths } from '../../../app_paths';
|
|
||||||
import { agentLabels } from '../i18n';
|
|
||||||
|
|
||||||
interface AgentEditViewProps {
|
|
||||||
agentId: string | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AgentEditView: React.FC<AgentEditViewProps> = ({ agentId }) => {
|
|
||||||
const { navigateToWorkchatUrl, createWorkchatUrl } = useNavigation();
|
|
||||||
const {
|
|
||||||
services: { notifications },
|
|
||||||
} = useKibana();
|
|
||||||
|
|
||||||
const breadcrumb = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{ text: agentLabels.breadcrumb.agentsPill, href: createWorkchatUrl(appPaths.agents.list) },
|
|
||||||
agentId
|
|
||||||
? { text: agentLabels.breadcrumb.editAgentPill }
|
|
||||||
: { text: agentLabels.breadcrumb.createAgensPill },
|
|
||||||
];
|
|
||||||
}, [agentId, createWorkchatUrl]);
|
|
||||||
|
|
||||||
useBreadcrumb(breadcrumb);
|
|
||||||
|
|
||||||
const handleCancel = useCallback(() => {
|
|
||||||
navigateToWorkchatUrl('/agents');
|
|
||||||
}, [navigateToWorkchatUrl]);
|
|
||||||
|
|
||||||
const onSaveSuccess = useCallback(() => {
|
|
||||||
notifications.toasts.addSuccess(
|
|
||||||
agentId
|
|
||||||
? agentLabels.notifications.agentUpdatedToastText
|
|
||||||
: agentLabels.notifications.agentCreatedToastText
|
|
||||||
);
|
|
||||||
navigateToWorkchatUrl('/agents');
|
|
||||||
}, [agentId, navigateToWorkchatUrl, notifications]);
|
|
||||||
|
|
||||||
const { editState, setFieldValue, submit, isSubmitting } = useAgentEdition({
|
|
||||||
agentId,
|
|
||||||
onSaveSuccess,
|
|
||||||
});
|
|
||||||
|
|
||||||
const onSubmit = useCallback(
|
|
||||||
(event: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
event.preventDefault();
|
|
||||||
if (isSubmitting) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
submit();
|
|
||||||
},
|
|
||||||
[submit, isSubmitting]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KibanaPageTemplate panelled>
|
|
||||||
<KibanaPageTemplate.Header
|
|
||||||
pageTitle={
|
|
||||||
agentId ? agentLabels.editView.editAgentTitle : agentLabels.editView.createAgentTitle
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<KibanaPageTemplate.Section grow={false} paddingSize="m">
|
|
||||||
<EuiPanel hasShadow={false} hasBorder={true}>
|
|
||||||
<EuiForm component="form" fullWidth onSubmit={onSubmit}>
|
|
||||||
<EuiDescribedFormGroup
|
|
||||||
ratio="third"
|
|
||||||
title={<h3>Base configuration</h3>}
|
|
||||||
description="Configure your agent"
|
|
||||||
>
|
|
||||||
<EuiFormRow label="Name">
|
|
||||||
<EuiFieldText
|
|
||||||
data-test-subj="workchatAppAgentEditViewFieldText"
|
|
||||||
name="name"
|
|
||||||
value={editState.name}
|
|
||||||
onChange={(e) => setFieldValue('name', e.target.value)}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
<EuiFormRow label="Description">
|
|
||||||
<EuiFieldText
|
|
||||||
data-test-subj="workchatAppAgentEditViewFieldText"
|
|
||||||
name="description"
|
|
||||||
value={editState.description}
|
|
||||||
onChange={(e) => setFieldValue('description', e.target.value)}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
<EuiFormRow label="Visibility">
|
|
||||||
<EuiSelect
|
|
||||||
data-test-subj="workchatAppAgentEditViewSelect"
|
|
||||||
name="public"
|
|
||||||
value={editState.public ? 'public' : 'private'}
|
|
||||||
options={[
|
|
||||||
{ value: 'public', text: 'Public - everyone can use it' },
|
|
||||||
{ value: 'private', text: 'Private - only you can use it' },
|
|
||||||
]}
|
|
||||||
onChange={(e) => setFieldValue('public', e.target.value === 'public')}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiDescribedFormGroup>
|
|
||||||
|
|
||||||
<EuiSpacer />
|
|
||||||
|
|
||||||
<EuiDescribedFormGroup
|
|
||||||
ratio="third"
|
|
||||||
title={<h3>Customization</h3>}
|
|
||||||
description="Optional parameters to customize the agent"
|
|
||||||
>
|
|
||||||
<EuiFormRow label="System prompt">
|
|
||||||
<EuiTextArea
|
|
||||||
data-test-subj="workchatAppAgentEditViewTextArea"
|
|
||||||
name="systemPrompt"
|
|
||||||
value={editState.systemPrompt}
|
|
||||||
onChange={(e) => setFieldValue('systemPrompt', e.target.value)}
|
|
||||||
/>
|
|
||||||
</EuiFormRow>
|
|
||||||
</EuiDescribedFormGroup>
|
|
||||||
|
|
||||||
<EuiSpacer />
|
|
||||||
|
|
||||||
<EuiFlexGroup justifyContent="spaceBetween">
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiButton
|
|
||||||
data-test-subj="workchatAppAgentEditViewCancelButton"
|
|
||||||
type="button"
|
|
||||||
iconType="framePrevious"
|
|
||||||
color="warning"
|
|
||||||
onClick={handleCancel}
|
|
||||||
>
|
|
||||||
{agentLabels.editView.cancelButtonLabel}
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFlexItem>
|
|
||||||
<EuiFlexItem grow={false}>
|
|
||||||
<EuiButton
|
|
||||||
data-test-subj="workchatAppAgentEditViewSaveButton"
|
|
||||||
type="submit"
|
|
||||||
iconType="save"
|
|
||||||
fill
|
|
||||||
disabled={isSubmitting}
|
|
||||||
>
|
|
||||||
{agentLabels.editView.saveButtonLabel}
|
|
||||||
</EuiButton>
|
|
||||||
</EuiFlexItem>
|
|
||||||
</EuiFlexGroup>
|
|
||||||
</EuiForm>
|
|
||||||
</EuiPanel>
|
|
||||||
</KibanaPageTemplate.Section>
|
|
||||||
</KibanaPageTemplate>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { i18n } from '@kbn/i18n';
|
|
||||||
|
|
||||||
export const agentLabels = {
|
|
||||||
breadcrumb: {
|
|
||||||
agentsPill: i18n.translate('workchatApp.agents.breadcrumb.agents', {
|
|
||||||
defaultMessage: 'Agents',
|
|
||||||
}),
|
|
||||||
editAgentPill: i18n.translate('workchatApp.agents.breadcrumb.editAgent', {
|
|
||||||
defaultMessage: 'Edit agent',
|
|
||||||
}),
|
|
||||||
createAgensPill: i18n.translate('workchatApp.agents.breadcrumb.createAgent', {
|
|
||||||
defaultMessage: 'Create agent',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
notifications: {
|
|
||||||
agentCreatedToastText: i18n.translate(
|
|
||||||
'workchatApp.agents.notifications.agentCreatedToastText',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Agent created',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
agentUpdatedToastText: i18n.translate(
|
|
||||||
'workchatApp.agents.notifications.agentCreatedToastText',
|
|
||||||
{
|
|
||||||
defaultMessage: 'Agent updated',
|
|
||||||
}
|
|
||||||
),
|
|
||||||
},
|
|
||||||
editView: {
|
|
||||||
createAgentTitle: i18n.translate('workchatApp.agents.editView.createTitle', {
|
|
||||||
defaultMessage: 'Create a new agent',
|
|
||||||
}),
|
|
||||||
editAgentTitle: i18n.translate('workchatApp.agents.editView.editTitle', {
|
|
||||||
defaultMessage: 'Edit agent',
|
|
||||||
}),
|
|
||||||
cancelButtonLabel: i18n.translate('workchatApp.agents.editView.cancelButtonLabel', {
|
|
||||||
defaultMessage: 'Cancel',
|
|
||||||
}),
|
|
||||||
saveButtonLabel: i18n.translate('workchatApp.agents.editView.saveButtonLabel', {
|
|
||||||
defaultMessage: 'Save',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import { EuiBasicTable, EuiBasicTableColumn, EuiButton } from '@elastic/eui';
|
|
||||||
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
|
||||||
import { Agent } from '../../../../../common/agents';
|
|
||||||
import { useNavigation } from '../../../hooks/use_navigation';
|
|
||||||
import { appPaths } from '../../../app_paths';
|
|
||||||
|
|
||||||
interface AgentListViewProps {
|
|
||||||
agents: Agent[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const AgentListView: React.FC<AgentListViewProps> = ({ agents }) => {
|
|
||||||
const { navigateToWorkchatUrl } = useNavigation();
|
|
||||||
const columns: Array<EuiBasicTableColumn<Agent>> = [
|
|
||||||
{ field: 'name', name: 'Name' },
|
|
||||||
{ field: 'description', name: 'Description' },
|
|
||||||
{ field: 'user.name', name: 'Created by' },
|
|
||||||
{ field: 'public', name: 'Public' },
|
|
||||||
{
|
|
||||||
name: 'Actions',
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
name: 'Edit',
|
|
||||||
description: 'Edit this agent',
|
|
||||||
isPrimary: true,
|
|
||||||
icon: 'documentEdit',
|
|
||||||
type: 'icon',
|
|
||||||
onClick: ({ id }) => {
|
|
||||||
navigateToWorkchatUrl(appPaths.agents.edit({ agentId: id }));
|
|
||||||
},
|
|
||||||
'data-test-subj': 'agentListTable-edit-btn',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<KibanaPageTemplate panelled>
|
|
||||||
<KibanaPageTemplate.Header pageTitle="Agents" />
|
|
||||||
|
|
||||||
<KibanaPageTemplate.Section grow={false} paddingSize="m">
|
|
||||||
<EuiButton
|
|
||||||
onClick={() => {
|
|
||||||
return navigateToWorkchatUrl('/agents/create');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Create new agent
|
|
||||||
</EuiButton>
|
|
||||||
</KibanaPageTemplate.Section>
|
|
||||||
|
|
||||||
<KibanaPageTemplate.Section>
|
|
||||||
<EuiBasicTable columns={columns} items={agents} />
|
|
||||||
</KibanaPageTemplate.Section>
|
|
||||||
</KibanaPageTemplate>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -0,0 +1,170 @@
|
||||||
|
/*
|
||||||
|
* 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, { Fragment, useCallback, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiButtonEmpty,
|
||||||
|
EuiForm,
|
||||||
|
EuiFormRow,
|
||||||
|
EuiModal,
|
||||||
|
EuiModalBody,
|
||||||
|
EuiModalFooter,
|
||||||
|
EuiModalHeader,
|
||||||
|
EuiModalHeaderTitle,
|
||||||
|
EuiFieldText,
|
||||||
|
EuiSuperSelect,
|
||||||
|
EuiText,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormProvider, useForm, Controller } from 'react-hook-form';
|
||||||
|
import { useKibana } from '../../hooks/use_kibana';
|
||||||
|
import { AgentEditState, useAgentEdition } from '../../hooks/use_agent_edition';
|
||||||
|
import type { Agent } from '../../../../common/agents';
|
||||||
|
import { appPaths } from '../../app_paths';
|
||||||
|
import { useNavigation } from '../../hooks/use_navigation';
|
||||||
|
import { assistantLabels } from './i18n';
|
||||||
|
import { ASSISTANT_USE_CASES } from './constants';
|
||||||
|
|
||||||
|
export interface CreateNewAssistantModalProps {
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateNewAssistantModal: React.FC<CreateNewAssistantModalProps> = ({ onClose }) => {
|
||||||
|
const {
|
||||||
|
services: { notifications },
|
||||||
|
} = useKibana();
|
||||||
|
|
||||||
|
const { navigateToWorkchatUrl } = useNavigation();
|
||||||
|
|
||||||
|
const onSaveSuccess = useCallback(
|
||||||
|
(agent: Agent) => {
|
||||||
|
notifications.toasts.addSuccess(
|
||||||
|
i18n.translate('workchatApp.assistants.createSuccessMessage', {
|
||||||
|
defaultMessage: 'Assistant created successfully',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
onClose();
|
||||||
|
navigateToWorkchatUrl(appPaths.assistants.edit({ agentId: agent.id }));
|
||||||
|
},
|
||||||
|
[notifications, onClose, navigateToWorkchatUrl]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onSaveError = useCallback(
|
||||||
|
(err: Error) => {
|
||||||
|
notifications.toasts.addError(err, {
|
||||||
|
title: 'Error',
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[notifications]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { state, isSubmitting, submit } = useAgentEdition({
|
||||||
|
onSaveSuccess,
|
||||||
|
onSaveError,
|
||||||
|
});
|
||||||
|
|
||||||
|
const formMethods = useForm<AgentEditState>({
|
||||||
|
values: state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { handleSubmit, control, watch, setValue } = formMethods;
|
||||||
|
|
||||||
|
const useCase = watch('useCase');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (useCase) {
|
||||||
|
const selectedUseCase = ASSISTANT_USE_CASES.find((uc) => uc.value === useCase);
|
||||||
|
if (selectedUseCase) {
|
||||||
|
setValue('systemPrompt', selectedUseCase.prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [useCase, setValue]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiModal onClose={onClose} style={{ width: 640 }}>
|
||||||
|
<EuiModalHeader>
|
||||||
|
<EuiModalHeaderTitle>
|
||||||
|
{i18n.translate('workchatApp.assistants.create.title', {
|
||||||
|
defaultMessage: 'Create new assistant',
|
||||||
|
})}
|
||||||
|
</EuiModalHeaderTitle>
|
||||||
|
</EuiModalHeader>
|
||||||
|
|
||||||
|
<EuiModalBody>
|
||||||
|
<FormProvider {...formMethods}>
|
||||||
|
<EuiForm component="form" onSubmit={handleSubmit((data) => submit(data))} fullWidth>
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.create.nameLabel', {
|
||||||
|
defaultMessage: 'Name',
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
rules={{ required: true }}
|
||||||
|
name="name"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<EuiFieldText data-test-subj="assistantNameInput" {...field} fullWidth />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
<Controller
|
||||||
|
name="useCase"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.editPromptModal.useCaseLabel', {
|
||||||
|
defaultMessage: 'Use case',
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<EuiSuperSelect
|
||||||
|
data-test-subj="assistantUseCaseSelect"
|
||||||
|
options={ASSISTANT_USE_CASES.map(({ label, value, description }) => ({
|
||||||
|
inputDisplay: label,
|
||||||
|
value,
|
||||||
|
dropdownDisplay: (
|
||||||
|
<Fragment>
|
||||||
|
<strong>{label}</strong>
|
||||||
|
{!!description && (
|
||||||
|
<EuiText size="s" color="subdued">
|
||||||
|
{description}
|
||||||
|
</EuiText>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
{...field}
|
||||||
|
valueOfSelected={field.value}
|
||||||
|
hasDividers
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</EuiForm>
|
||||||
|
</FormProvider>
|
||||||
|
</EuiModalBody>
|
||||||
|
|
||||||
|
<EuiModalFooter>
|
||||||
|
<EuiButtonEmpty onClick={onClose}>
|
||||||
|
{assistantLabels.editView.cancelButtonLabel}
|
||||||
|
</EuiButtonEmpty>
|
||||||
|
|
||||||
|
<EuiButton
|
||||||
|
data-test-subj="saveBasicInfoButton"
|
||||||
|
fill
|
||||||
|
onClick={handleSubmit((data) => submit(data))}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
|
{assistantLabels.editView.saveButtonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiModalFooter>
|
||||||
|
</EuiModal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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 { i18n } from '@kbn/i18n';
|
||||||
|
|
||||||
|
export const ASSISTANT_USE_CASES = [
|
||||||
|
{
|
||||||
|
value: 'customerSupport',
|
||||||
|
label: i18n.translate('workchatApp.assistants.editView.useCase.customerSupportLabel', {
|
||||||
|
defaultMessage: 'Customer Support',
|
||||||
|
}),
|
||||||
|
description: i18n.translate(
|
||||||
|
'workchatApp.assistants.editView.useCase.customerSupportDescription',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Help customers with inquiries and support issues',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
prompt:
|
||||||
|
'You are a helpful customer support assistant. Provide clear, accurate, and friendly responses to customer inquiries. Focus on resolving issues efficiently while maintaining a professional tone.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'dataAnalysis',
|
||||||
|
label: i18n.translate('workchatApp.assistants.editView.useCase.dataAnalysisLabel', {
|
||||||
|
defaultMessage: 'Data Analysis',
|
||||||
|
}),
|
||||||
|
description: i18n.translate('workchatApp.assistants.editView.useCase.dataAnalysisDescription', {
|
||||||
|
defaultMessage: 'Analyze and interpret data to provide insights',
|
||||||
|
}),
|
||||||
|
prompt:
|
||||||
|
'You are a data analysis assistant. Help users understand their data by identifying patterns, trends, and anomalies. Provide clear explanations of your findings and suggest actionable insights.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'custom',
|
||||||
|
label: i18n.translate('workchatApp.assistants.editView.useCase.customLabel', {
|
||||||
|
defaultMessage: 'Custom',
|
||||||
|
}),
|
||||||
|
description: i18n.translate('workchatApp.assistants.editView.useCase.customDescription', {
|
||||||
|
defaultMessage: 'Create a custom assistant for your specific needs',
|
||||||
|
}),
|
||||||
|
prompt: '',
|
||||||
|
},
|
||||||
|
];
|
|
@ -0,0 +1,266 @@
|
||||||
|
/*
|
||||||
|
* 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, useState, useCallback } from 'react';
|
||||||
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiPanel,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiIcon,
|
||||||
|
EuiText,
|
||||||
|
EuiTitle,
|
||||||
|
EuiLink,
|
||||||
|
EuiLoadingElastic,
|
||||||
|
EuiAvatar,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { useNavigation } from '../../../hooks/use_navigation';
|
||||||
|
import { useBreadcrumb } from '../../../hooks/use_breadcrumbs';
|
||||||
|
import { appPaths } from '../../../app_paths';
|
||||||
|
import { assistantLabels } from '../i18n';
|
||||||
|
import { useAgent } from '../../../hooks/use_agent';
|
||||||
|
import { useConversationList } from '../../../hooks/use_conversation_list';
|
||||||
|
import { sortAndGroupConversations } from '../../../utils/sort_and_group_conversations';
|
||||||
|
import { sliceRecentConversations } from '../../../utils/slice_recent_conversations';
|
||||||
|
import { EditAssistantBasicInfo } from './assistant_edit_basic_info_modal';
|
||||||
|
import { EditPrompt } from './assistant_edit_prompt_modal';
|
||||||
|
|
||||||
|
interface AssistantDetailsProps {
|
||||||
|
agentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AssistantDetails: React.FC<AssistantDetailsProps> = ({ agentId }) => {
|
||||||
|
const { navigateToWorkchatUrl, createWorkchatUrl } = useNavigation();
|
||||||
|
const { agent: assistant, isLoading, refetch } = useAgent({ agentId });
|
||||||
|
const { conversations } = useConversationList({ agentId });
|
||||||
|
|
||||||
|
// State for modals
|
||||||
|
const [isBasicInfoModalVisible, setIsBasicInfoModalVisible] = useState(false);
|
||||||
|
const [isPromptModalVisible, setIsPromptModalVisible] = useState(false);
|
||||||
|
|
||||||
|
const conversationGroups = useMemo(() => {
|
||||||
|
return sortAndGroupConversations(sliceRecentConversations(conversations, 10));
|
||||||
|
}, [conversations]);
|
||||||
|
|
||||||
|
const breadcrumb = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: assistantLabels.breadcrumb.assistantsPill,
|
||||||
|
href: createWorkchatUrl(appPaths.assistants.list),
|
||||||
|
},
|
||||||
|
{ text: assistantLabels.breadcrumb.assistantDetailsPill },
|
||||||
|
];
|
||||||
|
}, [createWorkchatUrl]);
|
||||||
|
|
||||||
|
useBreadcrumb(breadcrumb);
|
||||||
|
|
||||||
|
const handleOpenBasicInfoModal = useCallback(() => {
|
||||||
|
setIsBasicInfoModalVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleCloseBasicInfoModal = useCallback(() => {
|
||||||
|
setIsBasicInfoModalVisible(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleOpenPromptModal = useCallback(() => {
|
||||||
|
setIsPromptModalVisible(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleClosePromptModal = useCallback(() => {
|
||||||
|
setIsPromptModalVisible(false);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSaveSuccess = useCallback(() => {
|
||||||
|
refetch();
|
||||||
|
}, [refetch]);
|
||||||
|
|
||||||
|
if (isLoading || !assistant) {
|
||||||
|
return (
|
||||||
|
<KibanaPageTemplate.EmptyPrompt>
|
||||||
|
<EuiLoadingElastic />
|
||||||
|
</KibanaPageTemplate.EmptyPrompt>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssistantBasicInfo = () => (
|
||||||
|
<EuiPanel hasShadow={false} hasBorder={true}>
|
||||||
|
<EuiFlexGroup alignItems="flexStart" justifyContent="spaceBetween">
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiFlexGroup alignItems="center" direction="row" gutterSize="xl">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiAvatar
|
||||||
|
size="xl"
|
||||||
|
name={assistant.name}
|
||||||
|
initials={assistant.avatar?.text}
|
||||||
|
color={assistant.avatar?.color}
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexGroup direction="column" gutterSize="s">
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiTitle size="s">
|
||||||
|
<span>{assistant.name}</span>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiText size="s" color="subdued">
|
||||||
|
{assistant.description}
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton
|
||||||
|
size="s"
|
||||||
|
iconType="pencil"
|
||||||
|
color="text"
|
||||||
|
onClick={handleOpenBasicInfoModal}
|
||||||
|
data-test-subj="editAssistantBasicInfoButton"
|
||||||
|
>
|
||||||
|
{assistantLabels.editView.editButtonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiPanel>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AssistantPrompt = () => (
|
||||||
|
<EuiPanel hasShadow={false} hasBorder={true}>
|
||||||
|
<EuiFlexGroup alignItems="flexStart" justifyContent="spaceBetween">
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiText size="m" color="subdued">
|
||||||
|
{assistant?.configuration?.systemPrompt}
|
||||||
|
</EuiText>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiButton
|
||||||
|
size="s"
|
||||||
|
iconType="pencil"
|
||||||
|
color="text"
|
||||||
|
onClick={handleOpenPromptModal}
|
||||||
|
data-test-subj="editAssistantPromptButton"
|
||||||
|
>
|
||||||
|
{assistantLabels.editView.editButtonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiPanel>
|
||||||
|
);
|
||||||
|
|
||||||
|
const AssistantChatHistory = () => (
|
||||||
|
<EuiPanel hasShadow={false} hasBorder={true}>
|
||||||
|
<EuiFlexGroup direction="column" gutterSize="l">
|
||||||
|
{conversationGroups.map(({ conversations: groupConversations, dateLabel }) => (
|
||||||
|
<EuiFlexItem key={dateLabel}>
|
||||||
|
<EuiPanel hasBorder={false} hasShadow={false} color="transparent" paddingSize="s">
|
||||||
|
<EuiText size="s">
|
||||||
|
<h4>{dateLabel}</h4>
|
||||||
|
</EuiText>
|
||||||
|
</EuiPanel>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<EuiFlexGroup direction="column" gutterSize="m">
|
||||||
|
{groupConversations.map((conversation) => (
|
||||||
|
<EuiFlexItem key={conversation.id}>
|
||||||
|
<EuiLink
|
||||||
|
color="subdued"
|
||||||
|
onClick={() => {
|
||||||
|
navigateToWorkchatUrl(
|
||||||
|
appPaths.chat.conversation({
|
||||||
|
agentId: assistant.id,
|
||||||
|
conversationId: conversation.id,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{conversation.title}
|
||||||
|
</EuiLink>
|
||||||
|
</EuiFlexItem>
|
||||||
|
))}
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
</EuiFlexItem>
|
||||||
|
))}
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiPanel>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<KibanaPageTemplate.Section paddingSize="m">
|
||||||
|
<EuiSpacer size="l" />
|
||||||
|
|
||||||
|
<EuiFlexGroup gutterSize="xl" alignItems="flexStart" direction="row">
|
||||||
|
<EuiFlexItem grow={2}>
|
||||||
|
<EuiFlexGroup gutterSize="l" direction="column">
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiFlexGroup alignItems="center" gutterSize="xs">
|
||||||
|
<EuiIcon type="user" size="m" />
|
||||||
|
<EuiTitle size="xxs">
|
||||||
|
<h4>
|
||||||
|
{i18n.translate('workchatApp.assistants.basicInfoTitle', {
|
||||||
|
defaultMessage: 'Basic',
|
||||||
|
})}
|
||||||
|
</h4>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<AssistantBasicInfo />
|
||||||
|
</EuiFlexItem>
|
||||||
|
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiFlexGroup alignItems="center" gutterSize="xs">
|
||||||
|
<EuiIcon type="gear" size="m" />
|
||||||
|
<EuiTitle size="xxs">
|
||||||
|
<h4>
|
||||||
|
{i18n.translate('workchatApp.assistants.promptTitle', {
|
||||||
|
defaultMessage: 'Prompt',
|
||||||
|
})}
|
||||||
|
</h4>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<AssistantPrompt />
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
|
||||||
|
<EuiFlexItem grow={1}>
|
||||||
|
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||||
|
<EuiIcon type="list" size="m" />
|
||||||
|
<EuiTitle size="xxs">
|
||||||
|
<h4>
|
||||||
|
{i18n.translate('workchatApp.home.recentConversations.title', {
|
||||||
|
defaultMessage: 'Recent conversations',
|
||||||
|
})}
|
||||||
|
</h4>
|
||||||
|
</EuiTitle>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
<AssistantChatHistory />
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</KibanaPageTemplate.Section>
|
||||||
|
|
||||||
|
{/* Modals */}
|
||||||
|
{isBasicInfoModalVisible && (
|
||||||
|
<EditAssistantBasicInfo
|
||||||
|
agentId={agentId}
|
||||||
|
onClose={handleCloseBasicInfoModal}
|
||||||
|
onSaveSuccess={handleSaveSuccess}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{isPromptModalVisible && (
|
||||||
|
<EditPrompt
|
||||||
|
agentId={agentId}
|
||||||
|
onClose={handleClosePromptModal}
|
||||||
|
onSaveSuccess={handleSaveSuccess}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,226 @@
|
||||||
|
/*
|
||||||
|
* 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 { useForm, Controller } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiButtonEmpty,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiFlexItem,
|
||||||
|
EuiForm,
|
||||||
|
EuiFormRow,
|
||||||
|
EuiModal,
|
||||||
|
EuiModalBody,
|
||||||
|
EuiModalFooter,
|
||||||
|
EuiModalHeader,
|
||||||
|
EuiModalHeaderTitle,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiTextArea,
|
||||||
|
EuiColorPicker,
|
||||||
|
EuiText,
|
||||||
|
EuiFieldText,
|
||||||
|
EuiFormHelpText,
|
||||||
|
EuiAvatar,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { euiPaletteColorBlind } from '@elastic/eui';
|
||||||
|
import { useKibana } from '../../../hooks/use_kibana';
|
||||||
|
import { useAgentEdition } from '../../../hooks/use_agent_edition';
|
||||||
|
import { assistantLabels } from '../i18n';
|
||||||
|
|
||||||
|
export interface EditAssistantBasicInfoProps {
|
||||||
|
onClose: () => void;
|
||||||
|
onSaveSuccess: () => void;
|
||||||
|
agentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVATAR_COLORS = euiPaletteColorBlind();
|
||||||
|
|
||||||
|
export const EditAssistantBasicInfo: React.FC<EditAssistantBasicInfoProps> = ({
|
||||||
|
onClose,
|
||||||
|
agentId,
|
||||||
|
onSaveSuccess,
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
services: { notifications },
|
||||||
|
} = useKibana();
|
||||||
|
|
||||||
|
const { state, submit, isSubmitting } = useAgentEdition({
|
||||||
|
agentId,
|
||||||
|
onSaveSuccess: () => {
|
||||||
|
notifications.toasts.addSuccess(
|
||||||
|
i18n.translate('workchatApp.assistants.editBasicsModal.saveSuccessMessage', {
|
||||||
|
defaultMessage: 'Assistant updated successfully',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
onSaveSuccess();
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { control, handleSubmit, watch } = useForm({
|
||||||
|
values: state,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get form values for the avatar preview
|
||||||
|
const name = watch('name');
|
||||||
|
const avatarCustomText = watch('avatarCustomText');
|
||||||
|
const avatarColor = watch('avatarColor');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiModal onClose={onClose} style={{ width: 800 }}>
|
||||||
|
<EuiModalHeader>
|
||||||
|
<EuiModalHeaderTitle>
|
||||||
|
{i18n.translate('workchatApp.assistants.editBasicsModal.title', {
|
||||||
|
defaultMessage: 'Edit Assistant Basics',
|
||||||
|
})}
|
||||||
|
</EuiModalHeaderTitle>
|
||||||
|
</EuiModalHeader>
|
||||||
|
|
||||||
|
<EuiModalBody>
|
||||||
|
<EuiForm component="form" onSubmit={handleSubmit((data) => submit(data))} fullWidth>
|
||||||
|
<EuiText>
|
||||||
|
<h4>
|
||||||
|
{i18n.translate('workchatApp.assistants.editBasicsModal.identificationSection', {
|
||||||
|
defaultMessage: 'Identification',
|
||||||
|
})}
|
||||||
|
</h4>
|
||||||
|
</EuiText>
|
||||||
|
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="name"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: true }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.editBasicsModal.nameLabel', {
|
||||||
|
defaultMessage: 'Name',
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<EuiFieldText data-test-subj="assistantNameInput" {...field} fullWidth />
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="description"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.editBasicsModal.descriptionLabel', {
|
||||||
|
defaultMessage: 'Description',
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<EuiTextArea
|
||||||
|
data-test-subj="assistantDescriptionInput"
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
rows={6}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<EuiFormHelpText>
|
||||||
|
{i18n.translate('workchatApp.assistants.editBasicsModal.descriptionHelpText', {
|
||||||
|
defaultMessage: 'Describe what this assistant is going to be used for.',
|
||||||
|
})}
|
||||||
|
</EuiFormHelpText>
|
||||||
|
|
||||||
|
<EuiSpacer size="l" />
|
||||||
|
|
||||||
|
<EuiText>
|
||||||
|
<h4>
|
||||||
|
{i18n.translate('workchatApp.assistants.editBasicsModal.avatarSection', {
|
||||||
|
defaultMessage: 'Avatar',
|
||||||
|
})}
|
||||||
|
</h4>
|
||||||
|
</EuiText>
|
||||||
|
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
<EuiFlexGroup alignItems="center">
|
||||||
|
<EuiFlexItem grow={false}>
|
||||||
|
<EuiAvatar initials={avatarCustomText} name={name} color={avatarColor} size="xl" />
|
||||||
|
</EuiFlexItem>
|
||||||
|
|
||||||
|
<EuiFlexItem>
|
||||||
|
<EuiFlexGroup>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<Controller
|
||||||
|
name="avatarColor"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, value } }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.editBasicsModal.colorLabel', {
|
||||||
|
defaultMessage: 'Color',
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<EuiColorPicker
|
||||||
|
data-test-subj="assistantAvatarColorPicker"
|
||||||
|
onChange={onChange}
|
||||||
|
color={value}
|
||||||
|
swatches={AVATAR_COLORS}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
<EuiFlexItem>
|
||||||
|
<Controller
|
||||||
|
name="avatarCustomText"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.editBasicsModal.textLabel', {
|
||||||
|
defaultMessage: 'Custom Text',
|
||||||
|
})}
|
||||||
|
helpText={i18n.translate(
|
||||||
|
'workchatApp.assistants.editBasicsModal.emojiHelpText',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Press CTRL + CMD + Space for emojis',
|
||||||
|
}
|
||||||
|
)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<EuiFieldText
|
||||||
|
data-test-subj="assistantAvatarTextField"
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiFlexItem>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
</EuiForm>
|
||||||
|
</EuiModalBody>
|
||||||
|
|
||||||
|
<EuiModalFooter>
|
||||||
|
<EuiButtonEmpty onClick={onClose}>
|
||||||
|
{assistantLabels.editView.cancelButtonLabel}
|
||||||
|
</EuiButtonEmpty>
|
||||||
|
|
||||||
|
<EuiButton
|
||||||
|
data-test-subj="saveBasicInfoButton"
|
||||||
|
fill
|
||||||
|
onClick={handleSubmit((data) => submit(data))}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
|
{assistantLabels.editView.saveButtonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiModalFooter>
|
||||||
|
</EuiModal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
* 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, { Fragment, useEffect } from 'react';
|
||||||
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import {
|
||||||
|
EuiButton,
|
||||||
|
EuiButtonEmpty,
|
||||||
|
EuiForm,
|
||||||
|
EuiFormRow,
|
||||||
|
EuiModal,
|
||||||
|
EuiModalBody,
|
||||||
|
EuiModalFooter,
|
||||||
|
EuiModalHeader,
|
||||||
|
EuiModalHeaderTitle,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiSuperSelect,
|
||||||
|
EuiText,
|
||||||
|
EuiTextArea,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { useKibana } from '../../../hooks/use_kibana';
|
||||||
|
import { useAgentEdition } from '../../../hooks/use_agent_edition';
|
||||||
|
import { assistantLabels } from '../i18n';
|
||||||
|
import { ASSISTANT_USE_CASES } from '../constants';
|
||||||
|
|
||||||
|
export interface EditPromptProps {
|
||||||
|
onClose: () => void;
|
||||||
|
onSaveSuccess: () => void;
|
||||||
|
agentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EditPrompt: React.FC<EditPromptProps> = ({ onClose, onSaveSuccess, agentId }) => {
|
||||||
|
const {
|
||||||
|
services: { notifications },
|
||||||
|
} = useKibana();
|
||||||
|
|
||||||
|
const { state, submit, isSubmitting } = useAgentEdition({
|
||||||
|
agentId,
|
||||||
|
onSaveSuccess: () => {
|
||||||
|
notifications.toasts.addSuccess(
|
||||||
|
i18n.translate('workchatApp.assistants.editPromptModal.saveSuccessMessage', {
|
||||||
|
defaultMessage: 'Assistant updated successfully',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
onSaveSuccess();
|
||||||
|
onClose();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const { control, handleSubmit, watch, setValue } = useForm({
|
||||||
|
values: state,
|
||||||
|
});
|
||||||
|
|
||||||
|
const useCase = watch('useCase');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (useCase && useCase !== 'custom') {
|
||||||
|
const selectedUseCase = ASSISTANT_USE_CASES.find((uc) => uc.value === useCase);
|
||||||
|
if (selectedUseCase && selectedUseCase.prompt) {
|
||||||
|
setValue('systemPrompt', selectedUseCase.prompt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [useCase, setValue]);
|
||||||
|
|
||||||
|
const handlePromptChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
|
const newPrompt = e.target.value;
|
||||||
|
setValue('systemPrompt', newPrompt);
|
||||||
|
|
||||||
|
if (useCase !== 'custom') {
|
||||||
|
setValue('useCase', 'custom');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<EuiModal onClose={onClose} style={{ width: 800 }}>
|
||||||
|
<EuiModalHeader>
|
||||||
|
<EuiModalHeaderTitle>
|
||||||
|
{i18n.translate('workchatApp.assistants.editPromptModal.title', {
|
||||||
|
defaultMessage: 'Edit prompt',
|
||||||
|
})}
|
||||||
|
</EuiModalHeaderTitle>
|
||||||
|
</EuiModalHeader>
|
||||||
|
|
||||||
|
<EuiModalBody>
|
||||||
|
<EuiForm component="form" onSubmit={handleSubmit((data) => submit(data))} fullWidth>
|
||||||
|
<Controller
|
||||||
|
name="useCase"
|
||||||
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.editPromptModal.useCaseLabel', {
|
||||||
|
defaultMessage: 'Use case',
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<EuiSuperSelect
|
||||||
|
data-test-subj="assistantUseCaseSelect"
|
||||||
|
options={ASSISTANT_USE_CASES.map(({ label, value, description }) => ({
|
||||||
|
inputDisplay: label,
|
||||||
|
value,
|
||||||
|
dropdownDisplay: (
|
||||||
|
<Fragment>
|
||||||
|
<strong>{label}</strong>
|
||||||
|
{!!description && (
|
||||||
|
<EuiText size="s" color="subdued">
|
||||||
|
{description}
|
||||||
|
</EuiText>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
),
|
||||||
|
}))}
|
||||||
|
{...field}
|
||||||
|
valueOfSelected={field.value}
|
||||||
|
hasDividers
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="systemPrompt"
|
||||||
|
control={control}
|
||||||
|
rules={{ required: true }}
|
||||||
|
render={({ field }) => (
|
||||||
|
<EuiFormRow
|
||||||
|
label={i18n.translate('workchatApp.assistants.editPromptModal.promptLabel', {
|
||||||
|
defaultMessage: 'Prompt',
|
||||||
|
})}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<EuiTextArea
|
||||||
|
data-test-subj="assistantPromptTextArea"
|
||||||
|
{...field}
|
||||||
|
onChange={handlePromptChange}
|
||||||
|
fullWidth
|
||||||
|
rows={8}
|
||||||
|
/>
|
||||||
|
</EuiFormRow>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</EuiForm>
|
||||||
|
</EuiModalBody>
|
||||||
|
|
||||||
|
<EuiModalFooter>
|
||||||
|
<EuiButtonEmpty onClick={onClose}>
|
||||||
|
{assistantLabels.editView.cancelButtonLabel}
|
||||||
|
</EuiButtonEmpty>
|
||||||
|
|
||||||
|
<EuiButton
|
||||||
|
data-test-subj="savePromptButton"
|
||||||
|
fill
|
||||||
|
onClick={handleSubmit((data) => submit(data))}
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
>
|
||||||
|
{assistantLabels.editView.saveButtonLabel}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiModalFooter>
|
||||||
|
</EuiModal>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
* 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 { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { EuiButton, EuiButtonEmpty } from '@elastic/eui';
|
||||||
|
import { useNavigation } from '../../../hooks/use_navigation';
|
||||||
|
import { appPaths } from '../../../app_paths';
|
||||||
|
import { AssistantDetails } from './assistant_details';
|
||||||
|
import { AssistantWorkflow } from './assistant_workflow';
|
||||||
|
import { useAgent } from '../../../hooks/use_agent';
|
||||||
|
|
||||||
|
interface AssistantViewProps {
|
||||||
|
agentId: string;
|
||||||
|
selectedTab: 'details' | 'workflow';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AssistantView: React.FC<AssistantViewProps> = ({ agentId, selectedTab }) => {
|
||||||
|
const { navigateToWorkchatUrl } = useNavigation();
|
||||||
|
|
||||||
|
const { agent } = useAgent({ agentId });
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
label: i18n.translate('workchatApp.assistant.tabs.overviewTitle', {
|
||||||
|
defaultMessage: 'Overview',
|
||||||
|
}),
|
||||||
|
id: 'details',
|
||||||
|
onClick: () => navigateToWorkchatUrl(appPaths.assistants.edit({ agentId })),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: i18n.translate('workchatApp.assistant.tabs.workflowTitle', {
|
||||||
|
defaultMessage: 'Workflows',
|
||||||
|
}),
|
||||||
|
id: 'workflow',
|
||||||
|
onClick: () => navigateToWorkchatUrl(appPaths.assistants.workflow({ agentId })),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const headerButtons = [
|
||||||
|
<EuiButton
|
||||||
|
iconType={'newChat'}
|
||||||
|
color="primary"
|
||||||
|
fill
|
||||||
|
iconSide="left"
|
||||||
|
onClick={() => navigateToWorkchatUrl(appPaths.chat.new({ agentId }))}
|
||||||
|
>
|
||||||
|
New conversation
|
||||||
|
</EuiButton>,
|
||||||
|
<EuiButtonEmpty iconType={'questionInCircle'} color="primary" iconSide="left" href="/">
|
||||||
|
Learn more
|
||||||
|
</EuiButtonEmpty>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<KibanaPageTemplate data-test-subj="workChatEditAssistantPage">
|
||||||
|
<KibanaPageTemplate.Header
|
||||||
|
pageTitle={agent?.name}
|
||||||
|
rightSideItems={headerButtons}
|
||||||
|
tabs={tabs.map((tab) => {
|
||||||
|
return {
|
||||||
|
...tab,
|
||||||
|
isSelected: tab.id === selectedTab,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
{selectedTab === 'details' && <AssistantDetails agentId={agentId} />}
|
||||||
|
{selectedTab === 'workflow' && <AssistantWorkflow agentId={agentId} />}
|
||||||
|
</KibanaPageTemplate>
|
||||||
|
);
|
||||||
|
};
|
|
@ -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 React, { useMemo } from 'react';
|
||||||
|
|
||||||
|
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||||
|
import { useNavigation } from '../../../hooks/use_navigation';
|
||||||
|
import { assistantLabels } from '../i18n';
|
||||||
|
import { appPaths } from '../../../app_paths';
|
||||||
|
import { useBreadcrumb } from '../../../hooks/use_breadcrumbs';
|
||||||
|
|
||||||
|
interface AssistantWorkflowProps {
|
||||||
|
agentId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AssistantWorkflow: React.FC<AssistantWorkflowProps> = ({ agentId }) => {
|
||||||
|
const { createWorkchatUrl } = useNavigation();
|
||||||
|
|
||||||
|
const breadcrumb = useMemo(() => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
text: assistantLabels.breadcrumb.assistantsPill,
|
||||||
|
href: createWorkchatUrl(appPaths.assistants.list),
|
||||||
|
},
|
||||||
|
{ text: assistantLabels.breadcrumb.assistantWorkflowPill },
|
||||||
|
];
|
||||||
|
}, [createWorkchatUrl]);
|
||||||
|
|
||||||
|
useBreadcrumb(breadcrumb);
|
||||||
|
return <KibanaPageTemplate.Section paddingSize="m">todo...</KibanaPageTemplate.Section>;
|
||||||
|
};
|
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* 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 assistantLabels = {
|
||||||
|
breadcrumb: {
|
||||||
|
assistantsPill: i18n.translate('workchatApp.assistants.breadcrumb.assistants', {
|
||||||
|
defaultMessage: 'Assistants',
|
||||||
|
}),
|
||||||
|
assistantDetailsPill: i18n.translate('workchatApp.assistants.breadcrumb.assistantOverview', {
|
||||||
|
defaultMessage: 'Overview',
|
||||||
|
}),
|
||||||
|
assistantWorkflowPill: i18n.translate('workchatApp.assistants.breadcrumb.assistantWorkflow', {
|
||||||
|
defaultMessage: 'Workflows',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
notifications: {
|
||||||
|
assistantCreatedToastText: i18n.translate(
|
||||||
|
'workchatApp.assistants.notifications.assistantCreatedToastText',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Assistant created',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
assistantUpdatedToastText: i18n.translate(
|
||||||
|
'workchatApp.assistants.notifications.assistantCreatedToastText',
|
||||||
|
{
|
||||||
|
defaultMessage: 'Assistant updated',
|
||||||
|
}
|
||||||
|
),
|
||||||
|
},
|
||||||
|
editView: {
|
||||||
|
createassistantTitle: i18n.translate('workchatApp.assistants.editView.createTitle', {
|
||||||
|
defaultMessage: 'Create a new assistant',
|
||||||
|
}),
|
||||||
|
editassistantTitle: i18n.translate('workchatApp.assistants.editView.editTitle', {
|
||||||
|
defaultMessage: 'Edit assistant',
|
||||||
|
}),
|
||||||
|
cancelButtonLabel: i18n.translate('workchatApp.assistants.editView.cancelButtonLabel', {
|
||||||
|
defaultMessage: 'Cancel',
|
||||||
|
}),
|
||||||
|
saveButtonLabel: i18n.translate('workchatApp.assistants.editView.saveButtonLabel', {
|
||||||
|
defaultMessage: 'Save',
|
||||||
|
}),
|
||||||
|
editButtonLabel: i18n.translate('workchatApp.assistants.editView.editButtonLabel', {
|
||||||
|
defaultMessage: 'Edit',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,210 @@
|
||||||
|
/*
|
||||||
|
* 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 {
|
||||||
|
Comparators,
|
||||||
|
Criteria,
|
||||||
|
EuiAvatar,
|
||||||
|
EuiBasicTable,
|
||||||
|
EuiBasicTableColumn,
|
||||||
|
EuiButton,
|
||||||
|
EuiButtonEmpty,
|
||||||
|
EuiFlexGroup,
|
||||||
|
EuiSpacer,
|
||||||
|
EuiTableSortingType,
|
||||||
|
EuiText,
|
||||||
|
} from '@elastic/eui';
|
||||||
|
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
|
||||||
|
import { i18n } from '@kbn/i18n';
|
||||||
|
import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
|
import { Agent } from '../../../../../common/agents';
|
||||||
|
import { useNavigation } from '../../../hooks/use_navigation';
|
||||||
|
import { appPaths } from '../../../app_paths';
|
||||||
|
import { CreateNewAssistantModal } from '../assistant_create_modal';
|
||||||
|
|
||||||
|
interface AssistantListViewProps {
|
||||||
|
agents: Agent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AssistantListView: React.FC<AssistantListViewProps> = ({ agents }) => {
|
||||||
|
const { navigateToWorkchatUrl } = useNavigation();
|
||||||
|
|
||||||
|
const [sortField, setSortField] = useState<keyof Agent>('name');
|
||||||
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
||||||
|
const [pageIndex, setPageIndex] = useState(0);
|
||||||
|
const [pageSize, setPageSize] = useState(10);
|
||||||
|
|
||||||
|
const [createModalOpen, setCreateModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const columns: Array<EuiBasicTableColumn<Agent>> = [
|
||||||
|
{
|
||||||
|
field: 'name',
|
||||||
|
name: 'Name',
|
||||||
|
sortable: true,
|
||||||
|
render: (name: Agent['name'], agent: Agent) => (
|
||||||
|
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||||
|
<EuiAvatar
|
||||||
|
size="m"
|
||||||
|
name={agent.name}
|
||||||
|
initials={agent.avatar?.text}
|
||||||
|
color={agent.avatar?.color}
|
||||||
|
/>
|
||||||
|
<EuiButtonEmpty
|
||||||
|
onClick={() => navigateToWorkchatUrl(appPaths.assistants.edit({ agentId: agent.id }))}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</EuiButtonEmpty>
|
||||||
|
</EuiFlexGroup>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ field: 'user.name', name: 'Created by' },
|
||||||
|
{ field: 'public', name: 'Visibility' },
|
||||||
|
{
|
||||||
|
name: 'Actions',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
name: 'Edit',
|
||||||
|
description: 'Edit this agent',
|
||||||
|
isPrimary: true,
|
||||||
|
icon: 'documentEdit',
|
||||||
|
type: 'icon',
|
||||||
|
onClick: ({ id }) => {
|
||||||
|
navigateToWorkchatUrl(appPaths.assistants.edit({ agentId: id }));
|
||||||
|
},
|
||||||
|
'data-test-subj': 'agentListTable-edit-btn',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const findAgents = (
|
||||||
|
agentsList: Agent[],
|
||||||
|
currentPageIndex: number,
|
||||||
|
currentPageSize: number,
|
||||||
|
currentSortField: keyof Agent,
|
||||||
|
currentSortDirection: 'asc' | 'desc'
|
||||||
|
) => {
|
||||||
|
let items;
|
||||||
|
|
||||||
|
if (currentSortField) {
|
||||||
|
items = agentsList
|
||||||
|
.slice(0)
|
||||||
|
.sort(Comparators.property(currentSortField, Comparators.default(currentSortDirection)));
|
||||||
|
} else {
|
||||||
|
items = agentsList;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pageOfItems;
|
||||||
|
|
||||||
|
if (!currentPageIndex && !currentPageSize) {
|
||||||
|
pageOfItems = items;
|
||||||
|
} else {
|
||||||
|
const startIndex = currentPageIndex * currentPageSize;
|
||||||
|
pageOfItems = items.slice(
|
||||||
|
startIndex,
|
||||||
|
Math.min(startIndex + currentPageSize, agentsList.length)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
pageOfItems,
|
||||||
|
totalItemCount: agentsList.length,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const headerButtons = [
|
||||||
|
<EuiButton
|
||||||
|
iconType={'plusInCircle'}
|
||||||
|
color="primary"
|
||||||
|
fill
|
||||||
|
iconSide="left"
|
||||||
|
onClick={() => setCreateModalOpen(true)}
|
||||||
|
>
|
||||||
|
New
|
||||||
|
</EuiButton>,
|
||||||
|
<EuiButtonEmpty iconType={'questionInCircle'} color="primary" iconSide="left" href="/">
|
||||||
|
Learn more
|
||||||
|
</EuiButtonEmpty>,
|
||||||
|
];
|
||||||
|
|
||||||
|
const onTableChange = ({ page, sort }: Criteria<Agent>) => {
|
||||||
|
if (page) {
|
||||||
|
const { index, size } = page;
|
||||||
|
setPageIndex(index);
|
||||||
|
setPageSize(size);
|
||||||
|
}
|
||||||
|
if (sort) {
|
||||||
|
const { field, direction } = sort;
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection(direction);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const { pageOfItems, totalItemCount } = findAgents(
|
||||||
|
agents,
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
sortField,
|
||||||
|
sortDirection
|
||||||
|
);
|
||||||
|
|
||||||
|
const sorting: EuiTableSortingType<Agent> = {
|
||||||
|
sort: {
|
||||||
|
field: sortField,
|
||||||
|
direction: sortDirection,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const pagination = {
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
totalItemCount,
|
||||||
|
pageSizeOptions: [10, 20, 50],
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultsCount = (
|
||||||
|
<FormattedMessage
|
||||||
|
id="workchatApp.assistants.list.resultsCountLabel"
|
||||||
|
defaultMessage="Showing {start}-{end} of {total}"
|
||||||
|
values={{
|
||||||
|
start: <strong>{pageSize * pageIndex + 1}</strong>,
|
||||||
|
end: <strong>{pageSize * pageIndex + pageSize}</strong>,
|
||||||
|
total: totalItemCount,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<KibanaPageTemplate data-test-subj="workChatAssistantsListPage">
|
||||||
|
<KibanaPageTemplate.Header
|
||||||
|
pageTitle={i18n.translate('workchatApp.assistants.pageTitle', {
|
||||||
|
defaultMessage: 'Assistants',
|
||||||
|
})}
|
||||||
|
description={i18n.translate('workchatApp.assistants.pageDescription', {
|
||||||
|
defaultMessage:
|
||||||
|
'An assistant is an AI helper built around your data. It can search, summarize, and take action using your connected integrations. You can manage or create assistants from here.',
|
||||||
|
})}
|
||||||
|
rightSideItems={headerButtons}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<KibanaPageTemplate.Section>
|
||||||
|
<EuiText size="xs">{resultsCount}</EuiText>
|
||||||
|
<EuiSpacer size="m" />
|
||||||
|
|
||||||
|
<EuiBasicTable
|
||||||
|
columns={columns}
|
||||||
|
items={pageOfItems}
|
||||||
|
sorting={sorting}
|
||||||
|
onChange={onTableChange}
|
||||||
|
pagination={pagination}
|
||||||
|
/>
|
||||||
|
{createModalOpen && <CreateNewAssistantModal onClose={() => setCreateModalOpen(false)} />}
|
||||||
|
</KibanaPageTemplate.Section>
|
||||||
|
</KibanaPageTemplate>
|
||||||
|
);
|
||||||
|
};
|
|
@ -97,6 +97,8 @@ export const ChatNewConversationPrompt: React.FC<ChatNewConversationPromptProps>
|
||||||
name={agent?.name ?? chatCommonLabels.assistant.defaultNameLabel}
|
name={agent?.name ?? chatCommonLabels.assistant.defaultNameLabel}
|
||||||
size="xl"
|
size="xl"
|
||||||
type="user"
|
type="user"
|
||||||
|
initials={agent?.avatar?.text}
|
||||||
|
color={agent?.avatar?.color}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem>
|
<EuiFlexItem>
|
||||||
|
|
|
@ -34,7 +34,13 @@ export const AssistantBlock: React.FC<AssistantBlockProps> = ({ agentId }) => {
|
||||||
return (
|
return (
|
||||||
<EuiFlexGroup direction="row" alignItems="center" gutterSize="m">
|
<EuiFlexGroup direction="row" alignItems="center" gutterSize="m">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiAvatar name={agent?.name ?? 'Assistant'} size="l" type="user" />
|
<EuiAvatar
|
||||||
|
name={agent?.name ?? 'Assistant'}
|
||||||
|
size="l"
|
||||||
|
type="user"
|
||||||
|
initials={agent?.avatar?.text}
|
||||||
|
color={agent?.avatar?.color}
|
||||||
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow>
|
<EuiFlexItem grow>
|
||||||
<EuiFlexGroup
|
<EuiFlexGroup
|
||||||
|
|
|
@ -33,14 +33,19 @@ export const HomeAssistantsSection: React.FC<{}> = () => {
|
||||||
<EuiPanel key={assistant.id} paddingSize="m" hasBorder={true}>
|
<EuiPanel key={assistant.id} paddingSize="m" hasBorder={true}>
|
||||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
|
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" gutterSize="s">
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiAvatar size="l" name={assistant.name} />
|
<EuiAvatar
|
||||||
|
size="l"
|
||||||
|
name={assistant.name}
|
||||||
|
initials={assistant.avatar?.text}
|
||||||
|
color={assistant.avatar?.color}
|
||||||
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiFlexItem grow={false}>
|
||||||
<EuiButtonIcon
|
<EuiButtonIcon
|
||||||
iconType="gear"
|
iconType="gear"
|
||||||
color="text"
|
color="text"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigateToWorkchatUrl(appPaths.agents.edit({ agentId: assistant.id }));
|
navigateToWorkchatUrl(appPaths.assistants.edit({ agentId: assistant.id }));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</EuiFlexItem>
|
</EuiFlexItem>
|
||||||
|
|
|
@ -28,8 +28,8 @@ import { appPaths } from '../../app_paths';
|
||||||
|
|
||||||
export const HomeConversationHistorySection: React.FC<{}> = () => {
|
export const HomeConversationHistorySection: React.FC<{}> = () => {
|
||||||
const { navigateToWorkchatUrl } = useNavigation();
|
const { navigateToWorkchatUrl } = useNavigation();
|
||||||
const { agents } = useAgentList();
|
const { agents, isLoading: isAgentsLoading } = useAgentList();
|
||||||
const { conversations } = useConversationList({});
|
const { conversations, isLoading: isConversationHistoryLoading } = useConversationList({});
|
||||||
|
|
||||||
const agentMap = useMemo<Record<string, Agent>>(() => {
|
const agentMap = useMemo<Record<string, Agent>>(() => {
|
||||||
return agents.reduce<Record<string, Agent>>((map, agent) => {
|
return agents.reduce<Record<string, Agent>>((map, agent) => {
|
||||||
|
@ -42,6 +42,10 @@ export const HomeConversationHistorySection: React.FC<{}> = () => {
|
||||||
return sortAndGroupConversations(sliceRecentConversations(conversations, 10));
|
return sortAndGroupConversations(sliceRecentConversations(conversations, 10));
|
||||||
}, [conversations]);
|
}, [conversations]);
|
||||||
|
|
||||||
|
if (isAgentsLoading || isConversationHistoryLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const recentConversations = conversationGroups.map(
|
const recentConversations = conversationGroups.map(
|
||||||
({ conversations: groupConversations, dateLabel }) => {
|
({ conversations: groupConversations, dateLabel }) => {
|
||||||
return (
|
return (
|
||||||
|
@ -54,14 +58,21 @@ export const HomeConversationHistorySection: React.FC<{}> = () => {
|
||||||
<EuiFlexGroup direction={'column'}>
|
<EuiFlexGroup direction={'column'}>
|
||||||
{groupConversations.map((conversation) => {
|
{groupConversations.map((conversation) => {
|
||||||
const agent = agentMap[conversation.agentId];
|
const agent = agentMap[conversation.agentId];
|
||||||
|
if (!agent) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<EuiFlexItem key={conversation.id}>
|
<EuiFlexItem key={conversation.id}>
|
||||||
<EuiFlexGroup gutterSize="s" alignItems="center">
|
<EuiFlexGroup gutterSize="s" alignItems="center">
|
||||||
{agent && (
|
<EuiFlexItem grow={false}>
|
||||||
<EuiFlexItem grow={false}>
|
<EuiAvatar
|
||||||
<EuiAvatar name={agent.name} size="s" />
|
name={agent.name}
|
||||||
</EuiFlexItem>
|
initials={agent.avatar.text}
|
||||||
)}
|
color={agent.avatar.color}
|
||||||
|
size="s"
|
||||||
|
/>
|
||||||
|
</EuiFlexItem>
|
||||||
|
|
||||||
<EuiFlexItem direction="column" grow={false}>
|
<EuiFlexItem direction="column" grow={false}>
|
||||||
<EuiLink
|
<EuiLink
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|
|
@ -12,7 +12,11 @@ import { queryKeys } from '../query_keys';
|
||||||
export const useAgent = ({ agentId }: { agentId: string }) => {
|
export const useAgent = ({ agentId }: { agentId: string }) => {
|
||||||
const { agentService } = useWorkChatServices();
|
const { agentService } = useWorkChatServices();
|
||||||
|
|
||||||
const { data: agent, isLoading } = useQuery({
|
const {
|
||||||
|
data: agent,
|
||||||
|
isLoading,
|
||||||
|
refetch,
|
||||||
|
} = useQuery({
|
||||||
queryKey: queryKeys.agents.details(agentId),
|
queryKey: queryKeys.agents.details(agentId),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
return agentService.get(agentId);
|
return agentService.get(agentId);
|
||||||
|
@ -22,5 +26,6 @@ export const useAgent = ({ agentId }: { agentId: string }) => {
|
||||||
return {
|
return {
|
||||||
agent,
|
agent,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
refetch,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,9 @@ export interface AgentEditState {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
systemPrompt: string;
|
systemPrompt: string;
|
||||||
|
avatarColor?: string;
|
||||||
|
avatarCustomText?: string;
|
||||||
|
useCase?: string;
|
||||||
public: boolean;
|
public: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +24,9 @@ const emptyState = (): AgentEditState => {
|
||||||
name: '',
|
name: '',
|
||||||
description: '',
|
description: '',
|
||||||
systemPrompt: '',
|
systemPrompt: '',
|
||||||
|
avatarColor: undefined,
|
||||||
|
avatarCustomText: '',
|
||||||
|
useCase: '',
|
||||||
public: false,
|
public: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -28,63 +34,74 @@ const emptyState = (): AgentEditState => {
|
||||||
export const useAgentEdition = ({
|
export const useAgentEdition = ({
|
||||||
agentId,
|
agentId,
|
||||||
onSaveSuccess,
|
onSaveSuccess,
|
||||||
|
onSaveError,
|
||||||
}: {
|
}: {
|
||||||
agentId: string | undefined;
|
agentId?: string;
|
||||||
onSaveSuccess: (agent: Agent) => void;
|
onSaveSuccess: (agent: Agent) => void;
|
||||||
|
onSaveError?: (err: Error) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { agentService } = useWorkChatServices();
|
const { agentService } = useWorkChatServices();
|
||||||
|
|
||||||
const [editState, setEditState] = useState<AgentEditState>(emptyState());
|
const [state, setState] = useState<AgentEditState>(emptyState());
|
||||||
const [isSubmitting, setSubmitting] = useState<boolean>(false);
|
const [isSubmitting, setSubmitting] = useState<boolean>(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchAgent = async () => {
|
const fetchAgent = async () => {
|
||||||
if (agentId) {
|
if (agentId) {
|
||||||
const agent = await agentService.get(agentId);
|
const agent = await agentService.get(agentId);
|
||||||
setEditState({
|
setState({
|
||||||
name: agent.name,
|
name: agent.name,
|
||||||
description: agent.description,
|
description: agent.description,
|
||||||
systemPrompt: agent.configuration.systemPrompt ?? '',
|
systemPrompt: agent.configuration.systemPrompt ?? '',
|
||||||
public: agent.public,
|
public: agent.public,
|
||||||
|
useCase: agent.configuration.useCase ?? '',
|
||||||
|
avatarColor: agent.avatar.color,
|
||||||
|
avatarCustomText: agent.avatar.text ?? '',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
fetchAgent();
|
fetchAgent();
|
||||||
}, [agentId, agentService]);
|
}, [agentId, agentService]);
|
||||||
|
|
||||||
const setFieldValue = <T extends keyof AgentEditState>(key: T, value: AgentEditState[T]) => {
|
const submit = useCallback(
|
||||||
setEditState((previous) => ({ ...previous, [key]: value }));
|
(updatedAgent: AgentEditState) => {
|
||||||
};
|
setSubmitting(true);
|
||||||
|
|
||||||
const submit = useCallback(() => {
|
const payload = {
|
||||||
setSubmitting(true);
|
name: updatedAgent.name,
|
||||||
|
description: updatedAgent.description,
|
||||||
|
configuration: {
|
||||||
|
systemPrompt: updatedAgent.systemPrompt,
|
||||||
|
useCase: updatedAgent.useCase,
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
color: updatedAgent.avatarColor,
|
||||||
|
text: updatedAgent.avatarCustomText,
|
||||||
|
},
|
||||||
|
public: updatedAgent.public,
|
||||||
|
};
|
||||||
|
|
||||||
const payload = {
|
(agentId ? agentService.update(agentId, payload) : agentService.create(payload)).then(
|
||||||
name: editState.name,
|
(response) => {
|
||||||
description: editState.description,
|
setSubmitting(false);
|
||||||
configuration: {
|
if (response.success) {
|
||||||
systemPrompt: editState.systemPrompt,
|
onSaveSuccess(response.agent);
|
||||||
},
|
}
|
||||||
public: editState.public,
|
},
|
||||||
};
|
(err) => {
|
||||||
|
setSubmitting(false);
|
||||||
(agentId ? agentService.update(agentId, payload) : agentService.create(payload)).then(
|
if (onSaveError) {
|
||||||
(response) => {
|
onSaveError(err);
|
||||||
setSubmitting(false);
|
}
|
||||||
if (response.success) {
|
|
||||||
onSaveSuccess(response.agent);
|
|
||||||
}
|
}
|
||||||
},
|
);
|
||||||
(err) => {
|
},
|
||||||
setSubmitting(false);
|
[agentId, agentService, onSaveSuccess, onSaveError]
|
||||||
}
|
);
|
||||||
);
|
|
||||||
}, [agentId, editState, agentService, onSaveSuccess]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
editState,
|
state,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
setFieldValue,
|
|
||||||
submit,
|
submit,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License
|
|
||||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
|
||||||
* 2.0.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import { AgentEditView } from '../components/agents/edition/agent_edit_view';
|
|
||||||
|
|
||||||
const newAgentId = 'create';
|
|
||||||
|
|
||||||
export const WorkChatAgentEditOrCreatePage: React.FC<{}> = () => {
|
|
||||||
const { agentId: agentIdFromParams } = useParams<{
|
|
||||||
agentId: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const agentId = useMemo(() => {
|
|
||||||
return agentIdFromParams === newAgentId ? undefined : agentIdFromParams;
|
|
||||||
}, [agentIdFromParams]);
|
|
||||||
|
|
||||||
return <AgentEditView agentId={agentId} />;
|
|
||||||
};
|
|
|
@ -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 { useParams } from 'react-router-dom';
|
||||||
|
import { AssistantView } from '../components/assistant/details/assistant_view';
|
||||||
|
|
||||||
|
export const WorkChatAssistantOverviewPage: React.FC<{}> = () => {
|
||||||
|
const { agentId } = useParams<{
|
||||||
|
agentId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
return <AssistantView agentId={agentId} selectedTab="details" />;
|
||||||
|
};
|
|
@ -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 { useParams } from 'react-router-dom';
|
||||||
|
import { AssistantView } from '../components/assistant/details/assistant_view';
|
||||||
|
|
||||||
|
export const WorkChatAssistantWorkflowPage: React.FC<{}> = () => {
|
||||||
|
const { agentId } = useParams<{
|
||||||
|
agentId: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
return <AssistantView agentId={agentId} selectedTab="workflow" />;
|
||||||
|
};
|
|
@ -8,10 +8,10 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useBreadcrumb } from '../hooks/use_breadcrumbs';
|
import { useBreadcrumb } from '../hooks/use_breadcrumbs';
|
||||||
import { useAgentList } from '../hooks/use_agent_list';
|
import { useAgentList } from '../hooks/use_agent_list';
|
||||||
import { AgentListView } from '../components/agents/listing/agent_list_view';
|
import { AssistantListView } from '../components/assistant/list/assistant_list_view';
|
||||||
|
|
||||||
export const WorkChatAgentsPage: React.FC<{}> = () => {
|
export const WorkChatAssistantsPage: React.FC<{}> = () => {
|
||||||
useBreadcrumb([{ text: 'Agents' }]);
|
useBreadcrumb([{ text: 'Assistants' }]);
|
||||||
const { agents } = useAgentList();
|
const { agents } = useAgentList();
|
||||||
return <AgentListView agents={agents} />;
|
return <AssistantListView agents={agents} />;
|
||||||
};
|
};
|
|
@ -26,7 +26,7 @@ export const registerApp = ({
|
||||||
title: 'WorkChat',
|
title: 'WorkChat',
|
||||||
updater$: undefined,
|
updater$: undefined,
|
||||||
deepLinks: [
|
deepLinks: [
|
||||||
{ id: 'agents', path: '/agents', title: 'Agents' },
|
{ id: 'agents', path: '/assistants', title: 'Assistants' },
|
||||||
{ id: 'integrations', path: '/integrations', title: 'Integrations' },
|
{ id: 'integrations', path: '/integrations', title: 'Integrations' },
|
||||||
],
|
],
|
||||||
visibleIn: ['sideNav', 'globalSearch'],
|
visibleIn: ['sideNav', 'globalSearch'],
|
||||||
|
|
|
@ -9,28 +9,32 @@ import React from 'react';
|
||||||
import { Route, Routes } from '@kbn/shared-ux-router';
|
import { Route, Routes } from '@kbn/shared-ux-router';
|
||||||
import { WorkChatHomePage } from './pages/home';
|
import { WorkChatHomePage } from './pages/home';
|
||||||
import { WorkchatChatPage } from './pages/chat';
|
import { WorkchatChatPage } from './pages/chat';
|
||||||
import { WorkChatAgentsPage } from './pages/agents';
|
import { WorkChatAssistantsPage } from './pages/assistants';
|
||||||
import { WorkChatAgentEditOrCreatePage } from './pages/agent_edit_or_create';
|
import { WorkChatAssistantOverviewPage } from './pages/assistant_details';
|
||||||
import { WorkChatIntegrationsPage } from './pages/integrations';
|
import { WorkChatIntegrationsPage } from './pages/integrations';
|
||||||
import { WorkChatIntegrationEditOrCreatePage } from './pages/integration_edit_or_create';
|
import { WorkChatIntegrationEditOrCreatePage } from './pages/integration_edit_or_create';
|
||||||
|
import { WorkChatAssistantWorkflowPage } from './pages/assistant_workflow';
|
||||||
export const WorkchatAppRoutes: React.FC<{}> = () => {
|
export const WorkchatAppRoutes: React.FC<{}> = () => {
|
||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/agents/:agentId/chat/:conversationId">
|
<Route path="/assistants/:agentId/chat/:conversationId">
|
||||||
<WorkchatChatPage />
|
<WorkchatChatPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/agents/:agentId/chat">
|
<Route path="/assistants/:agentId/chat">
|
||||||
<WorkchatChatPage />
|
<WorkchatChatPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/agents/create" strict>
|
<Route path="/assistants/create" strict>
|
||||||
<WorkChatAgentEditOrCreatePage />
|
<WorkChatAssistantOverviewPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/agents/:agentId/edit" strict>
|
<Route path="/assistants/:agentId/edit" strict>
|
||||||
<WorkChatAgentEditOrCreatePage />
|
<WorkChatAssistantOverviewPage />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/agents" strict>
|
<Route path="/assistants/:agentId/workflow" strict>
|
||||||
<WorkChatAgentsPage />
|
<WorkChatAssistantWorkflowPage />
|
||||||
|
</Route>
|
||||||
|
<Route path="/assistants" strict>
|
||||||
|
<WorkChatAssistantsPage />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="/integrations/create">
|
<Route path="/integrations/create">
|
||||||
|
|
|
@ -63,8 +63,13 @@ export const registerAgentRoutes = ({ getServices, router, logger }: RouteDepend
|
||||||
description: schema.string({ defaultValue: '' }),
|
description: schema.string({ defaultValue: '' }),
|
||||||
configuration: schema.object({
|
configuration: schema.object({
|
||||||
systemPrompt: schema.maybe(schema.string()),
|
systemPrompt: schema.maybe(schema.string()),
|
||||||
|
useCase: schema.maybe(schema.string()),
|
||||||
}),
|
}),
|
||||||
public: schema.boolean({ defaultValue: false }),
|
public: schema.boolean({ defaultValue: false }),
|
||||||
|
avatar: schema.object({
|
||||||
|
color: schema.maybe(schema.string()),
|
||||||
|
text: schema.maybe(schema.string()),
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -105,6 +110,11 @@ export const registerAgentRoutes = ({ getServices, router, logger }: RouteDepend
|
||||||
description: schema.string({ defaultValue: '' }),
|
description: schema.string({ defaultValue: '' }),
|
||||||
configuration: schema.object({
|
configuration: schema.object({
|
||||||
systemPrompt: schema.maybe(schema.string()),
|
systemPrompt: schema.maybe(schema.string()),
|
||||||
|
useCase: schema.maybe(schema.string()),
|
||||||
|
}),
|
||||||
|
avatar: schema.object({
|
||||||
|
color: schema.maybe(schema.string()),
|
||||||
|
text: schema.maybe(schema.string()),
|
||||||
}),
|
}),
|
||||||
public: schema.boolean({ defaultValue: false }),
|
public: schema.boolean({ defaultValue: false }),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -15,7 +15,7 @@ export const agentSoType: SavedObjectsType<AgentAttributes> = {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
namespaceType: 'agnostic',
|
namespaceType: 'agnostic',
|
||||||
mappings: {
|
mappings: {
|
||||||
dynamic: 'strict',
|
dynamic: false,
|
||||||
properties: {
|
properties: {
|
||||||
agent_id: { type: 'keyword' },
|
agent_id: { type: 'keyword' },
|
||||||
agent_name: { type: 'text' },
|
agent_name: { type: 'text' },
|
||||||
|
@ -44,4 +44,8 @@ export interface AgentAttributes {
|
||||||
access_control: {
|
access_control: {
|
||||||
public: boolean;
|
public: boolean;
|
||||||
};
|
};
|
||||||
|
avatar: {
|
||||||
|
color?: string;
|
||||||
|
text?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { agentTypeName, type AgentAttributes } from '../../saved_objects/agents'
|
||||||
import { WorkchatError } from '../../errors';
|
import { WorkchatError } from '../../errors';
|
||||||
import { createBuilder } from '../../utils/so_filters';
|
import { createBuilder } from '../../utils/so_filters';
|
||||||
import { savedObjectToModel, createRequestToRaw, updateToAttributes } from './convert_model';
|
import { savedObjectToModel, createRequestToRaw, updateToAttributes } from './convert_model';
|
||||||
|
import { getRandomColorFromPalette } from '../../utils/color';
|
||||||
|
|
||||||
interface AgentClientOptions {
|
interface AgentClientOptions {
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
@ -21,7 +22,7 @@ interface AgentClientOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AgentUpdatableFields = Partial<
|
export type AgentUpdatableFields = Partial<
|
||||||
Pick<Agent, 'name' | 'description' | 'configuration' | 'public'>
|
Pick<Agent, 'name' | 'description' | 'configuration' | 'public' | 'avatar'>
|
||||||
>;
|
>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -83,12 +84,15 @@ export class AgentClientImpl implements AgentClient {
|
||||||
async create(createRequest: AgentCreateRequest): Promise<Agent> {
|
async create(createRequest: AgentCreateRequest): Promise<Agent> {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const id = createRequest.id ?? uuidv4();
|
const id = createRequest.id ?? uuidv4();
|
||||||
|
const color = createRequest.avatar?.color ?? getRandomColorFromPalette();
|
||||||
const attributes = createRequestToRaw({
|
const attributes = createRequestToRaw({
|
||||||
createRequest,
|
createRequest,
|
||||||
id,
|
id,
|
||||||
user: this.user,
|
user: this.user,
|
||||||
creationDate: now,
|
creationDate: now,
|
||||||
|
color,
|
||||||
});
|
});
|
||||||
|
|
||||||
const created = await this.client.create<AgentAttributes>(agentTypeName, attributes, { id });
|
const created = await this.client.create<AgentAttributes>(agentTypeName, attributes, { id });
|
||||||
return savedObjectToModel(created);
|
return savedObjectToModel(created);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,10 @@ export const savedObjectToModel = ({ attributes }: SavedObject<AgentAttributes>)
|
||||||
},
|
},
|
||||||
configuration: attributes.configuration,
|
configuration: attributes.configuration,
|
||||||
public: attributes.access_control.public,
|
public: attributes.access_control.public,
|
||||||
|
avatar: {
|
||||||
|
color: attributes.avatar?.color,
|
||||||
|
text: attributes.avatar?.text,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +39,7 @@ export const updateToAttributes = ({
|
||||||
agent_name: updatedFields.name,
|
agent_name: updatedFields.name,
|
||||||
description: updatedFields.description,
|
description: updatedFields.description,
|
||||||
configuration: updatedFields.configuration,
|
configuration: updatedFields.configuration,
|
||||||
|
avatar: updatedFields.avatar,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -43,11 +48,13 @@ export const createRequestToRaw = ({
|
||||||
id,
|
id,
|
||||||
user,
|
user,
|
||||||
creationDate,
|
creationDate,
|
||||||
|
color,
|
||||||
}: {
|
}: {
|
||||||
createRequest: AgentCreateRequest;
|
createRequest: AgentCreateRequest;
|
||||||
id: string;
|
id: string;
|
||||||
user: UserNameAndId;
|
user: UserNameAndId;
|
||||||
creationDate: Date;
|
creationDate: Date;
|
||||||
|
color: string;
|
||||||
}): AgentAttributes => {
|
}): AgentAttributes => {
|
||||||
return {
|
return {
|
||||||
agent_id: id,
|
agent_id: id,
|
||||||
|
@ -60,5 +67,9 @@ export const createRequestToRaw = ({
|
||||||
access_control: {
|
access_control: {
|
||||||
public: createRequest.public,
|
public: createRequest.public,
|
||||||
},
|
},
|
||||||
|
avatar: {
|
||||||
|
color: color || createRequest.avatar.color,
|
||||||
|
text: createRequest.avatar.text,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 { euiPaletteColorBlind } from '@elastic/eui';
|
||||||
|
|
||||||
|
export const getRandomColorFromPalette = (): string => {
|
||||||
|
const palette = euiPaletteColorBlind();
|
||||||
|
return palette[Math.floor(Math.random() * palette.length)];
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue