diff --git a/x-pack/plugins/observability_ai_assistant/public/components/ask_assistant_button.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.stories.tsx similarity index 100% rename from x-pack/plugins/observability_ai_assistant/public/components/ask_assistant_button.stories.tsx rename to x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.stories.tsx diff --git a/x-pack/plugins/observability_ai_assistant/public/components/ask_assistant_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.tsx similarity index 100% rename from x-pack/plugins/observability_ai_assistant/public/components/ask_assistant_button.tsx rename to x-pack/plugins/observability_ai_assistant/public/components/buttons/ask_assistant_button.tsx diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/hide_expand_conversation_list_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/hide_expand_conversation_list_button.tsx new file mode 100644 index 00000000000000..8921380c801e10 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/hide_expand_conversation_list_button.tsx @@ -0,0 +1,25 @@ +/* + * 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 { EuiButtonEmpty } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function HideExpandConversationListButton( + props: React.ComponentProps & { isExpanded: boolean } +) { + return ( + + {props.isExpanded + ? i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.hide', { + defaultMessage: 'Hide chats', + }) + : i18n.translate('xpack.observabilityAiAssistant.hideExpandConversationButton.show', { + defaultMessage: 'Show chats', + })} + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.stories.tsx new file mode 100644 index 00000000000000..f4e0cae677ef01 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.stories.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; +import { NewChatButton as Component } from './new_chat_button'; + +const meta: ComponentMeta = { + component: Component, + title: 'app/Atoms/NewChatButton', +}; + +export default meta; + +export const NewChatButton: ComponentStoryObj = { + args: {}, +}; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.tsx new file mode 100644 index 00000000000000..453c8da3511198 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/new_chat_button.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import React from 'react'; +import { EuiButton } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +export function NewChatButton(props: React.ComponentProps) { + return ( + + {i18n.translate('xpack.observabilityAiAssistant.newChatButton', { + defaultMessage: 'New chat', + })} + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.stories.tsx new file mode 100644 index 00000000000000..bfbb506a9e90aa --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.stories.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; +import { RegenerateResponseButton as Component } from './regenerate_response_button'; + +const meta: ComponentMeta = { + component: Component, + title: 'app/Atoms/RegenerateResponseButton', +}; + +export default meta; + +export const RegenerateResponseButton: ComponentStoryObj = { + args: {}, +}; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/regenerate_response_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.tsx similarity index 100% rename from x-pack/plugins/observability_ai_assistant/public/components/regenerate_response_button.tsx rename to x-pack/plugins/observability_ai_assistant/public/components/buttons/regenerate_response_button.tsx diff --git a/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.stories.tsx new file mode 100644 index 00000000000000..de9116900b61b2 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.stories.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import { ComponentMeta, ComponentStoryObj } from '@storybook/react'; +import { StartChatButton as Component } from './start_chat_button'; + +const meta: ComponentMeta = { + component: Component, + title: 'app/Atoms/StartChatButton', +}; + +export default meta; + +export const StartChatButton: ComponentStoryObj = { + args: {}, +}; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/start_chat_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.tsx similarity index 100% rename from x-pack/plugins/observability_ai_assistant/public/components/start_chat_button.tsx rename to x-pack/plugins/observability_ai_assistant/public/components/buttons/start_chat_button.tsx diff --git a/x-pack/plugins/observability_ai_assistant/public/components/stop_generating_button.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.stories.tsx similarity index 100% rename from x-pack/plugins/observability_ai_assistant/public/components/stop_generating_button.stories.tsx rename to x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.stories.tsx diff --git a/x-pack/plugins/observability_ai_assistant/public/components/stop_generating_button.tsx b/x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.tsx similarity index 100% rename from x-pack/plugins/observability_ai_assistant/public/components/stop_generating_button.tsx rename to x-pack/plugins/observability_ai_assistant/public/components/buttons/stop_generating_button.tsx diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx index 92b8d85a9dc117..3e9c7fc56a1df8 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_body.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiPanel, useEuiTheme } from '@elastic/eui'; import { css } from '@emotion/css'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import React from 'react'; @@ -13,6 +13,7 @@ import { type ConversationCreateRequest } from '../../../common/types'; import type { UseChatResult } from '../../hooks/use_chat'; import type { UseGenAIConnectorsResult } from '../../hooks/use_genai_connectors'; import { useTimeline } from '../../hooks/use_timeline'; +import { HideExpandConversationListButton } from '../buttons/hide_expand_conversation_list_button'; import { ChatHeader } from './chat_header'; import { ChatPromptEditor } from './chat_prompt_editor'; import { ChatTimeline } from './chat_timeline'; @@ -30,12 +31,18 @@ export function ChatBody({ connectors, currentUser, chat, + isConversationListExpanded, + onToggleExpandConversationList, }: { initialConversation?: ConversationCreateRequest; connectors: UseGenAIConnectorsResult; currentUser?: Pick; chat: UseChatResult; + isConversationListExpanded?: boolean; + onToggleExpandConversationList?: () => void; }) { + const { euiTheme } = useEuiTheme(); + const timeline = useTimeline({ initialConversation, connectors, @@ -46,15 +53,31 @@ export function ChatBody({ return ( - - + + + + + + + - + - + ; + +const Template: ComponentStory = (props: ChatFlyoutProps) => { + return ( +
+ +
+ ); +}; + +const defaultProps: ChatFlyoutProps = { + isOpen: true, + initialConversation: { + '@timestamp': '', + conversation: { + title: 'How is this working', + }, + messages: [], + labels: {}, + numeric_labels: {}, + }, + onClose: () => {}, +}; + +export const ChatFlyout = Template.bind({}); +ChatFlyout.args = defaultProps; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx index 437476d3552bab..00fac0dc2bdf0f 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_flyout.tsx @@ -4,13 +4,14 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { EuiFlyout } from '@elastic/eui'; -import React from 'react'; +import React, { useState } from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiFlyout, useEuiTheme } from '@elastic/eui'; import type { ConversationCreateRequest } from '../../../common/types'; import { useChat } from '../../hooks/use_chat'; import { useCurrentUser } from '../../hooks/use_current_user'; import { useGenAIConnectors } from '../../hooks/use_genai_connectors'; import { ChatBody } from './chat_body'; +import { ConversationList } from './conversation_list'; export function ChatFlyout({ initialConversation, @@ -21,20 +22,46 @@ export function ChatFlyout({ isOpen: boolean; onClose: () => void; }) { + const currentUser = useCurrentUser(); + const connectors = useGenAIConnectors(); + const { euiTheme } = useEuiTheme(); + const chat = useChat(); - const connectors = useGenAIConnectors(); + const [isConversationListExpanded, setIsConversationListExpanded] = useState(false); - const currentUser = useCurrentUser(); + const handleClickConversation = (id: string) => {}; + const handleClickNewChat = () => {}; + const handleClickSettings = () => {}; return isOpen ? ( - + + {isConversationListExpanded ? ( + + + + ) : null} + + + setIsConversationListExpanded(!isConversationListExpanded) + } + /> + + ) : null; } diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx index 388dab83f3d336..8d30bdc1ff57d8 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item.tsx @@ -20,8 +20,8 @@ import { MessageRole } from '../../../common/types'; import { Feedback, FeedbackButtons } from '../feedback_buttons'; import { MessagePanel } from '../message_panel/message_panel'; import { MessageText } from '../message_panel/message_text'; -import { RegenerateResponseButton } from '../regenerate_response_button'; -import { StopGeneratingButton } from '../stop_generating_button'; +import { RegenerateResponseButton } from '../buttons/regenerate_response_button'; +import { StopGeneratingButton } from '../buttons/stop_generating_button'; import { ChatItemAvatar } from './chat_item_avatar'; import { ChatItemTitle } from './chat_item_title'; import { ChatTimelineItem } from './chat_timeline'; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx index bfadafa7496a16..db671271176fe9 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_timeline.tsx @@ -5,9 +5,9 @@ * 2.0. */ +import React from 'react'; import { EuiCommentList } from '@elastic/eui'; import type { AuthenticatedUser } from '@kbn/security-plugin/common'; -import React from 'react'; import { MessageRole } from '../../../common/types'; import type { Feedback } from '../feedback_buttons'; import { ChatItem } from './chat_item'; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.stories.tsx new file mode 100644 index 00000000000000..ab949b44942ab4 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.stories.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { ComponentStory } from '@storybook/react'; +import React from 'react'; +import { KibanaReactStorybookDecorator } from '../../utils/storybook_decorator'; +import { ConversationList as Component } from './conversation_list'; + +export default { + component: Component, + title: 'app/Organisms/ConversationList', + decorators: [KibanaReactStorybookDecorator], +}; + +type ConversationListProps = React.ComponentProps; + +const Template: ComponentStory = (props: ConversationListProps) => { + return ( +
+ +
+ ); +}; + +const defaultProps: ConversationListProps = { + onClickConversation: (conversationId: string) => {}, + onClickNewChat: () => {}, + onClickSettings: () => {}, +}; + +export const ConversationList = Template.bind({}); +ConversationList.args = defaultProps; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.tsx new file mode 100644 index 00000000000000..e12b139135f8ce --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/conversation_list.tsx @@ -0,0 +1,64 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem, EuiListGroup, EuiListGroupItem, EuiPanel } from '@elastic/eui'; +import { css } from '@emotion/css'; +import { NewChatButton } from '../buttons/new_chat_button'; +import { useConversations } from '../../hooks/use_conversations'; +import { type ConversationCreateRequest } from '../../../common/types'; + +const containerClassName = css` + height: 100%; +`; + +export function ConversationList({ + selectedConversation, + onClickNewChat, + onClickConversation, +}: { + selectedConversation?: ConversationCreateRequest; + onClickConversation: (conversationId: string) => void; + onClickNewChat: () => void; + onClickSettings: () => void; +}) { + const conversations = useConversations(); + + return ( + + + + + Last 7 days} + size="s" + /> + + {conversations.map((conversation) => ( + onClickConversation(conversation.conversation.id)} + /> + ))} + + + + + + + + + + + + + + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx index 2082095c625996..5c4e3345b07d75 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight.tsx @@ -15,9 +15,9 @@ import { MessagePanel } from '../message_panel/message_panel'; import { MessageText } from '../message_panel/message_text'; import { InsightBase } from './insight_base'; import { InsightMissingCredentials } from './insight_missing_credentials'; -import { StopGeneratingButton } from '../stop_generating_button'; -import { RegenerateResponseButton } from '../regenerate_response_button'; -import { StartChatButton } from '../start_chat_button'; +import { StopGeneratingButton } from '../buttons/stop_generating_button'; +import { RegenerateResponseButton } from '../buttons/regenerate_response_button'; +import { StartChatButton } from '../buttons/start_chat_button'; import { ChatFlyout } from '../chat/chat_flyout'; function ChatContent({ messages, connectorId }: { messages: Message[]; connectorId: string }) { diff --git a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx index 45b184832e619a..7a65f8e62756eb 100644 --- a/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/components/insight/insight_base.stories.tsx @@ -16,8 +16,8 @@ import { ConnectorSelectorBase } from '../connector_selector/connector_selector_ import { MessagePanel } from '../message_panel/message_panel'; import { MessageText } from '../message_panel/message_text'; import { FeedbackButtons } from '../feedback_buttons'; -import { RegenerateResponseButton } from '../regenerate_response_button'; -import { StartChatButton } from '../start_chat_button'; +import { RegenerateResponseButton } from '../buttons/regenerate_response_button'; +import { StartChatButton } from '../buttons/start_chat_button'; export default { component: Component, diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_conversations.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_conversations.ts new file mode 100644 index 00000000000000..d41fd4aac1b299 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/__storybook_mocks__/use_conversations.ts @@ -0,0 +1,63 @@ +/* + * 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 { uniqueId } from 'lodash'; +import { buildConversation } from '../../utils/builders'; + +export function useConversations() { + return [ + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Investigation into Cart service degradation', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + buildConversation({ + conversation: { + id: uniqueId(), + title: 'Why is database service responding with errors after I did rm -rf /postgres', + last_updated: '', + }, + }), + ]; +} diff --git a/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversations.ts b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversations.ts new file mode 100644 index 00000000000000..b94108264007c0 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/hooks/use_conversations.ts @@ -0,0 +1,12 @@ +/* + * 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 { Conversation } from '../../common'; + +export function useConversations(): Conversation[] { + return []; +} diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/builders.ts b/x-pack/plugins/observability_ai_assistant/public/utils/builders.ts index 33dddc215749d8..259a6c9f652ad4 100644 --- a/x-pack/plugins/observability_ai_assistant/public/utils/builders.ts +++ b/x-pack/plugins/observability_ai_assistant/public/utils/builders.ts @@ -6,7 +6,7 @@ */ import { uniqueId } from 'lodash'; -import { MessageRole } from '../../common/types'; +import { MessageRole, Conversation } from '../../common/types'; import { ChatTimelineItem } from '../components/chat/chat_timeline'; type ChatItemBuildProps = Partial & Pick; @@ -77,3 +77,22 @@ export function buildTimelineItems() { items: [buildSystemChatItem(), buildUserChatItem(), buildAssistantChatItem()], }; } + +export function buildConversation(params?: Partial) { + return { + '@timestamp': '', + user: { + name: 'foo', + }, + conversation: { + id: uniqueId(), + title: '', + last_updated: '', + }, + messages: [], + labels: {}, + numeric_labels: {}, + namespace: '', + ...params, + }; +} diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx b/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx index 0ef1cfa5ce3586..5725ce3473fd2d 100644 --- a/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx +++ b/x-pack/plugins/observability_ai_assistant/public/utils/storybook_decorator.tsx @@ -6,8 +6,21 @@ */ import React, { ComponentType } from 'react'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { ObservabilityAIAssistantProvider } from '../context/observability_ai_assistant_provider'; + +const service = { + isEnabled: () => true, + chat: async (options: { + messages: []; + connectorId: string; + // signal: new AbortSignal(); + }) => {}, + // callApi: ObservabilityAIAssistantAPIClient; + getCurrentUser: async () => {}, +}; export function KibanaReactStorybookDecorator(Story: ComponentType) { + console.log('hello?'); return ( - + + + ); }