From d2452727baf99c68998da50f399290de3ab943f4 Mon Sep 17 00:00:00 2001 From: Coen Warmer Date: Tue, 8 Aug 2023 22:38:13 +0200 Subject: [PATCH] Cleanup --- .../public/components/chat/chat_item.tsx | 226 ++++-------------- .../components/chat/chat_item_actions.tsx | 130 ++++++++++ .../components/chat/chat_item_controls.tsx | 75 ++++++ .../public/utils/get_role_translation.ts | 30 +++ 4 files changed, 279 insertions(+), 182 deletions(-) create mode 100644 x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx create mode 100644 x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_controls.tsx create mode 100644 x-pack/plugins/observability_ai_assistant/public/utils/get_role_translation.ts 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 6e1f1ea28ff87..93c75b8e2172d 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 @@ -5,38 +5,23 @@ * 2.0. */ -import React, { useEffect, useState, useRef } from 'react'; +import React, { useState } from 'react'; import { css } from '@emotion/css'; import { EuiAccordion, - EuiButtonIcon, EuiComment, - EuiFlexGroup, EuiErrorBoundary, - EuiFlexItem, - EuiPopover, + EuiPanel, EuiSpacer, - EuiText, useGeneratedHtmlId, - EuiHorizontalRule, - EuiPanel, - useEuiTheme, } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { MessageRole } from '../../../common/types'; -import { Feedback, FeedbackButtons } from '../feedback_buttons'; -import { ChatItemContentInlinePromptEditor } from './chat_item_content_inline_prompt_editor'; -import { RegenerateResponseButton } from '../buttons/regenerate_response_button'; -import { StopGeneratingButton } from '../buttons/stop_generating_button'; +import { ChatItemActions } from './chat_item_actions'; import { ChatItemAvatar } from './chat_item_avatar'; +import { ChatItemContentInlinePromptEditor } from './chat_item_content_inline_prompt_editor'; +import { ChatItemControls } from './chat_item_controls'; import { ChatTimelineItem } from './chat_timeline'; - -export interface ChatItemAction { - id: string; - label: string; - icon: string; - handler: () => void; -} +import { getRoleTranslation } from '../../utils/get_role_translation'; +import type { Feedback } from '../feedback_buttons'; export interface ChatItemProps extends ChatTimelineItem { onEditSubmit: (content: string) => void; @@ -91,25 +76,23 @@ export function ChatItem({ role, title, }: ChatItemProps) { - const { euiTheme } = useEuiTheme(); const accordionId = useGeneratedHtmlId({ prefix: 'chat' }); - const inlineEditInputRef = useRef(null); - const [editing, setEditing] = useState(false); - const [isPopoverOpen, setIsPopoverOpen] = useState(); - const [isAccordionOpen, setIsAccordionOpen] = useState(false); + const [isExpanded, setIsExpanded] = useState(Boolean(element)); + + const actions = [canCopy, collapsed, canCopy].filter(Boolean); const noBodyMessageClassName = css` .euiCommentEvent__body { padding: 0; - height: ${isAccordionOpen ? 'fit-content' : '0px'}; + height: ${isExpanded ? 'fit-content' : '0px'}; overflow: hidden; } `; const handleAccordionToggle = () => { - setIsAccordionOpen(!isAccordionOpen); + setIsExpanded(!isExpanded); if (editing) { setEditing(false); @@ -118,7 +101,7 @@ export function ChatItem({ const handleToggleEdit = () => { if (collapsed) { - setIsAccordionOpen(true); + setIsExpanded(false); } setEditing(!editing); }; @@ -128,100 +111,12 @@ export function ChatItem({ onEditSubmit(newPrompt); }; - useEffect(() => { - if (editing) { - inlineEditInputRef.current?.focus(); - } - }, [editing]); - - useEffect(() => { - const timeout = setTimeout(() => { - if (isPopoverOpen) { - setIsPopoverOpen(undefined); - } - }, 800); - - return () => { - clearTimeout(timeout); - }; - }, [isPopoverOpen]); - - const actions: ChatItemAction[] = [ - ...(canEdit - ? [ - { - id: 'edit', - icon: 'documentEdit', - label: '', - handler: () => { - handleToggleEdit(); - }, - }, - ] - : []), - ...(collapsed - ? [ - { - id: 'expand', - icon: isAccordionOpen ? 'eyeClosed' : 'eye', - label: '', - handler: () => { - handleAccordionToggle(); - }, - }, - ] - : []), - ...(canCopy - ? [ - { - id: 'copy', - icon: 'copyClipboard', - label: i18n.translate( - 'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage', - { - defaultMessage: 'Copied message', - } - ), - handler: () => { - navigator.clipboard.writeText(content || ''); - }, - }, - ] - : []), - ]; - - let controls: React.ReactNode; - - const displayFeedback = !error && canGiveFeedback; - const displayRegenerate = !loading && canRegenerate; - - if (loading) { - controls = ; - } else if (displayFeedback || displayRegenerate) { - controls = ( - <> - - - - - {displayFeedback ? ( - - - - ) : null} - {displayRegenerate ? ( - - - - ) : null} - - - - ); - } + const handleCopyToClipboard = () => { + navigator.clipboard.writeText(content || ''); + }; let contentElement: React.ReactNode = - content || error || controls ? ( + content || error ? ( @@ -247,40 +142,22 @@ export function ChatItem({ return ( - label ? ( - { - setIsPopoverOpen(id); - handler(); - }} - color="text" - /> - } - isOpen={isPopoverOpen === id} - closePopover={() => setIsPopoverOpen(undefined)} - panelPaddingSize="s" - > - -

{label}

-
-
- ) : ( - - ) - )} + timelineAvatar={ + + } + username={getRoleTranslation(role)} event={title} + actions={ + + } className={ actions.length === 0 && !content ? noPanelMessageClassName @@ -288,37 +165,22 @@ export function ChatItem({ ? noBodyMessageClassName : normalMessageClassName } - timelineAvatar={ - - } - username={getRoleTranslation(role)} > {element ? {element} : null} + {contentElement} - {controls} + +
); } - -const getRoleTranslation = (role: MessageRole) => { - if (role === MessageRole.User) { - return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.user.label', { - defaultMessage: 'You', - }); - } - - if (role === MessageRole.System) { - return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.system.label', { - defaultMessage: 'System', - }); - } - - return i18n.translate( - 'xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label', - { - defaultMessage: 'Elastic Assistant', - } - ); -}; diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx new file mode 100644 index 0000000000000..e9aa0c29508a6 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_actions.tsx @@ -0,0 +1,130 @@ +/* + * 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, { useEffect, useState } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiButtonIcon, EuiPopover, EuiText } from '@elastic/eui'; + +export interface ChatItemAction { + id: string; + label: string; + icon: string; + handler: () => void; +} + +export function ChatItemActions({ + canEdit, + collapsed, + canCopy, + isCollapsed, + onToggleEdit, + onAccordionToggle, + onCopyToClipboard, +}: { + canEdit: boolean; + collapsed: boolean; + canCopy: boolean; + isCollapsed: boolean; + onToggleEdit: () => void; + onAccordionToggle: () => void; + onCopyToClipboard: () => void; +}) { + const [isPopoverOpen, setIsPopoverOpen] = useState(); + + useEffect(() => { + const timeout = setTimeout(() => { + if (isPopoverOpen) { + setIsPopoverOpen(undefined); + } + }, 800); + + return () => { + clearTimeout(timeout); + }; + }, [isPopoverOpen]); + + const actions: ChatItemAction[] = [ + ...(canEdit + ? [ + { + id: 'edit', + icon: 'documentEdit', + label: '', + handler: () => { + onToggleEdit(); + }, + }, + ] + : []), + ...(collapsed + ? [ + { + id: 'expand', + icon: isCollapsed ? 'eyeClosed' : 'eye', + label: '', + handler: () => { + onAccordionToggle(); + }, + }, + ] + : []), + ...(canCopy + ? [ + { + id: 'copy', + icon: 'copyClipboard', + label: i18n.translate( + 'xpack.observabilityAiAssistant.chatTimeline.actions.copyMessage', + { + defaultMessage: 'Copied message', + } + ), + handler: () => { + onCopyToClipboard(); + }, + }, + ] + : []), + ]; + return ( + <> + {actions.map(({ id, icon, label, handler }) => + label ? ( + { + setIsPopoverOpen(id); + handler(); + }} + color="text" + /> + } + isOpen={isPopoverOpen === id} + closePopover={() => setIsPopoverOpen(undefined)} + panelPaddingSize="s" + > + +

{label}

+
+
+ ) : ( + + ) + )} + + ); +} diff --git a/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_controls.tsx b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_controls.tsx new file mode 100644 index 0000000000000..74eeb184cb5c7 --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/components/chat/chat_item_controls.tsx @@ -0,0 +1,75 @@ +/* + * 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, + EuiHorizontalRule, + EuiPanel, + EuiSpacer, + useEuiTheme, +} from '@elastic/eui'; +import { Feedback, FeedbackButtons } from '../feedback_buttons'; +import { RegenerateResponseButton } from '../buttons/regenerate_response_button'; +import { StopGeneratingButton } from '../buttons/stop_generating_button'; + +export function ChatItemControls({ + error, + loading, + canRegenerate, + canGiveFeedback, + onFeedbackClick, + onRegenerateClick, + onStopGeneratingClick, +}: { + error: any; + loading: boolean; + canRegenerate: boolean; + canGiveFeedback: boolean; + onFeedbackClick: (feedback: Feedback) => void; + onRegenerateClick: () => void; + onStopGeneratingClick: () => void; +}) { + const { euiTheme } = useEuiTheme(); + + const displayFeedback = !error && canGiveFeedback; + const displayRegenerate = !loading && canRegenerate; + + let controls; + + if (loading) { + controls = ; + } else if (displayFeedback || displayRegenerate) { + controls = ( + + {displayFeedback ? ( + + + + ) : null} + {displayRegenerate ? ( + + + + ) : null} + + ); + } else { + controls = null; + } + + return controls ? ( + <> + + + + {controls} + + + ) : null; +} diff --git a/x-pack/plugins/observability_ai_assistant/public/utils/get_role_translation.ts b/x-pack/plugins/observability_ai_assistant/public/utils/get_role_translation.ts new file mode 100644 index 0000000000000..390c0cfd0321f --- /dev/null +++ b/x-pack/plugins/observability_ai_assistant/public/utils/get_role_translation.ts @@ -0,0 +1,30 @@ +/* + * 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'; +import { MessageRole } from '../../common'; + +export function getRoleTranslation(role: MessageRole) { + if (role === MessageRole.User) { + return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.user.label', { + defaultMessage: 'You', + }); + } + + if (role === MessageRole.System) { + return i18n.translate('xpack.observabilityAiAssistant.chatTimeline.messages.system.label', { + defaultMessage: 'System', + }); + } + + return i18n.translate( + 'xpack.observabilityAiAssistant.chatTimeline.messages.elasticAssistant.label', + { + defaultMessage: 'Elastic Assistant', + } + ); +}