Skip to content

Commit

Permalink
Merge pull request elastic#41 from CoenWarmer/storybook
Browse files Browse the repository at this point in the history
  • Loading branch information
CoenWarmer authored Aug 14, 2023
2 parents 352395c + 98d981d commit 42a623d
Show file tree
Hide file tree
Showing 11 changed files with 259 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ import React, { useState } from 'react';
import { ObservabilityAIAssistantChatServiceProvider } from '../../context/observability_ai_assistant_chat_service_provider';
import { useAbortableAsync } from '../../hooks/use_abortable_async';
import { useConversation } from '../../hooks/use_conversation';
import { useGenAIConnectors } from '../../hooks/use_genai_connectors';
import { useObservabilityAIAssistant } from '../../hooks/use_observability_ai_assistant';
import { EMPTY_CONVERSATION_TITLE } from '../../i18n';
import { AssistantAvatar } from '../assistant_avatar';
import { ChatFlyout } from '../chat/chat_flyout';

export function ObservabilityAIAssistantActionMenuItem() {
const service = useObservabilityAIAssistant();
const connectors = useGenAIConnectors();

const [isOpen, setIsOpen] = useState(false);

Expand All @@ -34,6 +36,7 @@ export function ObservabilityAIAssistantActionMenuItem() {

const { conversation, displayedMessages, setDisplayedMessages, save } = useConversation({
conversationId,
connectorId: connectors.selectedConnector,
});

if (!service.isEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,14 +78,20 @@ const defaultProps: ComponentProps<typeof Component> = {
trigger: MessageRole.Assistant,
},
actions: {
canEdit: true,
canEdit: false,
canCopy: true,
canGiveFeedback: true,
canRegenerate: true,
},
}),
buildFunctionChatItem({
content: '{ "message": "The arguments are wrong" }',
error: new Error(),
actions: {
canRegenerate: false,
canEdit: true,
canGiveFeedback: false,
canCopy: true,
},
}),
buildAssistantChatItem({
Expand All @@ -98,6 +104,9 @@ const defaultProps: ComponentProps<typeof Component> = {
},
actions: {
canEdit: true,
canCopy: true,
canGiveFeedback: true,
canRegenerate: true,
},
}),
buildFunctionChatItem({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ const titleClassName = css`
text-transform: uppercase;
`;

const newChatButtonWrapperClassName = css`
padding-bottom: 5px;
`;

export function ConversationList({
selected,
onClickNewChat,
Expand Down Expand Up @@ -101,6 +105,7 @@ export function ConversationList({
isActive={conversation.id === selected}
isDisabled={loading}
href={conversation.href}
wrapText
extraAction={
conversation.id
? {
Expand Down Expand Up @@ -135,7 +140,7 @@ export function ConversationList({
<EuiFlexItem grow={false}>
<EuiPanel paddingSize="s" hasBorder={false} hasShadow={false}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem grow>
<EuiFlexItem grow className={newChatButtonWrapperClassName}>
<NewChatButton onClick={onClickNewChat} />
</EuiFlexItem>
</EuiFlexGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,25 @@
* 2.0.
*/

import React, { useState } from 'react';
import React, { useEffect, useRef, useState } from 'react';
import {
EuiButtonEmpty,
EuiContextMenu,
EuiContextMenuPanel,
EuiHighlight,
EuiPopover,
EuiSelectable,
EuiSelectableOption,
EuiSpacer,
EuiText,
} from '@elastic/eui';
import type { EuiSelectableOptionCheckedType } from '@elastic/eui/src/components/selectable/selectable_option';
import { i18n } from '@kbn/i18n';
import { FunctionDefinition } from '../../../common/types';
import { useObservabilityAIAssistantChatService } from '../../hooks/use_observability_ai_assistant_chat_service';

interface FunctionListOption {
label: string;
searchableLabel: string;
}

export function FunctionListPopover({
selectedFunctionName,
onSelectFunction,
Expand All @@ -27,17 +33,78 @@ export function FunctionListPopover({
onSelectFunction: (func: string) => void;
disabled: boolean;
}) {
const chatService = useObservabilityAIAssistantChatService();
const { getFunctions } = useObservabilityAIAssistantChatService();
const filterRef = useRef<HTMLInputElement | null>(null);

const [functionOptions, setFunctionOptions] = useState<
Array<EuiSelectableOption<FunctionListOption>>
>([]);

const [isFunctionListOpen, setIsFunctionListOpen] = useState(false);

const handleClickFunctionList = () => {
setIsFunctionListOpen(!isFunctionListOpen);
};

const handleSelectFunction = (func: FunctionDefinition) => {
const handleSelectFunction = (func: EuiSelectableOption<FunctionListOption>) => {
setIsFunctionListOpen(false);
onSelectFunction(func.options.name);
onSelectFunction(func.label);
};

useEffect(() => {
const keyboardListener = (event: KeyboardEvent) => {
if (event.shiftKey && event.code === 'Digit4') {
setIsFunctionListOpen(true);
}
};

window.addEventListener('keyup', keyboardListener);

return () => {
window.removeEventListener('keyup', keyboardListener);
};
}, []);

useEffect(() => {
if (isFunctionListOpen && filterRef.current) {
filterRef.current.focus();
}
}, [isFunctionListOpen]);

useEffect(() => {
const options = getFunctions().map((func) => ({
label: func.options.name,
searchableLabel: func.options.descriptionForUser,
checked:
func.options.name === selectedFunctionName
? ('on' as EuiSelectableOptionCheckedType)
: ('off' as EuiSelectableOptionCheckedType),
}));

setFunctionOptions(options);
}, [getFunctions, selectedFunctionName]);

const renderCountryOption = (
option: EuiSelectableOption<FunctionListOption>,
searchValue: string
) => {
return (
<>
<EuiText size="s">
<p>
<strong>
<EuiHighlight search={searchValue}>{option.label}</EuiHighlight>{' '}
</strong>
</p>
</EuiText>
<EuiSpacer size="xs" />
<EuiText size="s">
<p style={{ textOverflow: 'ellipsis', overflow: 'hidden' }}>
<EuiHighlight search={searchValue}>{option.searchableLabel || ''}</EuiHighlight>
</p>
</EuiText>
</>
);
};

return (
Expand All @@ -53,43 +120,51 @@ export function FunctionListPopover({
>
{selectedFunctionName
? selectedFunctionName
: i18n.translate('xpack.observabilityAiAssistant.prompt.callFunction', {
: i18n.translate('xpack.observabilityAiAssistant.prompt.functionList.callFunction', {
defaultMessage: 'Call function',
})}
</EuiButtonEmpty>
}
closePopover={handleClickFunctionList}
css={{ maxWidth: 400 }}
panelPaddingSize="none"
isOpen={isFunctionListOpen}
>
<EuiContextMenuPanel size="s">
<EuiContextMenu
initialPanelId={0}
panels={[
{
id: 0,
width: 500,
items: chatService.getFunctions().map((func) => ({
name: (
<>
<EuiText size="s">
<p>
<strong>{func.options.name}</strong>
</p>
</EuiText>
<EuiSpacer size="xs" />
<EuiText size="s">
<p>{func.options.descriptionForUser}</p>
</EuiText>
</>
),
onClick: () => handleSelectFunction(func),
})),
},
]}
/>
</EuiContextMenuPanel>
<EuiSelectable
aria-label={i18n.translate(
'xpack.observabilityAiAssistant.prompt.functionList.functionList',
{
defaultMessage: 'Function list',
}
)}
listProps={{
isVirtualized: false,
showIcons: false,
}}
options={functionOptions}
renderOption={renderCountryOption}
searchable
searchProps={{
'data-test-subj': 'searchFiltersList',
inputRef: (node) => (filterRef.current = node),
placeholder: i18n.translate('xpack.observabilityAiAssistant.prompt.functionList.filter', {
defaultMessage: 'Filter',
}),
}}
singleSelection
onChange={(functions) => {
const selectedFunction = functions.filter((fn) => fn.checked !== 'off');
if (selectedFunction && selectedFunction.length === 1) {
handleSelectFunction({ ...selectedFunction[0], checked: 'on' });
}
}}
>
{(list, search) => (
<div style={{ overflow: 'hidden' }}>
{search}
<div style={{ width: 500, height: 350, overflowY: 'scroll' }}>{list}</div>
</div>
)}
</EuiSelectable>
</EuiPopover>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n';
import { merge, omit } from 'lodash';
import { Dispatch, SetStateAction, useState } from 'react';
import type { Conversation, Message } from '../../common';
import { type Conversation, type Message } from '../../common';
import type { ConversationCreateRequest } from '../../common/types';
import { ObservabilityAIAssistantChatService } from '../types';
import { useAbortableAsync, type AbortableAsyncState } from './use_abortable_async';
Expand All @@ -18,14 +18,16 @@ import { createNewConversation } from './use_timeline';
export function useConversation({
conversationId,
chatService,
connectorId,
}: {
conversationId?: string;
chatService?: ObservabilityAIAssistantChatService;
connectorId: string | undefined;
}): {
conversation: AbortableAsyncState<ConversationCreateRequest | Conversation | undefined>;
displayedMessages: Message[];
setDisplayedMessages: Dispatch<SetStateAction<Message[]>>;
save: (messages: Message[]) => Promise<Conversation>;
save: (messages: Message[], handleRefreshConversations?: () => void) => Promise<Conversation>;
} {
const service = useObservabilityAIAssistant();

Expand Down Expand Up @@ -67,11 +69,12 @@ export function useConversation({
conversation,
displayedMessages,
setDisplayedMessages,
save: (messages: Message[]) => {
save: (messages: Message[], handleRefreshConversations?: () => void) => {
const conversationObject = conversation.value!;

return conversationId
? service
.callApi(`POST /internal/observability_ai_assistant/conversation/{conversationId}`, {
.callApi(`PUT /internal/observability_ai_assistant/conversation/{conversationId}`, {
signal: null,
params: {
path: {
Expand Down Expand Up @@ -100,14 +103,38 @@ export function useConversation({
throw err;
})
: service
.callApi(`PUT /internal/observability_ai_assistant/conversation`, {
.callApi(`POST /internal/observability_ai_assistant/conversation`, {
signal: null,
params: {
body: {
conversation: merge({}, conversationObject, { messages }),
},
},
})
.then((nextConversation) => {
if (connectorId) {
service
.callApi(
`PUT /internal/observability_ai_assistant/conversation/{conversationId}/auto_title`,
{
signal: null,
params: {
path: {
conversationId: nextConversation.conversation.id,
},
body: {
connectorId,
},
},
}
)
.then(() => {
handleRefreshConversations?.();
return conversation.refresh();
});
}
return nextConversation;
})
.catch((err) => {
notifications.toasts.addError(err, {
title: i18n.translate('xpack.observabilityAiAssistant.errorCreatingConversation', {
Expand Down
Loading

0 comments on commit 42a623d

Please sign in to comment.